Skip to content

Commit bf1a393

Browse files
committed
verify that all sources of libraries are included in gradle/verification-metadata.xml
1 parent 85fd962 commit bf1a393

File tree

2 files changed

+117
-51
lines changed

2 files changed

+117
-51
lines changed

.github/workflows/pre-review.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ jobs:
3636
with:
3737
ref: ${{ github.event.pull_request.head.sha || github.ref }}
3838
- uses: gradle/actions/wrapper-validation@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6.0.1
39+
verify-source-checksums:
40+
name: "Verify Source Checksums"
41+
runs-on: ubuntu-latest
42+
steps:
43+
- name: Checkout Repo
44+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45+
with:
46+
ref: ${{ github.event.pull_request.head.sha || github.ref }}
47+
- name: Set up Java
48+
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
49+
with:
50+
distribution: temurin
51+
java-version: 21
52+
- name: Setup Gradle
53+
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6.0.1
54+
with:
55+
cache-disabled: true
56+
- name: Check source checksums are present in verification-metadata.xml
57+
run: ./gradlew checkSourceVerification
3958
spotless-checkLicense:
4059
name: "Spotless & Check License"
4160
runs-on: ubuntu-latest
@@ -126,7 +145,7 @@ jobs:
126145
unittests-passed:
127146
name: "unittests-passed"
128147
runs-on: ubuntu-latest
129-
needs: [compile, unitTests]
148+
needs: [compile, unitTests, verify-source-checksums]
130149
permissions:
131150
checks: write
132151
statuses: write

build.gradle

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import groovy.transform.CompileStatic
1818
import groovy.transform.Memoized
1919
import net.ltgt.gradle.errorprone.CheckSeverity
2020

21+
import groovy.xml.XmlSlurper
2122
import java.text.SimpleDateFormat
2223
import java.util.regex.Pattern
2324

@@ -474,73 +475,119 @@ configure(allprojects - project(':platform')) {
474475
}
475476
}
476477

477-
task resolveSourceArtifacts {
478-
group = 'verification'
479-
description = 'Resolves source artifacts for all configurations so they can be included in dependency verification metadata'
480-
doLast {
481-
def componentIds = new HashSet()
482-
def unresolvedConfigs = 0
483-
484-
def collectComponentIds = { configs, String label ->
485-
configs.matching { it.canBeResolved }.each { config ->
486-
try {
487-
config.incoming.resolutionResult.allComponents.each { component ->
488-
componentIds.add(component.id)
489-
}
490-
} catch (Exception e) {
491-
unresolvedConfigs++
492-
logger.info("Could not resolve ${label} '${config.name}': ${e.message}")
478+
// Shared helpers for source artifact verification tasks
479+
ext.collectExternalComponentIds = { allprojects, logger ->
480+
def componentIds = new HashSet()
481+
def unresolvedConfigs = 0
482+
def collectFromConfigs = { configs, String label ->
483+
configs.matching { it.canBeResolved }.each { config ->
484+
try {
485+
config.incoming.resolutionResult.allComponents.each { component ->
486+
componentIds.add(component.id)
493487
}
488+
} catch (Exception e) {
489+
unresolvedConfigs++
490+
logger.info("Could not resolve ${label} '${config.name}': ${e.message}")
494491
}
495492
}
493+
}
494+
allprojects.each { proj ->
495+
collectFromConfigs(proj.configurations, "${proj.path} config")
496+
collectFromConfigs(proj.buildscript.configurations, "${proj.path} buildscript config")
497+
}
498+
if (unresolvedConfigs > 0) {
499+
logger.warn("${unresolvedConfigs} configurations could not be resolved (run with --info for details)")
500+
}
501+
return componentIds.findAll { it instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier }
502+
}
496503

497-
allprojects.each { proj ->
498-
collectComponentIds(proj.configurations, "${proj.path} config")
499-
collectComponentIds(proj.buildscript.configurations, "${proj.path} buildscript config")
504+
ext.resolveSourceComponents = { depHandler, buildscriptDepHandler, externalIds, logger ->
505+
def resolvedComponentIds = new HashSet()
506+
def doResolve = { handler, idsToResolve, String label ->
507+
def sizeBefore = resolvedComponentIds.size()
508+
def result = handler.createArtifactResolutionQuery()
509+
.forComponents(idsToResolve)
510+
.withArtifacts(JvmLibrary, SourcesArtifact)
511+
.execute()
512+
result.resolvedComponents.each { component ->
513+
component.getArtifacts(SourcesArtifact).each { source ->
514+
if (source instanceof ResolvedArtifactResult) {
515+
source.file // access .file to trigger download and register with verification metadata
516+
resolvedComponentIds.add(component.id)
517+
} else {
518+
logger.warn("Could not download sources for ${component.id}: ${source}")
519+
}
520+
}
500521
}
522+
def count = resolvedComponentIds.size() - sizeBefore
523+
logger.lifecycle("Resolved sources for ${count} components from ${label}")
524+
}
525+
doResolve(depHandler, externalIds, "project repositories")
526+
def unresolvedIds = externalIds.findAll { !resolvedComponentIds.contains(it) }
527+
if (!unresolvedIds.isEmpty()) {
528+
doResolve(buildscriptDepHandler, unresolvedIds, "buildscript repositories")
529+
}
530+
return resolvedComponentIds
531+
}
501532

502-
if (unresolvedConfigs > 0) {
503-
logger.warn("${unresolvedConfigs} configurations could not be resolved (run with --info for details)")
533+
task resolveSourceArtifacts {
534+
group = 'verification'
535+
description = 'Resolves source artifacts for all configurations so they can be included in dependency verification metadata'
536+
doLast {
537+
def externalIds = collectExternalComponentIds(allprojects, logger)
538+
logger.lifecycle("Resolving sources for ${externalIds.size()} external dependencies...")
539+
540+
def resolved = resolveSourceComponents(dependencies, buildscript.dependencies, externalIds, logger)
541+
542+
def finalUnresolved = externalIds.size() - resolved.size()
543+
if (finalUnresolved > 0) {
544+
logger.warn("${finalUnresolved} dependencies have no published source artifacts")
504545
}
546+
logger.lifecycle("Total: sources resolved for ${resolved.size()}/${externalIds.size()} components")
547+
}
548+
}
505549

506-
def externalIds = componentIds.findAll { it instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier }
507-
logger.lifecycle("Resolving sources for ${externalIds.size()} external dependencies...")
550+
task checkSourceVerification {
551+
group = 'verification'
552+
description = 'Checks that all resolvable source artifacts have checksums in verification-metadata.xml'
553+
doLast {
554+
def verificationFile = file("gradle/verification-metadata.xml")
555+
if (!verificationFile.exists()) {
556+
throw new GradleException("gradle/verification-metadata.xml not found")
557+
}
508558

509-
// Populated by resolveSources across both project and buildscript repository calls
510-
def resolvedComponentIds = new HashSet()
511-
def resolveSources = { depHandler, componentIdsToResolve, String label ->
512-
def sizeBefore = resolvedComponentIds.size()
513-
def result = depHandler.createArtifactResolutionQuery()
514-
.forComponents(componentIdsToResolve)
515-
.withArtifacts(JvmLibrary, SourcesArtifact)
516-
.execute()
517-
result.resolvedComponents.each { component ->
518-
component.getArtifacts(SourcesArtifact).each { source ->
519-
if (source instanceof ResolvedArtifactResult) {
520-
source.file // access .file to trigger download and register with verification metadata
521-
resolvedComponentIds.add(component.id)
522-
} else {
523-
logger.warn("Could not download sources for ${component.id}: ${source}")
524-
}
559+
def xml = new XmlSlurper().parse(verificationFile)
560+
def knownSources = new HashSet()
561+
xml.components.component.each { component ->
562+
def key = "${component.@group}:${component.@name}:${component.@version}"
563+
component.artifact.each { artifact ->
564+
if (artifact.@name.toString().contains('-sources.jar')) {
565+
knownSources.add(key)
525566
}
526567
}
527-
def count = resolvedComponentIds.size() - sizeBefore
528-
logger.lifecycle("Resolved sources for ${count} components from ${label}")
529568
}
530569

531-
resolveSources(dependencies, externalIds, "project repositories")
570+
def externalIds = collectExternalComponentIds(allprojects, logger)
571+
def resolved = resolveSourceComponents(dependencies, buildscript.dependencies, externalIds, logger)
532572

533-
// Retry unresolved sources using buildscript repositories (includes Gradle Plugin Portal)
534-
def unresolvedIds = externalIds.findAll { !resolvedComponentIds.contains(it) }
535-
if (!unresolvedIds.isEmpty()) {
536-
resolveSources(buildscript.dependencies, unresolvedIds, "buildscript repositories")
573+
def missing = []
574+
resolved.each { id ->
575+
def key = "${id.group}:${id.module}:${id.version}"
576+
if (!knownSources.contains(key)) {
577+
missing.add(key)
578+
}
537579
}
538580

539-
def finalUnresolved = externalIds.size() - resolvedComponentIds.size()
540-
if (finalUnresolved > 0) {
541-
logger.warn("${finalUnresolved} dependencies have no published source artifacts")
581+
if (!missing.isEmpty()) {
582+
missing.sort()
583+
logger.error("The following dependencies have published sources but are missing source checksums in verification-metadata.xml:")
584+
missing.each { logger.error(" - ${it}") }
585+
throw new GradleException(
586+
"${missing.size()} source checksum(s) missing from verification-metadata.xml. " +
587+
"Run './gradlew --write-verification-metadata sha256 resolveSourceArtifacts' and commit the updated file."
588+
)
542589
}
543-
logger.lifecycle("Total: sources resolved for ${resolvedComponentIds.size()}/${externalIds.size()} components")
590+
logger.lifecycle("All ${resolved.size()} resolvable source artifacts have checksums in verification-metadata.xml")
544591
}
545592
}
546593

0 commit comments

Comments
 (0)