From 2b1196651b9bc13c6ce0a310d7d7b52ce725648d Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 15 Nov 2025 19:42:24 +0100 Subject: [PATCH 1/9] Test only the changed libraries on relevant JDKs --- .github/workflows/checkstyle-skip.yml | 1 + .github/workflows/checkstyle.yml | 1 + .../workflows/create-scheduled-release.yml | 2 +- .github/workflows/scan-docker-images.yml | 4 +- .github/workflows/test-all-metadata-skip.yml | 17 --- .github/workflows/test-all-metadata.yml | 6 - .../workflows/test-changed-metadata-skip.yml | 6 +- .github/workflows/test-changed-metadata.yml | 8 +- .../org.graalvm.internal.tck-harness.gradle | 125 ++++++++++++------ .../internal/tck/harness/TckExtension.java | 14 -- 10 files changed, 100 insertions(+), 84 deletions(-) delete mode 100644 .github/workflows/test-all-metadata-skip.yml diff --git a/.github/workflows/checkstyle-skip.yml b/.github/workflows/checkstyle-skip.yml index bed200961..365b82c9c 100644 --- a/.github/workflows/checkstyle-skip.yml +++ b/.github/workflows/checkstyle-skip.yml @@ -3,6 +3,7 @@ name: "Check code style" on: pull_request: paths: + - 'docs/*' - '**.md' - 'library-and-framework-list*.json' diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index 5ca5ec034..a299a3e40 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -3,6 +3,7 @@ name: "Check code style" on: pull_request: paths-ignore: + - 'docs/*' - '**.md' - 'library-and-framework-list*.json' diff --git a/.github/workflows/create-scheduled-release.yml b/.github/workflows/create-scheduled-release.yml index 693a62166..7c465d613 100644 --- a/.github/workflows/create-scheduled-release.yml +++ b/.github/workflows/create-scheduled-release.yml @@ -31,7 +31,7 @@ jobs: id: set-matrix run: | LATEST_TAG=$(git tag --list | sort -V | tail -1) - ./gradlew generateMatrixDiffCoordinates -PbaseCommit=$(git show-ref -s $LATEST_TAG) -PnewCommit=$(git rev-parse HEAD) + ./gradlew generateChangedCoordinatesMatrix -PbaseCommit=$(git show-ref -s $LATEST_TAG) -PnewCommit=$(git rev-parse HEAD) release: needs: get-changed-metadata diff --git a/.github/workflows/scan-docker-images.yml b/.github/workflows/scan-docker-images.yml index fbb179262..21a7c11a8 100644 --- a/.github/workflows/scan-docker-images.yml +++ b/.github/workflows/scan-docker-images.yml @@ -1,11 +1,11 @@ name: "Scan docker images from the allowed docker images list" on: - # we should run this job if somebody wants to add/update allowed docker images + # we run this job if somebody wants to add/update allowed docker images pull_request: paths: - 'tests/tck-build-logic/src/main/resources/allowed-docker-images/**' - # we should run this job once a week to check if new vulnerabilities are found in existing images + # we run this job once a week to check if new vulnerabilities are found in existing images schedule: - cron: "0 0 * * 6" diff --git a/.github/workflows/test-all-metadata-skip.yml b/.github/workflows/test-all-metadata-skip.yml deleted file mode 100644 index a9289b2b3..000000000 --- a/.github/workflows/test-all-metadata-skip.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: "Test all metadata" - -on: - push: - branches: - - master - paths: - - '**.md' - - 'library-and-framework-list*.json' - workflow_dispatch: - -jobs: - build: - name: "πŸ§ͺ All metadata tests have passed" - runs-on: ubuntu-latest - steps: - - run: 'echo "No build required"' diff --git a/.github/workflows/test-all-metadata.yml b/.github/workflows/test-all-metadata.yml index 9c4490e11..3c7e003ec 100644 --- a/.github/workflows/test-all-metadata.yml +++ b/.github/workflows/test-all-metadata.yml @@ -1,12 +1,6 @@ name: "Test all metadata" on: - push: - branches: - - master - paths-ignore: - - '**.md' - - 'library-and-framework-list*.json' workflow_dispatch: concurrency: diff --git a/.github/workflows/test-changed-metadata-skip.yml b/.github/workflows/test-changed-metadata-skip.yml index 81bfd24b3..94cedf047 100644 --- a/.github/workflows/test-changed-metadata-skip.yml +++ b/.github/workflows/test-changed-metadata-skip.yml @@ -4,9 +4,9 @@ on: pull_request: branches: - master - paths: - - '**.md' - - 'library-and-framework-list*.json' + paths-ignore: + - 'metadata/*' + - 'tests/*' jobs: get-changed-metadata: diff --git a/.github/workflows/test-changed-metadata.yml b/.github/workflows/test-changed-metadata.yml index d29dceebb..b8d5e4b94 100644 --- a/.github/workflows/test-changed-metadata.yml +++ b/.github/workflows/test-changed-metadata.yml @@ -4,9 +4,9 @@ on: pull_request: branches: - master - paths-ignore: - - '**.md' - - 'library-and-framework-list*.json' + paths: + - 'metadata/*' + - 'tests/*' concurrency: group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" @@ -35,7 +35,7 @@ jobs: - name: "πŸ•ΈοΈ Populate matrix" id: set-matrix run: | - ./gradlew generateMatrixDiffCoordinates -PbaseCommit=${{ github.event.pull_request.base.sha }} -PnewCommit=${{ github.event.pull_request.head.sha }} + ./gradlew generateChangedCoordinatesMatrix -PbaseCommit=${{ github.event.pull_request.base.sha }} -PnewCommit=${{ github.event.pull_request.head.sha }} test-changed-metadata: name: "πŸ§ͺ ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})" diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index 63b6e3ca7..6edc8d8e6 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -10,7 +10,7 @@ plugins { } import groovy.json.JsonOutput -import org.graalvm.internal.tck.ContributionTask +import org.graalvm.internal.tck. ContributionTask import org.graalvm.internal.tck.DockerTask import org.graalvm.internal.tck.ConfigFilesChecker import org.graalvm.internal.tck.ScaffoldTask @@ -24,6 +24,7 @@ import org.graalvm.internal.tck.harness.tasks.JavaTestInvocationTask import org.graalvm.internal.tck.harness.tasks.NativeTestCompileInvocationTask import org.graalvm.internal.tck.updaters.FetchExistingLibrariesWithNewerVersionsTask import org.graalvm.internal.tck.updaters.GroupUnsupportedLibraries +import org.gradle.util.internal.VersionNumber import static org.graalvm.internal.tck.Utils.generateTaskName @@ -31,6 +32,15 @@ import static org.graalvm.internal.tck.Utils.generateTaskName logger.lifecycle("GraalVM Reachability Metadata TCK") logger.lifecycle("---------------------------------") +def writeGithubOutput(String key, String value) { + def path = System.getenv("GITHUB_OUTPUT") + if (path == null || path.trim().isEmpty()) { + println "${key}=${value}" + } else { + new File(path).append("${key}=${value}\n") + } +} + String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") List matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter) @@ -132,7 +142,6 @@ Provider diff = tasks.register("diff", DefaultTask) { task -> } List diffCoordinates = new ArrayList<>() -boolean testAll = false if (project.hasProperty("baseCommit")) { String baseCommit = project.findProperty("baseCommit") String newCommit = Objects.requireNonNullElse(project.findProperty("newCommit"), "HEAD") @@ -146,8 +155,6 @@ if (project.hasProperty("baseCommit")) { dependsOn(taskTaskName) } } - - testAll = tck.shouldTestAll() } def matrixDefault = [ @@ -155,7 +162,7 @@ def matrixDefault = [ "17", "latest-ea" ], - "os" : ["ubuntu-latest"] // TODO: Add support for "windows-latest", "macos-latest" + "os" : ["ubuntu-latest"] ] final String METADATA_GROUP = "Metadata" @@ -169,12 +176,12 @@ Provider generateMatrixMatchingCoordinates = tasks.register("generateMatri "coordinates": matchingCoordinates ] matrix.putAll(matrixDefault) - new File(System.getenv("GITHUB_OUTPUT")).append("matrix=${JsonOutput.toJson(matrix)}\n") + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) } } -// gradle generateMatrixDiffCoordinates -PbaseCommit= -PnewCommit= -Provider generateMatrixDiffCoordinates = tasks.register("generateMatrixDiffCoordinates", DefaultTask) { task -> +// gradle generateChangedCoordinatesMatrix -PbaseCommit= -PnewCommit= +Provider generateChangedCoordinatesMatrix = tasks.register("generateChangedCoordinatesMatrix", DefaultTask) { task -> task.setDescription("Returns matrix definition populated with coordinates of changed libraries") task.setGroup(METADATA_GROUP) task.doFirst { @@ -182,40 +189,84 @@ Provider generateMatrixDiffCoordinates = tasks.register("generateMatrixDif throw new GradleException("Missing 'baseCommit' property! Rerun Gradle with '-PbaseCommit='") } - if (diffCoordinates.isEmpty()) { - def matrix = [ - "coordinates": ["No matches found!"] - ] - - matrix.putAll(matrixDefault) + boolean noneFound = diffCoordinates.isEmpty() + def matrix = [ + "coordinates": noneFound ? [] : diffCoordinates + ] + matrix.putAll(matrixDefault) + if (noneFound) { println "No changed coordinates were found!" - new File(System.getenv("GITHUB_OUTPUT")).append("matrix=${JsonOutput.toJson(matrix)}\n") - new File(System.getenv("GITHUB_OUTPUT")).append("none-found=true\n") - } else { - def matrix = [ - "coordinates": diffCoordinates + } + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) + writeGithubOutput("none-found", noneFound.toString()) + } +} + +// gradle generateInfrastructureChangedCoordinatesMatrix -PbaseCommit= -PnewCommit= +Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register("generateInfrastructureChangedCoordinatesMatrix", DefaultTask) { task -> + task.setDescription("Returns matrix definition populated with pre-selected coordinates when test infrastructure has changed") + task.setGroup(METADATA_GROUP) + task.doFirst { + if (!project.hasProperty("baseCommit")) { + throw new GradleException("Missing 'baseCommit' property! Rerun Gradle with '-PbaseCommit='") + } + String baseCommit = project.findProperty("baseCommit") + String newCommit = Objects.requireNonNullElse(project.findProperty("newCommit"), "HEAD") + + // Detect changes in test infrastructure (build logic and workflows) + def baos = new ByteArrayOutputStream() + project.exec { + standardOutput = baos + commandLine 'git', 'diff', '--name-only', '--diff-filter=ACMRT', baseCommit, newCommit + } + List changedFiles = baos.toString("UTF-8").split("\\r?\\n") as List + boolean infraChanged = changedFiles.any { it.startsWith('tests/tck-build-logic') || it.startsWith('.github/workflows') } + + List selected = [] + if (infraChanged) { + // Pre-selected modules; keep stable for infra verification + List preselectedModules = [ + 'org.bouncycastle:bcpkix-jdk15on', + 'org.bouncycastle:bcpkix-jdk18on', + 'io.netty:netty-common', + 'io.grpc:grpc-core', + 'org.postgresql:postgresql', + 'org.apache.kafka:kafka-clients', + 'com.zaxxer:HikariCP', + 'org.hibernate.validator:hibernate-validator', + 'io.undertow:undertow-core', + 'com.hazelcast:hazelcast' ] - matrix.putAll(matrixDefault) - - /** - * if we changed some files from tck-build-logic or github workflows, we must run all tests - * to check if we accidentally broke something. In this case, we will have a lots of tests, so we can't - * run them with all possible JDK versions, because we will hit the following error: - * Strategy expansion exceeded 256 results for job 'test-changed-metadata' - */ - if (!testAll) { - /** - * when we are introducing a new metadata, we should test it against all versions, - * not just the oldest and the newest one (which we are testing by default) - */ - matrix.version.add("21") - matrix.version.add("25") - matrix.version.add("latest-ea") + preselectedModules.each { module -> + List matches = tck.getMatchingCoordinates(module) + if (!matches.isEmpty()) { + def withVersions = matches.collect { c -> + def v = c.substring(c.lastIndexOf(':') + 1) + [coord: c, ver: v] + } + def latest = withVersions.max { a, b -> VersionNumber.parse(a.ver) <=> VersionNumber.parse(b.ver) } + selected.add(latest.coord) + } else { + logger.lifecycle("No tested versions found for ${module}, skipping") + } + } + } + + boolean noneFound = !infraChanged || selected.isEmpty() + def matrix = [ + "coordinates": noneFound ? [] : selected + ] + matrix.putAll(matrixDefault) + if (noneFound) { + if (!infraChanged) { + println "No test infrastructure changes detected." + } else { + println "Test infrastructure changed, but no pre-selected coordinates could be resolved." } - new File(System.getenv("GITHUB_OUTPUT")).append("matrix=${JsonOutput.toJson(matrix)}\n") - new File(System.getenv("GITHUB_OUTPUT")).append("none-found=false\n") } + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) + writeGithubOutput("none-found", noneFound.toString()) } } diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index eb31d97c4..8937516cb 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java @@ -119,12 +119,6 @@ Path getTestDir(String coordinates) { throw new RuntimeException("Missing test-directory for coordinates `" + coordinates + "`"); } - private boolean shouldTestAll = false; - - public boolean shouldTestAll() { - return shouldTestAll; - } - /** * Returns a list of coordinates that match changed files between baseCommit and newCommit. * @@ -142,13 +136,11 @@ List diffCoordinates(String baseCommit, String newCommit) { List diffFiles = Arrays.asList(output.split("\\r?\\n")); Path workflowsRoot = repoRoot().resolve(".github").resolve("workflows"); - AtomicBoolean testAll = new AtomicBoolean(false); // Group files by if they belong to 'metadata' or 'test' directory structures. Map> changed = diffFiles.stream() .map(line -> repoRoot().resolve(line)) .collect(Collectors.groupingBy((Path path) -> { if (path.startsWith(tckRoot()) || path.startsWith(workflowsRoot)) { - testAll.set(true); return "logic"; } else if (path.startsWith(testRoot())) { return "test"; @@ -159,12 +151,6 @@ List diffCoordinates(String baseCommit, String newCommit) { } })); - if (testAll.get()) { - shouldTestAll = true; - // If tck was changed we should retest everything, just to be safe. - return getMatchingCoordinates(""); - } - // if we didn't change any of metadata, tests or logic we don't need to test anything if (changed.get("metadata") != null && changed.get("metadata").isEmpty() && changed.get("test") != null && changed.get("test").isEmpty() From ded35092dbb774db75171b4c8569fc259d3c005f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 15 Nov 2025 20:06:58 +0100 Subject: [PATCH 2/9] Add infrastructure-changed matrix --- .../org.graalvm.internal.tck-harness.gradle | 67 +++++++------------ 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index 6edc8d8e6..644c96024 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -10,7 +10,7 @@ plugins { } import groovy.json.JsonOutput -import org.graalvm.internal.tck. ContributionTask +import org.graalvm.internal.tck.ContributionTask import org.graalvm.internal.tck.DockerTask import org.graalvm.internal.tck.ConfigFilesChecker import org.graalvm.internal.tck.ScaffoldTask @@ -197,6 +197,10 @@ Provider generateChangedCoordinatesMatrix = tasks.register("generateChange if (noneFound) { println "No changed coordinates were found!" } + + matrix.version.add("21") + matrix.version.add("25") + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) writeGithubOutput("none-found", noneFound.toString()) } @@ -207,25 +211,10 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" task.setDescription("Returns matrix definition populated with pre-selected coordinates when test infrastructure has changed") task.setGroup(METADATA_GROUP) task.doFirst { - if (!project.hasProperty("baseCommit")) { - throw new GradleException("Missing 'baseCommit' property! Rerun Gradle with '-PbaseCommit='") - } - String baseCommit = project.findProperty("baseCommit") - String newCommit = Objects.requireNonNullElse(project.findProperty("newCommit"), "HEAD") - - // Detect changes in test infrastructure (build logic and workflows) - def baos = new ByteArrayOutputStream() - project.exec { - standardOutput = baos - commandLine 'git', 'diff', '--name-only', '--diff-filter=ACMRT', baseCommit, newCommit - } - List changedFiles = baos.toString("UTF-8").split("\\r?\\n") as List - boolean infraChanged = changedFiles.any { it.startsWith('tests/tck-build-logic') || it.startsWith('.github/workflows') } - List selected = [] - if (infraChanged) { - // Pre-selected modules; keep stable for infra verification - List preselectedModules = [ + boolean infraChanged = true + // Pre-selected modules; keep stable for infra verification + List preselectedModules = [ 'org.bouncycastle:bcpkix-jdk15on', 'org.bouncycastle:bcpkix-jdk18on', 'io.netty:netty-common', @@ -236,37 +225,31 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" 'org.hibernate.validator:hibernate-validator', 'io.undertow:undertow-core', 'com.hazelcast:hazelcast' - ] - - preselectedModules.each { module -> - List matches = tck.getMatchingCoordinates(module) - if (!matches.isEmpty()) { - def withVersions = matches.collect { c -> - def v = c.substring(c.lastIndexOf(':') + 1) - [coord: c, ver: v] - } - def latest = withVersions.max { a, b -> VersionNumber.parse(a.ver) <=> VersionNumber.parse(b.ver) } - selected.add(latest.coord) - } else { - logger.lifecycle("No tested versions found for ${module}, skipping") + ] + preselectedModules.each { module -> + List matches = tck.getMatchingCoordinates(module) + if (!matches.isEmpty()) { + def withVersions = matches.collect { c -> + def v = c.substring(c.lastIndexOf(':') + 1) + [coord: c, ver: v] } + def latest = withVersions.max { a, b -> VersionNumber.parse(a.ver) <=> VersionNumber.parse(b.ver) } + selected.add(latest.coord) + } else { + throw new GradleException("No tested versions found for ${module}, skipping") } } - boolean noneFound = !infraChanged || selected.isEmpty() def matrix = [ - "coordinates": noneFound ? [] : selected + "coordinates": selected ] matrix.putAll(matrixDefault) - if (noneFound) { - if (!infraChanged) { - println "No test infrastructure changes detected." - } else { - println "Test infrastructure changed, but no pre-selected coordinates could be resolved." - } - } + + matrix.version.add("21") + matrix.version.add("25") + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) - writeGithubOutput("none-found", noneFound.toString()) + writeGithubOutput("none-found", "false") } } From d3a07fa3780e1408d27bfffdb7791637f35195e6 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 15 Nov 2025 22:09:52 +0100 Subject: [PATCH 3/9] Add separate tests for changed infrastructure --- .github/CODEOWNERS | 3 +- .../test-changed-infrastructure-skip.yml | 25 +++++ .../workflows/test-changed-infrastructure.yml | 101 ++++++++++++++++++ .../workflows/test-changed-metadata-skip.yml | 2 +- .github/workflows/test-changed-metadata.yml | 2 +- 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/test-changed-infrastructure-skip.yml create mode 100644 .github/workflows/test-changed-infrastructure.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ca70f2617..d3c37ebb6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,6 @@ metadata/* @oracle/graalvm-reachability-maintainer -tests/* @oracle/graalvm-reachability-maintainer +tests/src/* @oracle/graalvm-reachability-maintainer +tests/tck-build-logic/* @vjovanov tests/tck-build-logic/src/main/resources/allowed-docker-images/* @matneu library-and-framework-list.json @fniephaus .github/* @vjovanov diff --git a/.github/workflows/test-changed-infrastructure-skip.yml b/.github/workflows/test-changed-infrastructure-skip.yml new file mode 100644 index 000000000..e85f2aa23 --- /dev/null +++ b/.github/workflows/test-changed-infrastructure-skip.yml @@ -0,0 +1,25 @@ +name: "Test changed build logic" + +on: + pull_request: + branches: + - master + paths-ignore: + - "tests/tck-build-logic/**" + - "gradle/**" + - "build.gradle" + - "settings.gradle" + - "gradle.properties" + +jobs: + get-changed-infrastructure: + name: "πŸ“‹ Get a list of libraries to test for build-logic changes" + runs-on: ubuntu-latest + steps: + - run: 'echo "No build required"' + + all-infrastructure-passed: + name: "πŸ§ͺ All build-logic triggered tests have passed" + runs-on: ubuntu-latest + steps: + - run: 'echo "No build required"' diff --git a/.github/workflows/test-changed-infrastructure.yml b/.github/workflows/test-changed-infrastructure.yml new file mode 100644 index 000000000..539161917 --- /dev/null +++ b/.github/workflows/test-changed-infrastructure.yml @@ -0,0 +1,101 @@ +name: "Test changed build logic" + +on: + pull_request: + branches: + - master + paths: + - "tests/tck-build-logic/**" + - "gradle/**" + - "build.gradle" + - "settings.gradle" + - "gradle.properties" + +concurrency: + group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" + cancel-in-progress: true + +jobs: + get-changed-infrastructure: + if: github.repository == 'oracle/graalvm-reachability-metadata' + name: "πŸ“‹ Get a list of libraries to test for build-logic changes" + runs-on: "ubuntu-22.04" + timeout-minutes: 5 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + none-found: ${{ steps.set-matrix.outputs.none-found }} + steps: + - name: "☁️ Checkout repository" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "πŸ”§ Prepare environment" + uses: graalvm/setup-graalvm@v1 + with: + java-version: "17" + distribution: "graalvm" + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "πŸ•ΈοΈ Populate matrix" + id: set-matrix + run: | + ./gradlew generateInfrastructureChangedCoordinatesMatrix -PbaseCommit=${{ github.event.pull_request.base.sha }} -PnewCommit=${{ github.event.pull_request.head.sha }} + + test-changed-infrastructure: + name: "πŸ§ͺ ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})" + if: needs.get-changed-infrastructure.result == 'success' && needs.get-changed-infrastructure.outputs.none-found != 'true' && github.repository == 'oracle/graalvm-reachability-metadata' + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + needs: get-changed-infrastructure + + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.get-changed-infrastructure.outputs.matrix) }} + + steps: + - name: "☁️ Checkout repository" + uses: actions/checkout@v4 + + - name: "πŸ”§ Setup java" + uses: actions/setup-java@v4 + with: + distribution: "oracle" + java-version: "17" + + - name: "πŸ”§ Prepare environment" + uses: graalvm/setup-graalvm@v1 + with: + set-java-home: "false" + java-version: ${{ matrix.version }} + distribution: "graalvm" + github-token: ${{ secrets.GITHUB_TOKEN }} + native-image-job-reports: "true" + + - name: "Pull allowed docker images" + run: | + ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + + - name: "Disable docker networking" + run: bash ./.github/workflows/disable-docker.sh + + - name: "πŸ”Ž Check metadata config files content" + run: | + ./gradlew checkConfigFiles --coordinates=${{ matrix.coordinates }} + + - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" + run: | + ./gradlew test -Pcoordinates=${{ matrix.coordinates }} + + all-infrastructure-passed: + name: "πŸ§ͺ All build-logic triggered tests have passed" + runs-on: "ubuntu-22.04" + timeout-minutes: 1 + if: ${{ always() }} && github.repository == 'oracle/graalvm-reachability-metadata' + needs: test-changed-infrastructure + steps: + - name: "All tests passed" + if: needs.test-changed-infrastructure.result == 'success' + run: exit 0 + + - name: "Some tests failed" + if: needs.test-changed-infrastructure.result == 'failure' + run: exit 1 diff --git a/.github/workflows/test-changed-metadata-skip.yml b/.github/workflows/test-changed-metadata-skip.yml index 94cedf047..82d07a247 100644 --- a/.github/workflows/test-changed-metadata-skip.yml +++ b/.github/workflows/test-changed-metadata-skip.yml @@ -6,7 +6,7 @@ on: - master paths-ignore: - 'metadata/*' - - 'tests/*' + - 'tests/src/*' jobs: get-changed-metadata: diff --git a/.github/workflows/test-changed-metadata.yml b/.github/workflows/test-changed-metadata.yml index b8d5e4b94..31c93eee5 100644 --- a/.github/workflows/test-changed-metadata.yml +++ b/.github/workflows/test-changed-metadata.yml @@ -6,7 +6,7 @@ on: - master paths: - 'metadata/*' - - 'tests/*' + - 'tests/src/*' concurrency: group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" From 59c47656259bbc479d898cf886e629236b344269 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 15 Nov 2025 22:21:33 +0100 Subject: [PATCH 4/9] Move the pull request template to .github --- pull_request_template.md => .github/pull_request_template.md | 0 CONTRIBUTING.md | 2 +- REVIEWING.md | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename pull_request_template.md => .github/pull_request_template.md (100%) diff --git a/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from pull_request_template.md rename to .github/pull_request_template.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b01c8db88..128c671d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ not be included as it does not compose and can break code in unpredictable ways. * Make sure that you are using [Conditional Configuration](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json) in order to precisely define the metadata scope. This is a hard requirement as it prevents unnecessary bloating of images. -* Once you want to create a pull request, you will be asked to fill out the [following list](./pull_request_template.md). +* Once you want to create a pull request, you will be asked to fill out the [following list](.github/pull_request_template.md). ℹ️ To learn more about collecting metadata, see [How To Collect Metadata](docs/CollectingMetadata.md). diff --git a/REVIEWING.md b/REVIEWING.md index e8156c64a..194d824d9 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -6,7 +6,7 @@ This document should serve as a guideline for reviewers to simplify and harmoniz ## Checklist First step of every review is to verify the checklist from the pull request description. -Once the contributor wants to open a pull request [this checklist](pull_request_template.md) will be automatically added to the pull request description. +Once the contributor wants to open a pull request [this checklist](.github/pull_request_template.md) will be automatically added to the pull request description. * If the PR does not contain such a list, it **should not be reviewed** * If any of the items is not checked, the reviewer should ask for an explanation from the contributor @@ -61,4 +61,4 @@ All metadata files **must** be covered by the tests. Every metadata file should: There are various tools that could help checking the content of all json files we are collecting. To run these checks automatically, top-level metadata index file must contain `allowed-packages` properly set ([see this](./CONTRIBUTING.md#metadata-structure)). -If this field is properly configured, our GitHub workflows will automatically check `typeReachable` and origin of all entries in all config files. \ No newline at end of file +If this field is properly configured, our GitHub workflows will automatically check `typeReachable` and origin of all entries in all config files. From 06761484d3c92860fb39af473fc78a0e307fd9a5 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 16 Nov 2025 11:04:19 +0100 Subject: [PATCH 5/9] Add ci.json for matrix specs --- ci.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ci.json diff --git a/ci.json b/ci.json new file mode 100644 index 000000000..d941c44df --- /dev/null +++ b/ci.json @@ -0,0 +1,14 @@ +{ + "generateMatrixMatchingCoordinates": { + "java": ["17", "latest-ea"], + "os": ["ubuntu-latest"] + }, + "generateChangedCoordinatesMatrix": { + "java": ["17", "21", "25", "latest-ea"], + "os": ["ubuntu-latest"] + }, + "generateInfrastructureChangedCoordinatesMatrix": { + "java": ["17", "21", "25", "latest-ea"], + "os": ["ubuntu-latest"] + } +} From d1b88d029050af8ad9ddba228c1c2969fc22cc48 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 16 Nov 2025 10:58:44 +0100 Subject: [PATCH 6/9] Test all in batches --- .github/workflows/test-all-metadata.yml | 6 +- ci.json | 8 +- .../org.graalvm.internal.tck-harness.gradle | 179 ++++++++++++++++-- .../internal/tck/PullImagesFromFileTask.java | 53 ++++++ 4 files changed, 224 insertions(+), 22 deletions(-) create mode 100644 tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java diff --git a/.github/workflows/test-all-metadata.yml b/.github/workflows/test-all-metadata.yml index 3c7e003ec..3c0fdf61e 100644 --- a/.github/workflows/test-all-metadata.yml +++ b/.github/workflows/test-all-metadata.yml @@ -27,13 +27,13 @@ jobs: - name: "πŸ•ΈοΈ Populate matrix" id: set-matrix run: | - ./gradlew generateMatrixMatchingCoordinates -Pcoordinates=all + ./gradlew generateMatrixBatchedCoordinates -Pbatches=16 test-all-metadata: if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ§ͺ ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})" runs-on: ${{ matrix.os }} - timeout-minutes: 20 + timeout-minutes: 120 needs: get-all-metadata strategy: fail-fast: false @@ -56,7 +56,7 @@ jobs: native-image-job-reports: 'true' - name: "Pull allowed docker images" run: | - ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + ./gradlew pullAllowedDockerImagesMatching -Pcoordinates=${{ matrix.coordinates }} - name: "Disable docker networking" run: bash ./.github/workflows/disable-docker.sh - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" diff --git a/ci.json b/ci.json index d941c44df..6c088ad84 100644 --- a/ci.json +++ b/ci.json @@ -1,6 +1,6 @@ { - "generateMatrixMatchingCoordinates": { - "java": ["17", "latest-ea"], + "generateMatrixBatchedCoordinates": { + "java": ["17", "21", "25", "latest-ea"], "os": ["ubuntu-latest"] }, "generateChangedCoordinatesMatrix": { @@ -10,5 +10,9 @@ "generateInfrastructureChangedCoordinatesMatrix": { "java": ["17", "21", "25", "latest-ea"], "os": ["ubuntu-latest"] + }, + "generateMatrixMatchingCoordinates": { + "java": ["17", "latest-ea"], + "os": ["ubuntu-latest"] } } diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index 644c96024..a838e5a46 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -10,9 +10,12 @@ plugins { } import groovy.json.JsonOutput +import groovy.json.JsonSlurper import org.graalvm.internal.tck.ContributionTask import org.graalvm.internal.tck.DockerTask import org.graalvm.internal.tck.ConfigFilesChecker +import org.graalvm.internal.tck.DockerUtils +import org.graalvm.internal.tck.PullImagesFromFileTask import org.graalvm.internal.tck.ScaffoldTask import org.graalvm.internal.tck.GrypeTask import org.graalvm.internal.tck.TestedVersionUpdaterTask @@ -42,7 +45,47 @@ def writeGithubOutput(String key, String value) { } String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") -List matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter) + +// Support fractional batching coordinates in the form "k/n" (e.g., "1/16") +boolean isFractionalBatch(String s) { + return s != null && (s ==~ /\d+\/\d+/) +} + +List parseFraction(String s) { + def m = s =~ /(\d+)\/(\d+)/ + if (!m.matches()) return null + int k = (m[0][1] as int) + int n = (m[0][2] as int) + return [k, n] +} + +List computeBatchedCoordinates(List allCoords, int k, int n) { + if (n <= 0) throw new GradleException("Invalid batches denominator: ${n}") + if (k < 1 || k > n) throw new GradleException("Invalid batch index: ${k}/${n}") + def sorted = new ArrayList(allCoords) + java.util.Collections.sort(sorted) + int target = (k - 1) + def result = [] + int i = 0 + for (String c : sorted) { + if ((i % n) == target) { + result.add(c) + } + i++ + } + return result +} + +List matchingCoordinates +if (isFractionalBatch(coordinateFilter)) { + def frac = parseFraction(coordinateFilter) + int k = frac[0] + int n = frac[1] + List all = tck.getMatchingCoordinates("all") + matchingCoordinates = computeBatchedCoordinates(all, k, n) +} else { + matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter) +} // gradle test -Pcoordinates= Provider test = tasks.register("test", DefaultTask) { task -> @@ -78,6 +121,79 @@ tasks.named("check").configure { dependsOn(checkstyle) } +final String METADATA_GROUP = "Metadata" + +Provider pullAllowedDockerImagesMatching = tasks.register("pullAllowedDockerImagesMatching", DefaultTask) { task -> + task.setDescription("Pull allowed docker images for all matching coordinates") + task.setGroup(METADATA_GROUP) + task.doFirst { + if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { + throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") + } + println "Pulling allowed docker images for ${matchingCoordinates.size()} coordinate(s):" + matchingCoordinates.each { c -> println(" - ${c}") } + } +} + +// Collect unique required images across matching coordinates and filter by allowed list +tasks.register("collectRequiredAllowedDockerImagesMatching", DefaultTask) { task -> + task.setDescription("Collect unique allowed docker images required by matching coordinates into a file") + task.setGroup(METADATA_GROUP) + File out = new File(buildDir, "required-docker-images-union.txt") + // Declare the output so Gradle can track it + task.outputs.file(out) + task.doFirst { + if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { + throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") + } + Set unionRequired = new LinkedHashSet<>() + matchingCoordinates.each { c -> + def parts = c.split(":") + if (parts.length < 3) { + logger.warn("Skipping invalid coordinates: ${c}") + return + } + def group = parts[0] + def artifact = parts[1] + def version = parts[2] + File f = project.file("tests/src/${group}/${artifact}/${version}/required-docker-images.txt") + if (f.exists()) { + f.readLines() + .collect { it?.trim() } + .findAll { it && !it.startsWith("#") } + .each { unionRequired.add(it) } + } + } + + Set allowed = DockerUtils.getAllAllowedImages() + def notAllowed = unionRequired.findAll { !allowed.contains(it) } + if (!notAllowed.isEmpty()) { + throw new GradleException("The following images are not in the allowed list: ${notAllowed}. " + + "If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images " + + "per CONTRIBUTING.md, or adjust required-docker-images.txt files.") + } + + out.parentFile.mkdirs() + def finalList = unionRequired.findAll { allowed.contains(it) }.toList() + out.text = finalList.join(System.lineSeparator()) + println "Collected ${finalList.size()} required allowed image(s):" + finalList.each { println(" - ${it}") } + } +} + +// Pull the collected unique images (once) +tasks.register("pullRequiredAllowedDockerImagesMatching", PullImagesFromFileTask.class) { task -> + task.setDescription("Pull unique allowed docker images required by matching coordinates") + task.setGroup(METADATA_GROUP) + task.dependsOn("collectRequiredAllowedDockerImagesMatching") + task.imagesFile.set(new File(buildDir, "required-docker-images-union.txt")) +} + +// Rewire the aggregate to depend on the unique pull task +pullAllowedDockerImagesMatching.configure { + dependsOn("pullRequiredAllowedDockerImagesMatching") +} + // Here we want to configure all test and checkstyle tasks for all filtered subprojects for (String coordinates in matchingCoordinates) { String testTaskName = generateTaskName("test", coordinates) @@ -127,6 +243,13 @@ for (String coordinates in matchingCoordinates) { javaTest.configure { dependsOn(javaTestTaskName) } + + String pullDockerTaskName = generateTaskName("pullAllowedDockerImages", coordinates) + if ((!tasks.getNames().contains(pullDockerTaskName))) { + tasks.register(pullDockerTaskName, DockerTask.class) { t -> + t.setCoordinates(coordinates) + } + } } // gradle diff -PbaseCommit= -PnewCommit= @@ -157,15 +280,23 @@ if (project.hasProperty("baseCommit")) { } } -def matrixDefault = [ - "version": [ - "17", - "latest-ea" - ], - "os" : ["ubuntu-latest"] -] +Map loadCi() { + File f = project.file("ci.json") + if (!f.exists()) { + throw new GradleException("Missing ci.json in repo root") + } + new JsonSlurper().parse(f) as Map +} -final String METADATA_GROUP = "Metadata" +Map> matrixDefaultsFor(String section) { + def ci = loadCi() + def sec = ci[section] + if (sec == null) throw new GradleException("Missing '${section}' in ci.json") + [ + "version": (sec["java"] as List), + "os": (sec["os"] as List) + ] +} // gradle generateMatrixMatchingCoordinates -Pcoordinates= Provider generateMatrixMatchingCoordinates = tasks.register("generateMatrixMatchingCoordinates", DefaultTask) { task -> @@ -175,7 +306,25 @@ Provider generateMatrixMatchingCoordinates = tasks.register("generateMatri def matrix = [ "coordinates": matchingCoordinates ] - matrix.putAll(matrixDefault) + matrix.putAll(matrixDefaultsFor("generateMatrixMatchingCoordinates")) + writeGithubOutput("matrix", JsonOutput.toJson(matrix)) + } +} + + // gradle generateMatrixBatchedCoordinates [-Pbatches=] +Provider generateMatrixBatchedCoordinates = tasks.register("generateMatrixBatchedCoordinates", DefaultTask) { task -> + task.setDescription("Returns matrix definition populated with fractional batch coordinates (k/n)") + task.setGroup(METADATA_GROUP) + task.doFirst { + int batches = project.hasProperty("batches") ? Integer.parseInt(project.findProperty("batches").toString()) : 16 + if (batches <= 0) { + throw new GradleException("Invalid 'batches' value: ${batches}") + } + List coords = (1..batches).collect { i -> "${i}/${batches}" } + def matrix = [ + "coordinates": coords + ] + matrix.putAll(matrixDefaultsFor("generateMatrixBatchedCoordinates")) writeGithubOutput("matrix", JsonOutput.toJson(matrix)) } } @@ -193,13 +342,11 @@ Provider generateChangedCoordinatesMatrix = tasks.register("generateChange def matrix = [ "coordinates": noneFound ? [] : diffCoordinates ] - matrix.putAll(matrixDefault) + matrix.putAll(matrixDefaultsFor("generateChangedCoordinatesMatrix")) if (noneFound) { println "No changed coordinates were found!" } - matrix.version.add("21") - matrix.version.add("25") writeGithubOutput("matrix", JsonOutput.toJson(matrix)) writeGithubOutput("none-found", noneFound.toString()) @@ -215,7 +362,7 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" boolean infraChanged = true // Pre-selected modules; keep stable for infra verification List preselectedModules = [ - 'org.bouncycastle:bcpkix-jdk15on', + 'ch.qos.logback:logback-classic', 'org.bouncycastle:bcpkix-jdk18on', 'io.netty:netty-common', 'io.grpc:grpc-core', @@ -243,10 +390,8 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" def matrix = [ "coordinates": selected ] - matrix.putAll(matrixDefault) + matrix.putAll(matrixDefaultsFor("generateInfrastructureChangedCoordinatesMatrix")) - matrix.version.add("21") - matrix.version.add("25") writeGithubOutput("matrix", JsonOutput.toJson(matrix)) writeGithubOutput("none-found", "false") diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java new file mode 100644 index 000000000..7b2d84751 --- /dev/null +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java @@ -0,0 +1,53 @@ +package org.graalvm.internal.tck; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecOperations; +import org.gradle.api.GradleException; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +/** + * Pulls docker images listed in a text file (one image per line). + * Fails the task immediately if any image pull fails to ensure batch execution fails on single error. + */ +public abstract class PullImagesFromFileTask extends DefaultTask { + + @InputFile + public abstract RegularFileProperty getImagesFile(); + + @Inject + protected abstract ExecOperations getExecOperations(); + + @TaskAction + public void run() throws IOException { + File inFile = getImagesFile().get().getAsFile(); + if (!inFile.exists()) { + getLogger().lifecycle("No required docker images collected: {}", inFile.getAbsolutePath()); + return; + } + + List images = Files.readAllLines(inFile.toPath()).stream() + .map(String::trim) + .filter(s -> !s.isEmpty() && !s.startsWith("#")) + .toList(); + + for (String image : images) { + getLogger().lifecycle("Pulling image {}...", image); + try { + getExecOperations().exec(spec -> { + spec.setExecutable("docker"); + spec.args("pull", image); + }); + } catch (Exception e) { + throw new GradleException("Failed to pull image " + image + ": " + e.getMessage(), e); + } + } + } +} From f467d3733bfe4ce8dc5dc76622941eb6808f6e3d Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 17 Nov 2025 13:28:07 +0100 Subject: [PATCH 7/9] Add build arguments to ci.json --- .github/workflows/test-all-metadata.yml | 7 +- ci.json | 1 + .../org.graalvm.internal.tck-harness.gradle | 144 ++++++++---------- .../groovy/org.graalvm.internal.tck.gradle | 25 ++- 4 files changed, 91 insertions(+), 86 deletions(-) diff --git a/.github/workflows/test-all-metadata.yml b/.github/workflows/test-all-metadata.yml index 3c0fdf61e..2b1c2eda5 100644 --- a/.github/workflows/test-all-metadata.yml +++ b/.github/workflows/test-all-metadata.yml @@ -2,6 +2,11 @@ name: "Test all metadata" on: workflow_dispatch: + pull_request: + branches: + - master + paths: + - 'ci.json' concurrency: group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" @@ -56,7 +61,7 @@ jobs: native-image-job-reports: 'true' - name: "Pull allowed docker images" run: | - ./gradlew pullAllowedDockerImagesMatching -Pcoordinates=${{ matrix.coordinates }} + ./gradlew pullAllowedDockerImages -Pcoordinates=${{ matrix.coordinates }} - name: "Disable docker networking" run: bash ./.github/workflows/disable-docker.sh - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" diff --git a/ci.json b/ci.json index 6c088ad84..9e50fe296 100644 --- a/ci.json +++ b/ci.json @@ -1,4 +1,5 @@ { + "buildArgs": ["-verbose", "-Ob"], "generateMatrixBatchedCoordinates": { "java": ["17", "21", "25", "latest-ea"], "os": ["ubuntu-latest"] diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index a838e5a46..d6485e38a 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -15,7 +15,6 @@ import org.graalvm.internal.tck.ContributionTask import org.graalvm.internal.tck.DockerTask import org.graalvm.internal.tck.ConfigFilesChecker import org.graalvm.internal.tck.DockerUtils -import org.graalvm.internal.tck.PullImagesFromFileTask import org.graalvm.internal.tck.ScaffoldTask import org.graalvm.internal.tck.GrypeTask import org.graalvm.internal.tck.TestedVersionUpdaterTask @@ -28,6 +27,7 @@ import org.graalvm.internal.tck.harness.tasks.NativeTestCompileInvocationTask import org.graalvm.internal.tck.updaters.FetchExistingLibrariesWithNewerVersionsTask import org.graalvm.internal.tck.updaters.GroupUnsupportedLibraries import org.gradle.util.internal.VersionNumber +import org.graalvm.internal.tck.PullImagesFromFileTask import static org.graalvm.internal.tck.Utils.generateTaskName @@ -47,11 +47,11 @@ def writeGithubOutput(String key, String value) { String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") // Support fractional batching coordinates in the form "k/n" (e.g., "1/16") -boolean isFractionalBatch(String s) { +static boolean isFractionalBatch(String s) { return s != null && (s ==~ /\d+\/\d+/) } -List parseFraction(String s) { +static List parseFraction(String s) { def m = s =~ /(\d+)\/(\d+)/ if (!m.matches()) return null int k = (m[0][1] as int) @@ -59,16 +59,16 @@ List parseFraction(String s) { return [k, n] } -List computeBatchedCoordinates(List allCoords, int k, int n) { - if (n <= 0) throw new GradleException("Invalid batches denominator: ${n}") - if (k < 1 || k > n) throw new GradleException("Invalid batch index: ${k}/${n}") - def sorted = new ArrayList(allCoords) - java.util.Collections.sort(sorted) - int target = (k - 1) +static List computeBatchedCoordinates(List coordinates, int index, int batches) { + if (batches <= 0) throw new GradleException("Invalid batches denominator: ${batches}") + if (index < 1 || index > batches) throw new GradleException("Invalid batch index: ${index}/${batches}") + def sorted = new ArrayList(coordinates) + Collections.sort(sorted) + int target = (index - 1) def result = [] int i = 0 for (String c : sorted) { - if ((i % n) == target) { + if ((i % batches) == target) { result.add(c) } i++ @@ -79,10 +79,8 @@ List computeBatchedCoordinates(List allCoords, int k, int n) { List matchingCoordinates if (isFractionalBatch(coordinateFilter)) { def frac = parseFraction(coordinateFilter) - int k = frac[0] - int n = frac[1] List all = tck.getMatchingCoordinates("all") - matchingCoordinates = computeBatchedCoordinates(all, k, n) + matchingCoordinates = computeBatchedCoordinates(all, frac[0], frac[1]) } else { matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter) } @@ -123,76 +121,8 @@ tasks.named("check").configure { final String METADATA_GROUP = "Metadata" -Provider pullAllowedDockerImagesMatching = tasks.register("pullAllowedDockerImagesMatching", DefaultTask) { task -> - task.setDescription("Pull allowed docker images for all matching coordinates") - task.setGroup(METADATA_GROUP) - task.doFirst { - if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { - throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") - } - println "Pulling allowed docker images for ${matchingCoordinates.size()} coordinate(s):" - matchingCoordinates.each { c -> println(" - ${c}") } - } -} -// Collect unique required images across matching coordinates and filter by allowed list -tasks.register("collectRequiredAllowedDockerImagesMatching", DefaultTask) { task -> - task.setDescription("Collect unique allowed docker images required by matching coordinates into a file") - task.setGroup(METADATA_GROUP) - File out = new File(buildDir, "required-docker-images-union.txt") - // Declare the output so Gradle can track it - task.outputs.file(out) - task.doFirst { - if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { - throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") - } - Set unionRequired = new LinkedHashSet<>() - matchingCoordinates.each { c -> - def parts = c.split(":") - if (parts.length < 3) { - logger.warn("Skipping invalid coordinates: ${c}") - return - } - def group = parts[0] - def artifact = parts[1] - def version = parts[2] - File f = project.file("tests/src/${group}/${artifact}/${version}/required-docker-images.txt") - if (f.exists()) { - f.readLines() - .collect { it?.trim() } - .findAll { it && !it.startsWith("#") } - .each { unionRequired.add(it) } - } - } - Set allowed = DockerUtils.getAllAllowedImages() - def notAllowed = unionRequired.findAll { !allowed.contains(it) } - if (!notAllowed.isEmpty()) { - throw new GradleException("The following images are not in the allowed list: ${notAllowed}. " + - "If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images " + - "per CONTRIBUTING.md, or adjust required-docker-images.txt files.") - } - - out.parentFile.mkdirs() - def finalList = unionRequired.findAll { allowed.contains(it) }.toList() - out.text = finalList.join(System.lineSeparator()) - println "Collected ${finalList.size()} required allowed image(s):" - finalList.each { println(" - ${it}") } - } -} - -// Pull the collected unique images (once) -tasks.register("pullRequiredAllowedDockerImagesMatching", PullImagesFromFileTask.class) { task -> - task.setDescription("Pull unique allowed docker images required by matching coordinates") - task.setGroup(METADATA_GROUP) - task.dependsOn("collectRequiredAllowedDockerImagesMatching") - task.imagesFile.set(new File(buildDir, "required-docker-images-union.txt")) -} - -// Rewire the aggregate to depend on the unique pull task -pullAllowedDockerImagesMatching.configure { - dependsOn("pullRequiredAllowedDockerImagesMatching") -} // Here we want to configure all test and checkstyle tasks for all filtered subprojects for (String coordinates in matchingCoordinates) { @@ -422,9 +352,57 @@ tasks.register("checkAllowedDockerImages", GrypeTask.class) { task -> task.setGroup(METADATA_GROUP) } -tasks.register("pullAllowedDockerImages", DockerTask.class) { task -> - task.setDescription("Pull allowed docker images from list.") +def imagesFileProvider = project.layout.buildDirectory.file("tck/required-docker-images.txt") + +def computeImages = tasks.register("computeAllowedDockerImagesFile", DefaultTask) { task -> + task.setDescription("Computes allowed docker images required by matching coordinates and writes them to a file") + task.setGroup(METADATA_GROUP) + task.outputs.file(imagesFileProvider) + task.doFirst { + if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { + throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") + } + Set unionRequired = new LinkedHashSet<>() + matchingCoordinates.each { c -> + def parts = c.split(":") + if (parts.length < 3) { + logger.warn("Skipping invalid coordinates: ${c}") + return + } + def group = parts[0] + def artifact = parts[1] + def version = parts[2] + File f = project.file("tests/src/${group}/${artifact}/${version}/required-docker-images.txt") + if (f.exists()) { + f.readLines() + .collect { it?.trim() } + .findAll { it && !it.startsWith("#") } + .each { unionRequired.add(it) } + } + } + + Set allowed = DockerUtils.getAllAllowedImages() + def notAllowed = unionRequired.findAll { !allowed.contains(it) } + if (!notAllowed.isEmpty()) { + throw new GradleException("The following images are not in the allowed list: ${notAllowed}. " + + "If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images " + + "per CONTRIBUTING.md, or adjust required-docker-images.txt files.") + } + + def finalList = unionRequired.findAll { allowed.contains(it) }.toList() + println "Collected ${finalList.size()} required allowed image(s)" + File outFile = imagesFileProvider.get().asFile + outFile.parentFile.mkdirs() + outFile.text = finalList.join(System.lineSeparator()) + } +} + +tasks.register("pullAllowedDockerImages", PullImagesFromFileTask.class) { task -> + task.setDescription("Pull allowed docker images required by matching coordinates") task.setGroup(METADATA_GROUP) + task.getImagesFile().set(imagesFileProvider) +}.configure { t -> + t.dependsOn(computeImages) } diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle index d0b443eba..35178718b 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle @@ -6,6 +6,8 @@ */ +import groovy.json.JsonSlurper + plugins { id 'org.graalvm.internal.tck-base' id 'checkstyle' @@ -51,6 +53,26 @@ String overrideVal = System.getenv("GVM_TCK_EXCLUDE") ?: providers.gradlePropert overrideVal = overrideVal ?: "false" boolean override = overrideVal.toBoolean() +// Determine native-image build arguments from ci.json. +def ciJsonFile = rootProject.file("ci.json") +def nativeImageArgs = [] +if (ciJsonFile.exists()) { + try { + def parsed = new JsonSlurper().parse(ciJsonFile) + def argsFromCi = parsed?.buildArgs + if (argsFromCi instanceof Collection && !argsFromCi.isEmpty()) { + nativeImageArgs = argsFromCi.collect { it.toString() } + // Post-process placeholders in args + nativeImageArgs = nativeImageArgs.collect { arg -> + arg.replace('{{library.version}}', libraryVersion) + .replace('{{library.coordinates}}', libraryGAV) + } + } + } catch (Exception ignored) { + throw new GradleException("ci.json must contain the buildArgs") + } +} + tck.testedLibraryVersion = libraryVersion // This value can be used to request specific library version to test with. @@ -78,8 +100,7 @@ graalvmNative { if (override) { excludeConfig.put(libraryGAV, [".*"]) } - verbose = true - quickBuild = true + buildArgs.addAll(nativeImageArgs) } } } From 8cc7affd70704e3668dfa1cdd901b6504563c599 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 17 Nov 2025 18:13:41 +0100 Subject: [PATCH 8/9] =?UTF-8?q?Give=20this=20repo=20the=20=E2=9D=A4?= =?UTF-8?q?=EF=B8=8F=20it=20deserves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - All commands now support a -Pcoordinates=k/n mode for batched execution. - One stop shop testing command: ./gradlew testAllParallel -Pparallelism=n - Documentation and structure cleanup (docs). - CLine support. - Complete command cleanup and simplification. --- .github/CODEOWNERS | 3 +- .github/workflows/checkstyle-skip.yml | 2 +- .github/workflows/checkstyle.yml | 13 +- .../workflows/create-scheduled-release.yml | 10 + .../library-and-framework-list-validation.yml | 4 +- .github/workflows/scan-docker-images.yml | 12 +- .../workflows/{ => scripts}/disable-docker.sh | 0 .../workflows/{ => scripts}/discard-port.conf | 0 .../workflows/{ => scripts}/dockerd.service | 0 .../{ => scripts}/run-consecutive-tests.sh | 2 +- .github/workflows/test-all-metadata.yml | 34 +-- .../workflows/test-changed-infrastructure.yml | 31 ++- .github/workflows/test-changed-metadata.yml | 38 +-- ...rify-new-library-version-compatibility.yml | 26 +- AGENTS.md | 64 +++++ README.md | 10 +- build.gradle | 158 +++++++++++- docs/CI.md | 37 +++ CONTRIBUTING.md => docs/CONTRIBUTING.md | 4 +- docs/CollectingMetadata.md | 2 +- docs/DEVELOPING.md | 138 ++++++++++ .../check-new-versions-of-libraries.md | 51 ---- REVIEWING.md => docs/REVIEWING.md | 4 +- SECURITY.md => docs/SECURITY.md | 0 .../3.1.0.M1/build.gradle | 2 +- .../thymeleaf-spring6/3.1.0.M2/build.gradle | 2 +- .../org.graalvm.internal.tck-harness.gradle | 235 +++++------------- .../groovy/org.graalvm.internal.tck.gradle | 2 +- .../internal/tck/harness/TckExtension.java | 47 ++-- .../tasks/AbstractSubprojectTask.groovy | 1 - .../harness/tasks/CleanInvocationTask.groovy | 8 + ...y => CompileTestJavaInvocationTask.groovy} | 4 +- ...ComputeAndPullAllowedDockerImagesTask.java | 138 ++++++++++ ...stingLibrariesWithNewerVersionsTask.groovy | 6 +- .../tck/harness/tasks/TeeOutputStream.java | 6 +- .../updaters/GroupUnsupportedLibraries.groovy | 53 ---- .../internal/tck/ContributionTask.java | 1 + .../graalvm/internal/tck/CoordinateUtils.java | 27 -- .../org/graalvm/internal/tck/Coordinates.java | 14 +- .../org/graalvm/internal/tck/DockerTask.java | 2 + .../org/graalvm/internal/tck/GrypeTask.java | 8 +- ...ker.java => MetadataFilesCheckerTask.java} | 49 ++-- .../internal/tck/PullImagesFromFileTask.java | 53 ---- .../graalvm/internal/tck/ScaffoldTask.java | 4 +- .../tck/TestedVersionUpdaterTask.java | 7 +- .../internal/tck/utils/CoordinateUtils.java | 95 +++++++ 46 files changed, 889 insertions(+), 518 deletions(-) rename .github/workflows/{ => scripts}/disable-docker.sh (100%) rename .github/workflows/{ => scripts}/discard-port.conf (100%) rename .github/workflows/{ => scripts}/dockerd.service (100%) rename .github/workflows/{ => scripts}/run-consecutive-tests.sh (96%) create mode 100644 AGENTS.md create mode 100644 docs/CI.md rename CONTRIBUTING.md => docs/CONTRIBUTING.md (99%) create mode 100644 docs/DEVELOPING.md delete mode 100644 docs/Infrastructure/check-new-versions-of-libraries.md rename REVIEWING.md => docs/REVIEWING.md (95%) rename SECURITY.md => docs/SECURITY.md (100%) rename tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/{JavacInvocationTask.groovy => CompileTestJavaInvocationTask.groovy} (84%) create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ComputeAndPullAllowedDockerImagesTask.java rename tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/{updaters => harness/tasks}/FetchExistingLibrariesWithNewerVersionsTask.groovy (97%) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/GroupUnsupportedLibraries.groovy delete mode 100644 tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/CoordinateUtils.java rename tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/{ConfigFilesChecker.java => MetadataFilesCheckerTask.java} (92%) delete mode 100644 tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java create mode 100644 tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/utils/CoordinateUtils.java diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d3c37ebb6..0bc4ad10e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,7 @@ tests/tck-build-logic/* @vjovanov tests/tck-build-logic/src/main/resources/allowed-docker-images/* @matneu library-and-framework-list.json @fniephaus .github/* @vjovanov -docs/* @vjovanov +docs/* @vjovanov @ban-mi +README.md @vjovanov @ban-mi gradle/* @vjovanov /* @vjovanov diff --git a/.github/workflows/checkstyle-skip.yml b/.github/workflows/checkstyle-skip.yml index 365b82c9c..2ae60e70b 100644 --- a/.github/workflows/checkstyle-skip.yml +++ b/.github/workflows/checkstyle-skip.yml @@ -3,7 +3,7 @@ name: "Check code style" on: pull_request: paths: - - 'docs/*' + - 'docs/**' - '**.md' - 'library-and-framework-list*.json' diff --git a/.github/workflows/checkstyle.yml b/.github/workflows/checkstyle.yml index a299a3e40..9fcef7915 100644 --- a/.github/workflows/checkstyle.yml +++ b/.github/workflows/checkstyle.yml @@ -3,24 +3,25 @@ name: "Check code style" on: pull_request: paths-ignore: - - 'docs/*' + - 'docs/**' - '**.md' - 'library-and-framework-list*.json' jobs: checkstyle: - if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ“‹ Check style according to checkstyle.xml" runs-on: "ubuntu-22.04" timeout-minutes: 15 steps: - name: "☁️ Checkout repository" uses: actions/checkout@v4 + - uses: actions/setup-python@v4 - - name: "πŸ”§ Prepare environment" - uses: graalvm/setup-graalvm@v1 + + - name: "πŸ”§ Setup java" + uses: actions/setup-java@v4 with: - java-version: '17' distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: '21' + - run: ./gradlew checkstyle diff --git a/.github/workflows/create-scheduled-release.yml b/.github/workflows/create-scheduled-release.yml index 7c465d613..a26e59e32 100644 --- a/.github/workflows/create-scheduled-release.yml +++ b/.github/workflows/create-scheduled-release.yml @@ -12,6 +12,7 @@ permissions: jobs: get-changed-metadata: name: "πŸ“‹ Get a list of changed metadata" + if: github.repository == 'oracle/graalvm-reachability-metadata' runs-on: "ubuntu-22.04" timeout-minutes: 5 outputs: @@ -22,11 +23,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: distribution: 'graalvm' java-version: '21' + - name: "πŸ•ΈοΈ Get changed metadata matrix" id: set-matrix run: | @@ -45,11 +48,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: distribution: 'graalvm' java-version: '21' + - name: "Get tags" run: | PREVIOUS_RELEASE_TAG=$(git tag --list | sort -V | tail -1) @@ -57,15 +62,19 @@ jobs: CURRENT_RELEASE_TAG=$(sed -E 's/^([0-9]+\.)([0-9]+\.)([0-9]+)/echo \1\2$((\3+1))/e' <<< $PREVIOUS_RELEASE_TAG) echo "CURRENT_RELEASE_TAG=$CURRENT_RELEASE_TAG" >> ${GITHUB_ENV} + - name: "⬆️ Update version" run: | sed -i "s/project.version(\"1.0.0-SNAPSHOT\")/project.version(\"${{ env.CURRENT_RELEASE_TAG }}\")/g" build.gradle + - name: "πŸ” Run spotless check" run: | ./gradlew spotlessCheck + - name: "🏭 Generate release artifacts" run: | ./gradlew package + - name: "πŸ“„ Commit changes" run: | git config --local user.email "actions@github.com" @@ -74,6 +83,7 @@ jobs: git commit -m "Release version ${{ env.CURRENT_RELEASE_TAG }}" git tag ${{ env.CURRENT_RELEASE_TAG }} git push origin ${{ env.CURRENT_RELEASE_TAG }} + - name: "πŸ“ Publish a release" run: | gh release create ${{ env.CURRENT_RELEASE_TAG }} build/graalvm-reachability-metadata-*.zip --generate-notes --notes-start-tag ${{ env.PREVIOUS_RELEASE_TAG }} diff --git a/.github/workflows/library-and-framework-list-validation.yml b/.github/workflows/library-and-framework-list-validation.yml index bec665690..a146b16fa 100644 --- a/.github/workflows/library-and-framework-list-validation.yml +++ b/.github/workflows/library-and-framework-list-validation.yml @@ -7,16 +7,17 @@ on: jobs: validate-library-and-framework-list-json: - if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ“‹ Validate the JSON file" runs-on: "ubuntu-22.04" timeout-minutes: 5 steps: - name: "☁️ Checkout repository" uses: actions/checkout@v4 + - uses: actions/setup-python@v4 with: python-version: '3.10' + - name: Check that the JSON file is well-formatted and sorted by artifact run: | JSON="library-and-framework-list.json" @@ -29,6 +30,7 @@ jobs: echo "'${JSON}' is no longer sorted by artifact key. You can use 'jq' to sort it: 'sorted="$(jq -s '.[] | sort_by(.artifact)' ${JSON})" && echo -E "${sorted}" > ${JSON}" exit 8 fi + - name: Check that the JSON file conforms to the schema run: | pip install check-jsonschema diff --git a/.github/workflows/scan-docker-images.yml b/.github/workflows/scan-docker-images.yml index 21a7c11a8..14d8c9117 100644 --- a/.github/workflows/scan-docker-images.yml +++ b/.github/workflows/scan-docker-images.yml @@ -18,18 +18,22 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: graalvm/setup-graalvm@v1 + + - uses: actions/setup-java@v4 with: - java-version: '17' distribution: 'graalvm' + java-version: '21' github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Install required tools" run: | - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin + curl -sSfL https://get.anchore.io/grype/v0.104.0/install.sh | sudo sh -s -- -b /usr/local/bin sudo apt-get install jq + - name: "πŸ”Ž Check changed docker images" if: github.event_name == 'pull_request' run: ./gradlew checkAllowedDockerImages --baseCommit=${{ github.event.pull_request.base.sha }} --newCommit=${{ github.event.pull_request.head.sha }} + - name: "πŸ”Ž Check all docker images" - if: github.event_name != 'pull_request' && github.repository == 'oracle/graalvm-reachability-metadata' + if: github.event_name == 'schedule' && github.repository == 'oracle/graalvm-reachability-metadata' run: ./gradlew checkAllowedDockerImages diff --git a/.github/workflows/disable-docker.sh b/.github/workflows/scripts/disable-docker.sh similarity index 100% rename from .github/workflows/disable-docker.sh rename to .github/workflows/scripts/disable-docker.sh diff --git a/.github/workflows/discard-port.conf b/.github/workflows/scripts/discard-port.conf similarity index 100% rename from .github/workflows/discard-port.conf rename to .github/workflows/scripts/discard-port.conf diff --git a/.github/workflows/dockerd.service b/.github/workflows/scripts/dockerd.service similarity index 100% rename from .github/workflows/dockerd.service rename to .github/workflows/scripts/dockerd.service diff --git a/.github/workflows/run-consecutive-tests.sh b/.github/workflows/scripts/run-consecutive-tests.sh similarity index 96% rename from .github/workflows/run-consecutive-tests.sh rename to .github/workflows/scripts/run-consecutive-tests.sh index beaac23a7..26036a4aa 100644 --- a/.github/workflows/run-consecutive-tests.sh +++ b/.github/workflows/scripts/run-consecutive-tests.sh @@ -62,7 +62,7 @@ for VERSION in "${VERSIONS[@]}"; do echo "$DELIMITER" - if ! run_multiple_attempts "javac compile" 1 javac; then + if ! run_multiple_attempts "javac compile" 1 compileTestJava; then break fi diff --git a/.github/workflows/test-all-metadata.yml b/.github/workflows/test-all-metadata.yml index 2b1c2eda5..4a87c9d71 100644 --- a/.github/workflows/test-all-metadata.yml +++ b/.github/workflows/test-all-metadata.yml @@ -1,4 +1,4 @@ -name: "Test all metadata" +name: "Test all metadata (one version per test suite)" on: workflow_dispatch: @@ -14,7 +14,7 @@ concurrency: jobs: get-all-metadata: - if: github.repository == 'oracle/graalvm-reachability-metadata' + if: github.event_name == 'workflow_dispatch' || github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ“‹ Get list of all supported libraries" runs-on: "ubuntu-22.04" timeout-minutes: 5 @@ -23,19 +23,19 @@ jobs: steps: - name: "☁️ Checkout repository" uses: actions/checkout@v4 - - name: "πŸ”§ Prepare environment" - uses: graalvm/setup-graalvm@v1 + + - name: "πŸ”§ Setup java" + uses: actions/setup-java@v4 with: - java-version: '17' distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: '21' + - name: "πŸ•ΈοΈ Populate matrix" id: set-matrix run: | ./gradlew generateMatrixBatchedCoordinates -Pbatches=16 test-all-metadata: - if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ§ͺ ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})" runs-on: ${{ matrix.os }} timeout-minutes: 120 @@ -46,24 +46,29 @@ jobs: steps: - name: "☁️ Checkout repository" uses: actions/checkout@v4 + - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: - distribution: 'oracle' - java-version: '17' - - name: "πŸ”§ Prepare environment" + distribution: 'graalvm' + java-version: '21' + + - name: "πŸ”§ Download GraalVM for metadata testing" uses: graalvm/setup-graalvm@v1 with: - set-java-home: 'false' - java-version: ${{ matrix.version }} distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: ${{ matrix.version }} + set-java-home: 'false' native-image-job-reports: 'true' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Pull allowed docker images" run: | ./gradlew pullAllowedDockerImages -Pcoordinates=${{ matrix.coordinates }} + - name: "Disable docker networking" - run: bash ./.github/workflows/disable-docker.sh + run: bash ./.github/workflows/scripts/disable-docker.sh + - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" run: | ./gradlew test -Pcoordinates=${{ matrix.coordinates }} @@ -72,7 +77,6 @@ jobs: name: "πŸ§ͺ All metadata tests have passed" runs-on: "ubuntu-22.04" timeout-minutes: 1 - if: ${{ always() }} && github.repository == 'oracle/graalvm-reachability-metadata' needs: test-all-metadata steps: - name: "All tests passed" diff --git a/.github/workflows/test-changed-infrastructure.yml b/.github/workflows/test-changed-infrastructure.yml index 539161917..5eac2ed13 100644 --- a/.github/workflows/test-changed-infrastructure.yml +++ b/.github/workflows/test-changed-infrastructure.yml @@ -17,7 +17,6 @@ concurrency: jobs: get-changed-infrastructure: - if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ“‹ Get a list of libraries to test for build-logic changes" runs-on: "ubuntu-22.04" timeout-minutes: 5 @@ -29,12 +28,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: "πŸ”§ Prepare environment" - uses: graalvm/setup-graalvm@v1 + + - name: "πŸ”§ Setup java" + uses: actions/setup-java@v4 with: - java-version: "17" - distribution: "graalvm" - github-token: ${{ secrets.GITHUB_TOKEN }} + distribution: 'graalvm' + java-version: '21' + - name: "πŸ•ΈοΈ Populate matrix" id: set-matrix run: | @@ -58,28 +58,28 @@ jobs: - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: - distribution: "oracle" - java-version: "17" + distribution: 'graalvm' + java-version: '21' - - name: "πŸ”§ Prepare environment" + - name: "πŸ”§ Download GraalVM for metadata testing" uses: graalvm/setup-graalvm@v1 with: - set-java-home: "false" - java-version: ${{ matrix.version }} distribution: "graalvm" + java-version: ${{ matrix.version }} + set-java-home: "false" github-token: ${{ secrets.GITHUB_TOKEN }} native-image-job-reports: "true" - name: "Pull allowed docker images" run: | - ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + ./gradlew pullAllowedDockerImages -Pcoordinates=${{ matrix.coordinates }} - name: "Disable docker networking" - run: bash ./.github/workflows/disable-docker.sh + run: bash ./.github/workflows/scripts/disable-docker.sh - - name: "πŸ”Ž Check metadata config files content" + - name: "πŸ”Ž Check metadata files content" run: | - ./gradlew checkConfigFiles --coordinates=${{ matrix.coordinates }} + ./gradlew checkMetadataFiles -Pcoordinates=${{ matrix.coordinates }} - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" run: | @@ -89,7 +89,6 @@ jobs: name: "πŸ§ͺ All build-logic triggered tests have passed" runs-on: "ubuntu-22.04" timeout-minutes: 1 - if: ${{ always() }} && github.repository == 'oracle/graalvm-reachability-metadata' needs: test-changed-infrastructure steps: - name: "All tests passed" diff --git a/.github/workflows/test-changed-metadata.yml b/.github/workflows/test-changed-metadata.yml index 31c93eee5..3e104005e 100644 --- a/.github/workflows/test-changed-metadata.yml +++ b/.github/workflows/test-changed-metadata.yml @@ -5,8 +5,8 @@ on: branches: - master paths: - - 'metadata/*' - - 'tests/src/*' + - 'metadata/**' + - 'tests/src/**' concurrency: group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}" @@ -14,7 +14,6 @@ concurrency: jobs: get-changed-metadata: - if: github.repository == 'oracle/graalvm-reachability-metadata' name: "πŸ“‹ Get a list of all changed libraries" runs-on: "ubuntu-22.04" timeout-minutes: 5 @@ -26,11 +25,11 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: "πŸ”§ Prepare environment" - uses: graalvm/setup-graalvm@v1 + - name: "πŸ”§ Setup Java" + uses: actions/setup-java@v4 with: - java-version: '17' distribution: 'graalvm' + java-version: '21' github-token: ${{ secrets.GITHUB_TOKEN }} - name: "πŸ•ΈοΈ Populate matrix" id: set-matrix @@ -50,27 +49,33 @@ jobs: steps: - name: "☁️ Checkout repository" uses: actions/checkout@v4 + - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: - distribution: 'oracle' - java-version: '17' - - name: "πŸ”§ Prepare environment" + distribution: 'graalvm' + java-version: '21' + + - name: "πŸ”§ Download GraalVM for metadata testing" uses: graalvm/setup-graalvm@v1 with: - set-java-home: 'false' - java-version: ${{ matrix.version }} distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: ${{ matrix.version }} + set-java-home: 'false' native-image-job-reports: 'true' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Pull allowed docker images" run: | - ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + ./gradlew pullAllowedDockerImages -Pcoordinates=${{ matrix.coordinates }} + - name: "Disable docker networking" - run: bash ./.github/workflows/disable-docker.sh - - name: "πŸ”Ž Check metadata config files content" + run: bash ./.github/workflows/scripts/disable-docker.sh + + - name: "πŸ”Ž Check metadata files content" run: | - ./gradlew checkConfigFiles --coordinates=${{ matrix.coordinates }} + ./gradlew checkMetadataFiles -Pcoordinates=${{ matrix.coordinates }} + - name: "πŸ§ͺ Run '${{ matrix.coordinates }}' tests" run: | ./gradlew test -Pcoordinates=${{ matrix.coordinates }} @@ -79,7 +84,6 @@ jobs: name: "πŸ§ͺ All metadata tests have passed" runs-on: "ubuntu-22.04" timeout-minutes: 1 - if: ${{ always() }} && github.repository == 'oracle/graalvm-reachability-metadata' needs: test-changed-metadata steps: - name: "All tests passed" diff --git a/.github/workflows/verify-new-library-version-compatibility.yml b/.github/workflows/verify-new-library-version-compatibility.yml index 46e6e90e5..652f56fc8 100644 --- a/.github/workflows/verify-new-library-version-compatibility.yml +++ b/.github/workflows/verify-new-library-version-compatibility.yml @@ -16,6 +16,7 @@ concurrency: jobs: get-all-libraries: name: "πŸ“‹ Get list of all supported libraries with newer versions" + # Opens PRs, with labels and assignees. Works only on the main repo. if: github.repository == 'oracle/graalvm-reachability-metadata' runs-on: ubuntu-22.04 timeout-minutes: 5 @@ -27,12 +28,11 @@ jobs: - name: "☁️ Checkout repository" uses: actions/checkout@v4 - - name: "πŸ”§ Prepare environment" - uses: graalvm/setup-graalvm@v1 + - name: "πŸ”§ Setup java" + uses: actions/setup-java@v4 with: - java-version: '21' distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: '21' - name: "πŸ“… Set branch name" id: set-branch-name @@ -74,17 +74,17 @@ jobs: - name: "πŸ”§ Setup java" uses: actions/setup-java@v4 with: - distribution: 'oracle' + distribution: 'graalvm' java-version: '21' - - name: "πŸ”§ Prepare environment" + - name: "πŸ”§ Download GraalVM for metadata testing" uses: graalvm/setup-graalvm@v1 with: - set-java-home: 'false' - java-version: 21 distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} + java-version: 25 + set-java-home: 'false' native-image-job-reports: 'true' + github-token: ${{ secrets.GITHUB_TOKEN }} - name: "Check for an existing issue and skip further testing if found" id: check_existing_issue @@ -122,17 +122,17 @@ jobs: - name: "Pull allowed docker images" if: steps.check_existing_issue.outputs.skip != 'true' - run: ./gradlew pullAllowedDockerImages --coordinates="${{ env.TEST_COORDINATES }}" + run: ./gradlew pullAllowedDockerImages -Pcoordinates="${{ env.TEST_COORDINATES }}" - name: "Disable docker networking" if: steps.check_existing_issue.outputs.skip != 'true' - run: bash ./.github/workflows/disable-docker.sh + run: bash ./.github/workflows/scripts/disable-docker.sh - name: "πŸ§ͺ Run '${{ env.TEST_COORDINATES }}' tests" if: steps.check_existing_issue.outputs.skip != 'true' id: runtests run: | - bash ./.github/workflows/run-consecutive-tests.sh "${{ env.TEST_COORDINATES }}" '${{ toJson(matrix.item.versions) }}' 2>&1 | tee test_results.txt || true + bash ./.github/workflows/scripts/run-consecutive-tests.sh "${{ env.TEST_COORDINATES }}" '${{ toJson(matrix.item.versions) }}' 2>&1 | tee test_results.txt || true # Extract successful versions grep "^PASSED:" test_results.txt | sed 's/PASSED://g' > successful_versions.txt @@ -174,7 +174,7 @@ jobs: update_new_versions() { while read version; do if [ -n "$version" ]; then - ./gradlew addTestedVersion --coordinates="${{ matrix.item.name }}:$version" --lastSupportedVersion="${{ env.LATEST_VERSION }}" + ./gradlew addTestedVersion -Pcoordinates="${{ matrix.item.name }}:$version" --lastSupportedVersion="${{ env.LATEST_VERSION }}" fi done < successful_versions.txt } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..62a30bd90 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,64 @@ +# Development Cheat Sheet + +## Prerequisites (assume exists) +- JAVA_HOME set to JDK 21 (GraalVM recommended to match CI) +- Docker +- grype v0.104.0 (install: curl -sSfL https://get.anchore.io/grype/v0.104.0/install.sh | sudo sh -s -- -b /usr/local/bin) + +## Setup +- Always use Gradle wrapper from repo root: + - Unix: ./gradlew [options] + - Windows: gradlew.bat [options] +- Tip: add --stacktrace for debugging + +## One command for complete testing (use at the end of the task to verify) +./gradlew testAllParallel -Pparallelism=4 + +## Code Style +- Always try to reuse existing code. +- Be assertive in code. +- Write type annotations in all functions and most variables. +- Document code without being too verbose. +- In java, always import classes and use them without qualified names. + +## Testing individual components + +- Clean previous build outputs for the selected coordinates: ./gradlew clean -Pcoordinates=[group:artifact:version|k/n|all] +- Pre-fetch Docker images allowed by metadata (used in tests) for the selected coordinates: ./gradlew pullAllowedDockerImages -Pcoordinates=[group:artifact:version|k/n|all] +- Validate reachability metadata files for the selected coordinates: ./gradlew checkMetadataFiles -Pcoordinates=[group:artifact:version|k/n|all] +- Run Checkstyle for the selected coordinates: ./gradlew checkstyle -Pcoordinates=[group:artifact:version|k/n|all] +- Compile test sources for the selected coordinates: ./gradlew compileTestJava -Pcoordinates=[group:artifact:version|k/n|all] +- Run JVM-based tests for the selected coordinates: ./gradlew javaTest -Pcoordinates=[group:artifact:version|k/n|all] +- Build native images used by native tests (compile-only) for the selected coordinates: ./gradlew nativeTestCompile -Pcoordinates=[group:artifact:version|k/n|all] +- Run all tests for the selected coordinates: ./gradlew test -Pcoordinates=[group:artifact:version|k/n|all] + + +## Check style and formatting +- Style check: ./gradlew checkstyle +- Format check: ./gradlew spotlessCheck + +## Testing the metadata +- Single library (replace with group:artifact:version): + - ./gradlew pullAllowedDockerImages -Pcoordinates=group:artifact:version + - ./gradlew checkMetadataFiles -Pcoordinates=group:artifact:version + - ./gradlew test -Pcoordinates=group:artifact:version +- Sharded example (1/64): + - ./gradlew pullAllowedDockerImages -Pcoordinates=1/64 + - ./gradlew checkMetadataFiles -Pcoordinates=1/64 + - ./gradlew test -Pcoordinates=1/64 + +## Docker Image Vulnerability Scanning +- Changed images between commits: + - ./gradlew checkAllowedDockerImages --baseCommit=$(git rev-parse origin/master) --newCommit=$(git rev-parse HEAD) +- All allowed images: + - ./gradlew checkAllowedDockerImages + +##s Compatibility Automation (latest library versions) +- List libs with newer upstream versions: + - ./gradlew fetchExistingLibrariesWithNewerVersions --quiet +- Record a newly tested version: + - ./gradlew addTestedVersion -Pcoordinates="group:artifact:newVersion" --lastSupportedVersion="oldVersion" + - Example: ./gradlew addTestedVersion -Pcoordinates="org.postgresql:postgresql:42.7.4" --lastSupportedVersion="42.7.3" + +## Releases and Packaging +- Package artifacts: ./gradlew package diff --git a/README.md b/README.md index f87d710fe..894fe49cc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ you can enable automatic use of the metadata repository for [Gradle projects](ht [This web page](https://www.graalvm.org/native-image/libraries-and-frameworks/) provides an overview of libraries and frameworks that are tested and thus ready for GraalVM Native Image. If you would like to see your library or framework in the list too, please open a pull request and extend [this JSON file](https://github.com/oracle/graalvm-reachability-metadata/blob/master/library-and-framework-list.json). -Before submitting a pull request, please read [this guide](./CONTRIBUTING.md#tested-libraries-and-frameworks). +Before submitting a pull request, please read [this guide](docs/CONTRIBUTING.md#tested-libraries-and-frameworks). ## Rationale @@ -25,4 +25,10 @@ Please visit [this web page](https://www.graalvm.org/latest/reference-manual/nat ## Contributing We welcome contributions from the community. -Before submitting a pull request, please [review our contribution guide](./CONTRIBUTING.md). +Before submitting a pull request, please [review our contribution guide](docs/CONTRIBUTING.md). + +## Further Information + +1. Continuous integration is described in [CI.md](docs/CI.md). +2. PR Reviewing guides are described in [REVIEWING.md](docs/REVIEWING.md). +3. Development is described in [DEVELOPING.md](docs/DEVELOPING.md). diff --git a/build.gradle b/build.gradle index d043c1cff..ee694bc4c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + /* * Copyright and related rights waived via CC0 * @@ -8,7 +11,7 @@ plugins { id 'base' id "com.diffplug.spotless" version "6.3.0" - id "org.graalvm.internal.tck-harness" + id "org.graalvm.internal.tck-base" } allprojects { @@ -25,17 +28,17 @@ project.version("1.0.0-SNAPSHOT") spotless { json { target( - tck.metadataRoot.map { it.toString() + '/**/*.json' }.get(), + tck.metadataRoot.map { it.toString() + '/**/*.json' }.get(), tck.testRoot.map { it.toString() + '/**/*.json' } ) targetExclude( - tck.testRoot.map { it.toString() + '/**/build/**/*.json' }, + tck.testRoot.map { it.toString() + '/**/build/**/*.json' }, tck.repoRoot.map { it.toString() + '/.github/**/*.json' } ) gson() - .indentWithSpaces(2) - .sortByKeys() - .version("2.9.0") + .indentWithSpaces(2) + .sortByKeys() + .version("2.9.0") } } @@ -54,3 +57,146 @@ tasks.register('package', Zip) { task -> include("library-and-framework-list.json") } } + +tasks.register('test') { t -> + t.setDescription("Aggregates tests configured by the TCK harness") + t.setGroup("verification") +} + +// Apply the harness after creating the 'test' aggregator to avoid missing property, +// and without applying the 'java' plugin to avoid task name conflicts. +apply plugin: "org.graalvm.internal.tck-harness" + +ext.runLoggedCommand = { List args, String label, File logFile, File workingDir -> + def header = "==== BEGIN ${label}" + println(header) + logFile.text = header + System.lineSeparator() + + def pb = new ProcessBuilder(args as String[]) + pb.directory(workingDir) + pb.redirectErrorStream(true) + pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)) + def proc = pb.start() + int exit = proc.waitFor() + + def footer = exit != 0 + ? "==== END ${label} FAILURE (exit ${exit})" + : "==== END ${label} SUCCESS" + logFile.append(footer + System.lineSeparator()) + println(footer) + return exit +} + +static List testAllCommands(String gradleCmd, String target) { + def taskNames = [ + "checkstyle", + "spotlessCheck", + "checkMetadataFiles", + "fetchExistingLibrariesWithNewerVersions", + "test" + ] + return taskNames.collect { name -> + [name: name, args: [gradleCmd, name, "-Pcoordinates=${target}"]] + } +} + +tasks.register('testAllParallel') { t -> + t.group = "verification" + t.setDescription("For each target, run clean and pullAllowedDockerImages first (sequentially), then run style checks and individual testing stages (checkstyle, spotlessCheck, checkMetadataFiles, fetchExistingLibrariesWithNewerVersions, test) concurrently with isolated logs. Options: -Pcoordinates to specify a single coordinate or 1/64 for the pull step; -Pparallelism to control the number of concurrent tasks.") + + doLast { + def selectedArtifact = "org.postgresql:postgresql:42.7.3" + def selectedBatch = "1/64" + + def gradleCmd = System.getProperty("os.name").toLowerCase().contains("windows") ? "gradlew.bat" : "./gradlew" + def parallelismProp = (project.findProperty('parallelism') ?: '').toString().trim() + int parallelism = 4 + if (!parallelismProp.isEmpty()) { + try { + parallelism = Integer.parseInt(parallelismProp) + } catch (NumberFormatException ignored) { + throw new GradleException("Invalid -Pparallelism='${parallelismProp}': must be a positive integer.") + } + if (parallelism <= 0) { + throw new GradleException("Invalid -Pparallelism='${parallelismProp}': must be >= 1.") + } + } + println("Using parallelism=${parallelism} for testAllParallel.") + + def logsDir = layout.buildDirectory.dir("command-logs").get().asFile + logsDir.mkdirs() + + // Helper to run pull + command set for a given target (artifact or coordinates) + def runPhaseForCoordinate = { String coordinate -> + // Clean project before pulling Docker images for this coordinate + def cleanLogsDir = new File(project.rootDir, "command-logs-clean") + cleanLogsDir.mkdirs() + + def fileSuffix = coordinate.replaceAll(File.separator, "-") + def cleanLogFile = new File(cleanLogsDir, "clean-${fileSuffix}.log") + int cleanExit = project.ext.runLoggedCommand([gradleCmd, "clean", "-Pcoordinates=${coordinate}"], "clean (${coordinate})", cleanLogFile, project.rootDir) + if (cleanExit != 0) { + throw new GradleException("clean failed with exit code ${cleanExit} (see ${cleanLogFile})") + } + // Ensure logsDir exists after clean + logsDir.mkdirs() + + // Pull Docker images sequentially for this coordinate + def pullArgs = [gradleCmd, "pullAllowedDockerImages", "-Pcoordinates=${coordinate}"] + def pullLogFile = new File(logsDir, "pullAllowedDockerImages-${fileSuffix}.log") + int pullExit = project.ext.runLoggedCommand(pullArgs, "pullAllowedDockerImages (${coordinate})", pullLogFile, project.rootDir) + if (pullExit != 0) { + throw new GradleException("pullAllowedDockerImages failed with exit code ${pullExit} (see ${pullLogFile})") + } + + def commands = testAllCommands(gradleCmd, coordinate) + int effectiveParallelism = Math.min(parallelism, commands.size()) + def pool = Executors.newFixedThreadPool(effectiveParallelism) + def failures = Collections.synchronizedList(new ArrayList()) + def futures = [] + + commands.each { cmd -> + futures << pool.submit { + def label = "${cmd.name} (${coordinate})" + def logFile = new File(logsDir, "${cmd.name}-${fileSuffix}.log") + int exit = project.ext.runLoggedCommand(cmd.args, label, logFile, project.rootDir) + if (exit != 0) { + failures.add([label: label, logFile: logFile, exit: exit]) + } + } + } + + futures.each { it.get() } + pool.shutdown() + pool.awaitTermination(1, TimeUnit.HOURS) + + if (!failures.isEmpty()) { + println("Some commands failed (coordinates=${coordinate}). Logs in: ${logsDir}") + failures.each { f -> + println(" - ${f.label} failed with exit code ${f.exit} (see ${f.logFile})") + } + println("==== BEGIN FAILED TASK LOGS (${coordinate})") + failures.each { f -> + println("---- LOG: ${f.logFile}") + try { + if (f.logFile?.exists()) { + f.logFile.eachLine { println(it) } + } else { + println("(log file not found)") + } + } catch (Throwable e) { + println("Error reading ${f.logFile}: ${e.message}") + } + } + println("==== END FAILED TASK LOGS (${coordinate})") + throw new GradleException("Failures encountered. See logs above. Logs directory: ${logsDir}") + } else { + println("All commands succeeded for coordinates=${coordinate}. Logs in: ${logsDir}") + } + } + + // Run parallel phases sequentially: first the selectedArtifact, then the selectedBatch + runPhaseForCoordinate(selectedArtifact) + runPhaseForCoordinate(selectedBatch) + } +} diff --git a/docs/CI.md b/docs/CI.md new file mode 100644 index 000000000..4d5e42588 --- /dev/null +++ b/docs/CI.md @@ -0,0 +1,37 @@ +# Continuous Integration (CI) + +This repository uses GitHub Actions and Gradle-based checks to ensure metadata quality, stability, and automated releases. +Below is an overview of the CI pipelines and the configuration they rely on. + +- GitHub Actions workflows: [.github/workflows](../.github/workflows)/*.yml +- Source of truth for style checks: [gradle/checkstyle.xml](../gradle/checkstyle.xml) + +CI prefetches docker images and temporarily disables Docker networking for test isolation via [`.github/workflows/disable-docker.sh`](../.github/workflows/scripts/disable-docker.sh). +You usually do not need this locally. + +## Types of jobs in the CI + +The release is made every two weeks if there are metadata changes: [.github/workflows/create-scheduled-release.yml](../.github/workflows/create-scheduled-release.yml). + +The test matrx definition starts with [ci.json](../ci.json): +- A single source of truth for which Java versions and OS runners to test on. +- Gradle tasks read it to generate GitHub Actions matrices consumed by the workflows below. +- Way to define build arguments for the build. If this file is changed everything is re-run. + +Workflows testing metadata using [ci.json](../ci.json): +- Test all metadata ([.github/workflows/test-all-metadata.yml](../.github/workflows/test-all-metadata.yml)) + - Triggers: manual (workflow_dispatch) and PRs that touch [ci.json](../ci.json). + - Uses: generateMatrixBatchedCoordinates to build a matrix (java + os). Runs full tests, pulls only allowed Docker images, then disables Docker networking for deterministic runs. +- Test changed metadata ([.github/workflows/test-changed-metadata.yml](../.github/workflows/test-changed-metadata.yml)) + - Triggers: PRs to master touching [metadata/](metadata/) or [tests/src/](tests/src/). + - Uses: generateChangedCoordinatesMatrix with base/head SHAs to test only what changed. Pulls allowed images, disables Docker networking, validates config, then runs tests. +- Test changed build logic ([.github/workflows/test-changed-infrastructure.yml](../.github/workflows/test-changed-infrastructure.yml)) + - Triggers: PRs to master touching [tests/tck-build-logic/](tests/tck-build-logic/), [gradle/](gradle/), [build.gradle](../build.gradle), [settings.gradle](../settings.gradle), or [gradle.properties](../gradle.properties). + - Uses: generateInfrastructureChangedCoordinatesMatrix. Pulls allowed images, disables Docker networking, validates config, then runs tests. + +Workflow for testing latest library versions from Maven: [.github/workflows/verify-new-library-version-compatibility.yml](../.github/workflows/verify-new-library-version-compatibility.yml): scheduled verifier for newer upstream library versions; uses pinned Java/OS in the workflow. + +Workflows for style and security: +- [.github/workflows/checkstyle.yml](../.github/workflows/checkstyle.yml) / [.github/workflows/checkstyle-skip.yml](../.github/workflows/checkstyle-skip.yml): code style checks. +- [.github/workflows/library-and-framework-list-validation.yml](../.github/workflows/library-and-framework-list-validation.yml) / [.github/workflows/library-and-framework-list-validation-skip.yml](../.github/workflows/library-and-framework-list-validation-skip.yml): validates and sorts [library-and-framework-list.json](../library-and-framework-list.json), checks schema. +- [.github/workflows/scan-docker-images.yml](../.github/workflows/scan-docker-images.yml) / [.github/workflows/scan-docker-images-skip.yml](../.github/workflows/scan-docker-images-skip.yml): scans allowed Docker images on PR/schedule. diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 99% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index 128c671d1..f53c3949e 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -52,9 +52,9 @@ not be included as it does not compose and can break code in unpredictable ways. * Make sure that you are using [Conditional Configuration](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json) in order to precisely define the metadata scope. This is a hard requirement as it prevents unnecessary bloating of images. -* Once you want to create a pull request, you will be asked to fill out the [following list](.github/pull_request_template.md). +* Once you want to create a pull request, you will be asked to fill out the [following list](../.github/pull_request_template.md). -ℹ️ To learn more about collecting metadata, see [How To Collect Metadata](docs/CollectingMetadata.md). +ℹ️ To learn more about collecting metadata, see [How To Collect Metadata](CollectingMetadata.md). ### Generate Metadata and Test diff --git a/docs/CollectingMetadata.md b/docs/CollectingMetadata.md index 16670a652..60d21250a 100644 --- a/docs/CollectingMetadata.md +++ b/docs/CollectingMetadata.md @@ -81,4 +81,4 @@ The best thing for the community is to include the generated metadata and build - Copy the metadata to the project sources under META-INF/native-image/artifact.id with `metadataCopy`. - Introduce a CI task that tests the library with the newly added metadata. -If the library maintainers do not accept the change, the next best thing is to open a ticket on the library’s issue tracker so the community can upvote this feature. In the meantime, you can open a PR to this repository. See [how to contribute](../CONTRIBUTING.md). +If the library maintainers do not accept the change, the next best thing is to open a ticket on the library’s issue tracker so the community can upvote this feature. In the meantime, you can open a PR to this repository. See [how to contribute](CONTRIBUTING.md). diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md new file mode 100644 index 000000000..51bfd38c0 --- /dev/null +++ b/docs/DEVELOPING.md @@ -0,0 +1,138 @@ +# Developing the Repository Infrastructure + +This document summarizes those commands that you need to perform development tasks. + +This repo is developed with IntelliJ. To initialize use: +```console +intellij-idea-community ./ +``` + +Always use the Gradle wrapper from the repository root: +- Unix: `./gradlew [options]` +- Windows: `gradlew.bat [options]` + +Prerequisites for most commands: +- `JAVA_HOME` should be set to JDK 21 or later. GraalVM is recommended to match CI. +- Docker (required for pulling/using allowed images during tests). Needs to work without `sudo`: `sudo usermod -aG docker $USER` and reboot. +- [`grype`](https://github.com/anchore/grype) version `0.104.0` for scanning docker images: + ```console + curl -sSfL https://get.anchore.io/grype/v0.104.0/install.sh | sudo sh -s -- -b /usr/local/bin + ``` + +Tip: When debugging locally, add `--stacktrace` for better error output. + +### End-to-end testing before the commit +```console +./gradlew testAllParallel --stacktrace +``` + +### Style and formatting + +1. To check style use + ```console + ./gradlew checkstyle + ``` + This will run Checkstyle using `gradle/checkstyle.xml`. + +2. Spotless Verifies formatting (used in release workflow prior to packaging): + ```console + ./gradlew spotlessCheck + ``` + +### Testing one library locally + +For a single coordinate, CI runs three steps in this order: +1. Pull Docker images: + ```console + ./gradlew pullAllowedDockerImages -Pcoordinates=org.postgresql:postgresql:42.7.3 + ``` +2. Validate metadata: + ```console + ./gradlew checkMetadataFiles -Pcoordinates=org.postgresql:postgresql:42.7.3 + ``` +3. Then run tests: + ```console + ./gradlew test -Pcoordinates=org.postgresql:postgresql:42.7.3 + ``` + +### Testing libraries in bulk + +To exercise many tests at once, you can target all coordinates or a slice of the test space via the N/M shard syntax. + +All coordinates: +1. Test the whole repo: + ```console + ./gradlew pullAllowedDockerImages -Pcoordinates=all + ./gradlew checkMetadataFiles -Pcoordinates=all + ./gradlew test -Pcoordinates=all + ``` + +2. Splitting the tests in batches (e.g., batch 1 of 16): + ```console + ./gradlew pullAllowedDockerImages -Pcoordinates=1/16 + ./gradlew checkMetadataFiles -Pcoordinates=1/16 + ./gradlew test -Pcoordinates=1/16 + ``` + +### Testing individual stages + +Each stage of the testing can be run with `-Pcoordinates=[group:artifact:version|k/n|all]`. Here are the examples: +```console +./gradlew clean -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew pullAllowedDockerImages -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew checkMetadataFiles -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew checkstyle -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew compileTestJava -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew javaTest -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew nativeTestCompile -Pcoordinates=[group:artifact:version|k/n|all] +./gradlew test -Pcoordinates=[group:artifact:version|k/n|all] +``` + +### Docker image vulnerability scanning + +1. Scan only images affected in a commit range: + ```console + ./gradlew checkAllowedDockerImages --baseCommit=$(git rev-parse origin/master) --newCommit=$(git rev-parse HEAD) + ``` + +2. Scan all allowed images + ```console + ./gradlew checkAllowedDockerImages + ``` + +### Compatibility automation with latest library versions + +These tasks support the scheduled workflow that checks newer upstream library versions and updates our metadata accordingly. + +1. List supported libraries that have newer upstream versions + ```console + ./gradlew fetchExistingLibrariesWithNewerVersions --quiet + ``` + +2. Mark a new tested version for a library + ```console + ./gradlew addTestedVersion -Pcoordinates="group:artifact:newVersion" --lastSupportedVersion="oldVersion" + ``` + For example: + ```console + ./gradlew addTestedVersion -Pcoordinates="org.postgresql:postgresql:42.7.4" --lastSupportedVersion="42.7.3" + ``` + +### Releases and Packaging + +```console +./gradlew package +``` + +### Quick reference (copy/paste) + +- Style: `./gradlew checkstyle` +- Format check: `./gradlew spotlessCheck` +- Pull images (single lib): `./gradlew pullAllowedDockerImages -Pcoordinates=[group:artifact:version|k/n|all]` +- Check metadata (single lib): `./gradlew checkMetadataFiles -Pcoordinates=[group:artifact:version|k/n|all]` +- Test (single lib): `./gradlew test -Pcoordinates=[group:artifact:version|k/n|all]` +- Scan changed Docker images: `./gradlew checkAllowedDockerImages --baseCommit= --newCommit=` +- Scan all Docker images: `./gradlew checkAllowedDockerImages` +- List libs with newer versions: `./gradlew fetchExistingLibrariesWithNewerVersions --quiet` +- Record a newly tested version: `./gradlew addTestedVersion -Pcoordinates="group:artifact:newVersion" --lastSupportedVersion="oldVersion"` +- Package release artifacts: `./gradlew package` diff --git a/docs/Infrastructure/check-new-versions-of-libraries.md b/docs/Infrastructure/check-new-versions-of-libraries.md deleted file mode 100644 index 79c97e54b..000000000 --- a/docs/Infrastructure/check-new-versions-of-libraries.md +++ /dev/null @@ -1,51 +0,0 @@ -# Check new versions of existing libraries in the repository - -As the number of libraries in the repository grow fast, it is hard to track new library versions for every library manually. -Instead of doing this process manually, we provided a mechanism (through [a GitHub workflow](https://github.com/oracle/graalvm-reachability-metadata/blob/master/.github/workflows/check-new-library-versions.yml)) -that automatically scans MavenCentral repository for new versions of the libraries that we currently have. - -## How it works - -The workflow gets triggered every two weeks automatically (alternating to the automatic release weeks). Besides that, the job can be triggered manually from the GitHub actions. -The whole process consists of the following parts: -* Scanning of the MavenCentral -* Running existing tests with newer versions of the library -* Creating a pull-request that updates `tested-versions` field of the `index.json` file for libraries that passed tests with a new version -* Creating an issue that lists all versions of libraries that failed their existing tests. - -As a preparation for the whole process, we are creating a branch for all successful tests, and a single issue for all failed tests. - -### Scanning the MavenCentral - -At first, the workflow runs gradle task called `fetchExistingLibrariesWithNewerVersions`. -The task itself does the following: -1. Gets the list of all existing libraries in the repository -2. For each library, it searches for the latest tested version in the corresponding library `index.json` file -3. For the given library name, it fetches `maven-metadata.xml` file from the MavenCentral repository -4. In the fetched `maven-metadata.xml` file, it finds the position of the latest tested version (gathered in the step 3) and returns all the versions after it -5. As a last step, the task returns list of maven coordinates of libraries with newer versions (alongside java version and os version required for testing) - -### Running existing tests with newer versions - -Now that we have coordinates list, we are spawning a new job in GitHub workflow for each coordinate in the list. -Each of the spawned jobs: -1. Extracts the following parts from the given maven coordinates: - 1. Latest version that we have tests written for - 2. Path to the latest tests we have - 3. Maven coordinates of the latest tests -2. Sets `GVM_TCK_LV` env variable to the version we want to test. This way the executed tests will use library version specified in the env variable. -3. Run the latest test with `./gradlew test -Pcoordinates=` (with `testCoordinates` calculated in the step 1) - -### Aggregating results of the tests - -Based on the outcome of the test we: -* Update the list of `tested-versions` in the proper library `index.json` file and commit changes to the previously created branch, if the test passed -* Add a comment that explains which library version cannot pass the tests, in the issue we previously created - -Note: since the spawned jobs run tests in parallel, we have to make some kind of synchronization to avoid merge conflicts if two tests are populating the same `index.json` file. -The whole process of synchronization is driven by the [tryPushVersionsUpdate](https://github.com/oracle/graalvm-reachability-metadata/blob/master/.github/workflows/tryPushVersionsUpdate.sh) script. - -At the end, when all jobs have finished their executions, the workflow just creates a pull-request based on a branch the jobs committed to. -As a final result, we have: -* a pull-request with updates of all new tested versions -* an issue with list of all versions that doesn't work with existing metadata diff --git a/REVIEWING.md b/docs/REVIEWING.md similarity index 95% rename from REVIEWING.md rename to docs/REVIEWING.md index 194d824d9..b65f34cf7 100644 --- a/REVIEWING.md +++ b/docs/REVIEWING.md @@ -6,7 +6,7 @@ This document should serve as a guideline for reviewers to simplify and harmoniz ## Checklist First step of every review is to verify the checklist from the pull request description. -Once the contributor wants to open a pull request [this checklist](.github/pull_request_template.md) will be automatically added to the pull request description. +Once the contributor wants to open a pull request [this checklist](../.github/pull_request_template.md) will be automatically added to the pull request description. * If the PR does not contain such a list, it **should not be reviewed** * If any of the items is not checked, the reviewer should ask for an explanation from the contributor @@ -60,5 +60,5 @@ All metadata files **must** be covered by the tests. Every metadata file should: * not have entries that describes classes that serve only for tests There are various tools that could help checking the content of all json files we are collecting. -To run these checks automatically, top-level metadata index file must contain `allowed-packages` properly set ([see this](./CONTRIBUTING.md#metadata-structure)). +To run these checks automatically, top-level metadata index file must contain `allowed-packages` properly set ([see this](CONTRIBUTING.md#metadata-structure)). If this field is properly configured, our GitHub workflows will automatically check `typeReachable` and origin of all entries in all config files. diff --git a/SECURITY.md b/docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to docs/SECURITY.md diff --git a/tests/src/org.thymeleaf.extras/thymeleaf-extras-springsecurity6/3.1.0.M1/build.gradle b/tests/src/org.thymeleaf.extras/thymeleaf-extras-springsecurity6/3.1.0.M1/build.gradle index e8728bc17..4d60de9e9 100644 --- a/tests/src/org.thymeleaf.extras/thymeleaf-extras-springsecurity6/3.1.0.M1/build.gradle +++ b/tests/src/org.thymeleaf.extras/thymeleaf-extras-springsecurity6/3.1.0.M1/build.gradle @@ -11,7 +11,7 @@ plugins { String libraryVersion = tck.testedLibraryVersion.get() tasks.withType(JavaCompile) { - options.release = 17 + options.release = 21 } dependencies { diff --git a/tests/src/org.thymeleaf/thymeleaf-spring6/3.1.0.M2/build.gradle b/tests/src/org.thymeleaf/thymeleaf-spring6/3.1.0.M2/build.gradle index e175d23ec..5a8ea8013 100644 --- a/tests/src/org.thymeleaf/thymeleaf-spring6/3.1.0.M2/build.gradle +++ b/tests/src/org.thymeleaf/thymeleaf-spring6/3.1.0.M2/build.gradle @@ -11,7 +11,7 @@ plugins { String libraryVersion = tck.testedLibraryVersion.get() tasks.withType(JavaCompile) { - options.release = 17 + options.release = 21 } dependencies { diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index d6485e38a..2b0ed2dd0 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -13,7 +13,7 @@ import groovy.json.JsonOutput import groovy.json.JsonSlurper import org.graalvm.internal.tck.ContributionTask import org.graalvm.internal.tck.DockerTask -import org.graalvm.internal.tck.ConfigFilesChecker +import org.graalvm.internal.tck.MetadataFilesCheckerTask import org.graalvm.internal.tck.DockerUtils import org.graalvm.internal.tck.ScaffoldTask import org.graalvm.internal.tck.GrypeTask @@ -21,13 +21,13 @@ import org.graalvm.internal.tck.TestedVersionUpdaterTask import org.graalvm.internal.tck.harness.tasks.TestInvocationTask import org.graalvm.internal.tck.harness.tasks.CheckstyleInvocationTask import org.graalvm.internal.tck.harness.tasks.CleanInvocationTask -import org.graalvm.internal.tck.harness.tasks.JavacInvocationTask +import org.graalvm.internal.tck.harness.tasks.CompileTestJavaInvocationTask import org.graalvm.internal.tck.harness.tasks.JavaTestInvocationTask import org.graalvm.internal.tck.harness.tasks.NativeTestCompileInvocationTask -import org.graalvm.internal.tck.updaters.FetchExistingLibrariesWithNewerVersionsTask -import org.graalvm.internal.tck.updaters.GroupUnsupportedLibraries +import org.graalvm.internal.tck.harness.tasks.FetchExistingLibrariesWithNewerVersionsTask import org.gradle.util.internal.VersionNumber -import org.graalvm.internal.tck.PullImagesFromFileTask +import org.graalvm.internal.tck.harness.tasks.ComputeAndPullAllowedDockerImagesTask +import org.graalvm.internal.tck.utils.CoordinateUtils import static org.graalvm.internal.tck.Utils.generateTaskName @@ -35,6 +35,8 @@ import static org.graalvm.internal.tck.Utils.generateTaskName logger.lifecycle("GraalVM Reachability Metadata TCK") logger.lifecycle("---------------------------------") +final String METADATA_GROUP = "Metadata" + def writeGithubOutput(String key, String value) { def path = System.getenv("GITHUB_OUTPUT") if (path == null || path.trim().isEmpty()) { @@ -46,140 +48,96 @@ def writeGithubOutput(String key, String value) { String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") -// Support fractional batching coordinates in the form "k/n" (e.g., "1/16") -static boolean isFractionalBatch(String s) { - return s != null && (s ==~ /\d+\/\d+/) -} - -static List parseFraction(String s) { - def m = s =~ /(\d+)\/(\d+)/ - if (!m.matches()) return null - int k = (m[0][1] as int) - int n = (m[0][2] as int) - return [k, n] -} - -static List computeBatchedCoordinates(List coordinates, int index, int batches) { - if (batches <= 0) throw new GradleException("Invalid batches denominator: ${batches}") - if (index < 1 || index > batches) throw new GradleException("Invalid batch index: ${index}/${batches}") - def sorted = new ArrayList(coordinates) - Collections.sort(sorted) - int target = (index - 1) - def result = [] - int i = 0 - for (String c : sorted) { - if ((i % batches) == target) { - result.add(c) - } - i++ - } - return result -} List matchingCoordinates -if (isFractionalBatch(coordinateFilter)) { - def frac = parseFraction(coordinateFilter) +if (CoordinateUtils.isFractionalBatch(coordinateFilter)) { + def frac = CoordinateUtils.parseFraction(coordinateFilter) List all = tck.getMatchingCoordinates("all") - matchingCoordinates = computeBatchedCoordinates(all, frac[0], frac[1]) + matchingCoordinates = CoordinateUtils.computeBatchedCoordinates(all, frac[0], frac[1]) } else { matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter) } +matchingCoordinates = matchingCoordinates.findAll { !it.startsWith("samples:") } -// gradle test -Pcoordinates= -Provider test = tasks.register("test", DefaultTask) { task -> - task.setDescription("Tests GraalVM Reflection Metadata that matches given coordinates") - task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) -} // gradle checkstyle -Pcoordinates= Provider checkstyle = tasks.register("checkstyle") { task -> task.setDescription("Runs checkstyle on all subprojects") task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) -} +} as Provider - // gradle javac -Pcoordinates= -Provider javac = tasks.register("javac") { task -> + // gradle compileTestJava -Pcoordinates= +Provider compileTestJava = tasks.register("compileTestJava") { task -> task.setDescription("Compiles sources (javac) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) -} +} as Provider + +// gradle javaTest -Pcoordinates= +Provider javaTest = tasks.register("javaTest") { task -> + task.setDescription("Runs JVM tests (Gradle 'test') for all subprojects") + task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) +} as Provider // gradle nativeTestCompile -Pcoordinates= Provider nativeTestCompile = tasks.register("nativeTestCompile") { task -> task.setDescription("Compiles native tests (nativeTestCompile) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) -} +} as Provider + +// gradle checkMetadataFiles -Pcoordinates= +Provider checkMetadataFiles = tasks.register("checkMetadataFiles") { task -> + task.setDescription("Checks content of metadata files for matching coordinates") + task.setGroup(METADATA_GROUP) +} as Provider - // gradle javaTest -Pcoordinates= -Provider javaTest = tasks.register("javaTest") { task -> - task.setDescription("Runs JVM tests (Gradle 'test') for all subprojects") - task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) -} tasks.named("check").configure { dependsOn(checkstyle) } -final String METADATA_GROUP = "Metadata" - - - - -// Here we want to configure all test and checkstyle tasks for all filtered subprojects -for (String coordinates in matchingCoordinates) { - String testTaskName = generateTaskName("test", coordinates) - if ((!tasks.getNames().contains(testTaskName))) { - tasks.register(testTaskName, TestInvocationTask, coordinates) - } - test.configure { - dependsOn(testTaskName) - } - - String checkstyleTaskName = generateTaskName("checkstyle", coordinates) - if ((!tasks.getNames().contains(checkstyleTaskName))) { - tasks.register(checkstyleTaskName, CheckstyleInvocationTask, coordinates) - } - checkstyle.configure { - dependsOn(checkstyleTaskName) - } - - String cleanTaskName = generateTaskName("clean", coordinates) - if ((!tasks.getNames().contains(cleanTaskName))) { - tasks.register(cleanTaskName, CleanInvocationTask, coordinates) - } - clean.configure { - dependsOn(cleanTaskName) - } - - String javacTaskName = generateTaskName("javac", coordinates) - if ((!tasks.getNames().contains(javacTaskName))) { - tasks.register(javacTaskName, JavacInvocationTask, coordinates) - } - javac.configure { - dependsOn(javacTaskName) - } - String nativeTestCompileTaskName = generateTaskName("nativeTestCompile", coordinates) - if ((!tasks.getNames().contains(nativeTestCompileTaskName))) { - tasks.register(nativeTestCompileTaskName, NativeTestCompileInvocationTask, coordinates) + // Here we want to configure all test and checkstyle tasks for all filtered subprojects +// Extracted helper to register per-coordinate tasks and wire them to aggregate tasks. +def registerPerCoordinateTask = { String coordinates, Object aggregate, String baseName, Class taskClass, boolean passCtorArg = true, Closure customConfigure = null -> + String taskName = generateTaskName(baseName, coordinates) + if ((!tasks.getNames().contains(taskName))) { + if (passCtorArg) { + tasks.register(taskName, taskClass, coordinates) + } else { + tasks.register(taskName, taskClass) { t -> + if (customConfigure != null) { + customConfigure.call(t) + } + } + } } - nativeTestCompile.configure { - dependsOn(nativeTestCompileTaskName) + if (aggregate != null) { + if (aggregate instanceof org.gradle.api.provider.Provider) { + aggregate.configure { + dependsOn(taskName) + } + } else { + tasks.named(aggregate.toString()).configure { + dependsOn(taskName) + } + } } +} - String javaTestTaskName = generateTaskName("javaTest", coordinates) - if ((!tasks.getNames().contains(javaTestTaskName))) { - tasks.register(javaTestTaskName, JavaTestInvocationTask, coordinates) +for (String coordinates in matchingCoordinates) { + registerPerCoordinateTask(coordinates, checkstyle, "checkstyle", CheckstyleInvocationTask, true) + registerPerCoordinateTask(coordinates, "clean", "clean", CleanInvocationTask, true) + registerPerCoordinateTask(coordinates, compileTestJava, "compileTestJava", CompileTestJavaInvocationTask, true) + registerPerCoordinateTask(coordinates, javaTest, "javaTest", JavaTestInvocationTask, true) + registerPerCoordinateTask(coordinates, nativeTestCompile, "nativeTestCompile", NativeTestCompileInvocationTask, true) + // No aggregate wiring for per-coordinate docker pulls; they are invoked by dedicated workflows. + registerPerCoordinateTask(coordinates, null, "pullAllowedDockerImages", DockerTask.class, false) { t -> + t.setCoordinates(coordinates) } - javaTest.configure { - dependsOn(javaTestTaskName) - } - - String pullDockerTaskName = generateTaskName("pullAllowedDockerImages", coordinates) - if ((!tasks.getNames().contains(pullDockerTaskName))) { - tasks.register(pullDockerTaskName, DockerTask.class) { t -> - t.setCoordinates(coordinates) - } + registerPerCoordinateTask(coordinates, checkMetadataFiles, "checkMetadataFiles", MetadataFilesCheckerTask.class, false) { t -> + t.setCoordinates(coordinates) } + registerPerCoordinateTask(coordinates, "test", "test", TestInvocationTask, true) } // gradle diff -PbaseCommit= -PnewCommit= @@ -335,12 +293,6 @@ tasks.register("fetchExistingLibrariesWithNewerVersions", FetchExistingLibraries task.setAllLibraryCoordinates(matchingCoordinates) } -tasks.register("groupLibrariesByName", GroupUnsupportedLibraries.class) { task -> - task.setGroup(METADATA_GROUP) - task.setDescription("Extracts groups of libraries from github comments provided in a form of string.") -} - - tasks.register("addTestedVersion", TestedVersionUpdaterTask.class) { task -> task.setDescription("Updates list of tested versions.") task.setGroup(METADATA_GROUP) @@ -352,57 +304,9 @@ tasks.register("checkAllowedDockerImages", GrypeTask.class) { task -> task.setGroup(METADATA_GROUP) } -def imagesFileProvider = project.layout.buildDirectory.file("tck/required-docker-images.txt") - -def computeImages = tasks.register("computeAllowedDockerImagesFile", DefaultTask) { task -> - task.setDescription("Computes allowed docker images required by matching coordinates and writes them to a file") - task.setGroup(METADATA_GROUP) - task.outputs.file(imagesFileProvider) - task.doFirst { - if (matchingCoordinates == null || matchingCoordinates.isEmpty()) { - throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates= or a fractional batch 'k/n'.") - } - Set unionRequired = new LinkedHashSet<>() - matchingCoordinates.each { c -> - def parts = c.split(":") - if (parts.length < 3) { - logger.warn("Skipping invalid coordinates: ${c}") - return - } - def group = parts[0] - def artifact = parts[1] - def version = parts[2] - File f = project.file("tests/src/${group}/${artifact}/${version}/required-docker-images.txt") - if (f.exists()) { - f.readLines() - .collect { it?.trim() } - .findAll { it && !it.startsWith("#") } - .each { unionRequired.add(it) } - } - } - - Set allowed = DockerUtils.getAllAllowedImages() - def notAllowed = unionRequired.findAll { !allowed.contains(it) } - if (!notAllowed.isEmpty()) { - throw new GradleException("The following images are not in the allowed list: ${notAllowed}. " + - "If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images " + - "per CONTRIBUTING.md, or adjust required-docker-images.txt files.") - } - - def finalList = unionRequired.findAll { allowed.contains(it) }.toList() - println "Collected ${finalList.size()} required allowed image(s)" - File outFile = imagesFileProvider.get().asFile - outFile.parentFile.mkdirs() - outFile.text = finalList.join(System.lineSeparator()) - } -} - -tasks.register("pullAllowedDockerImages", PullImagesFromFileTask.class) { task -> - task.setDescription("Pull allowed docker images required by matching coordinates") +tasks.register("pullAllowedDockerImages", ComputeAndPullAllowedDockerImagesTask.class) { task -> + task.setDescription("Computes and pulls allowed docker images required by matching coordinates") task.setGroup(METADATA_GROUP) - task.getImagesFile().set(imagesFileProvider) -}.configure { t -> - t.dependsOn(computeImages) } @@ -418,8 +322,3 @@ tasks.register("contribute", ContributionTask.class) { task -> task.setDescription("Generates metadata and prepares pull request for contibuting on metadata repository based on provided tests.") task.setGroup(METADATA_GROUP) } - -tasks.register("checkConfigFiles", ConfigFilesChecker.class) { task -> - task.setDescription("Checks content of config files for a new library.") - task.setGroup(METADATA_GROUP) -} diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle index 35178718b..1b903e8db 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle @@ -20,7 +20,7 @@ repositories { } tasks.withType(JavaCompile).configureEach { - options.release = 17 + options.release = 21 } checkstyle { diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index 8937516cb..d64e95c76 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java @@ -6,11 +6,6 @@ */ package org.graalvm.internal.tck.harness; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; import org.gradle.api.Project; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; @@ -18,6 +13,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.process.ExecOperations; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.ByteArrayOutputStream; @@ -28,19 +24,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.graalvm.internal.tck.Utils.coordinatesMatch; -import static org.graalvm.internal.tck.Utils.extractJsonFile; -import static org.graalvm.internal.tck.Utils.readIndexFile; -import static org.graalvm.internal.tck.Utils.splitCoordinates; +import static org.graalvm.internal.tck.Utils.*; public abstract class TckExtension { - private static final List REPO_ROOT_FILES = List.of("CONTRIBUTING.md", "metadata"); + private static final List REPO_ROOT_FILES = List.of("LICENSE", "metadata", "tests"); public abstract DirectoryProperty getRepoRoot(); @@ -50,7 +40,7 @@ public abstract class TckExtension { public abstract DirectoryProperty getTckRoot(); - public abstract Property getTestedLibraryVersion(); + public abstract Property<@NotNull String> getTestedLibraryVersion(); @Inject public abstract ExecOperations getExecOperations(); @@ -58,8 +48,13 @@ public abstract class TckExtension { public TckExtension(Project project) { getRepoRoot().value(project.getObjects().directoryProperty().value(project.getLayout().getProjectDirectory()).map(dir -> { Directory current = dir; + var level = 0; + int maxLevel = 50; while (!isRootDir(current)) { current = current.dir(".."); + if (level++ == maxLevel) { + throw new RuntimeException("Could not detect root dir at level " + maxLevel); + } } return current; })).finalizeValueOnRead(); @@ -72,7 +67,7 @@ private static boolean isRootDir(Directory dir) { return REPO_ROOT_FILES.stream().allMatch(fileName -> dir.file(fileName).getAsFile().exists()); } - private static Path toPath(Provider provider) { + private static Path toPath(Provider provider) { return provider.get().getAsFile().toPath(); } @@ -96,7 +91,7 @@ private Path testRoot() { * Given full coordinates returns matching test directory */ @SuppressWarnings("unchecked") - Path getTestDir(String coordinates) { + public Path getTestDir(String coordinates) { List strings = splitCoordinates(coordinates); String groupId = strings.get(0); String artifactId = strings.get(1); @@ -110,7 +105,7 @@ Path getTestDir(String coordinates) { for (Map entry : index) { boolean found = ((List>) entry.get("libraries")).stream().anyMatch( lib -> coordinatesMatch((String) lib.get("name"), groupId, artifactId) && - ((List) lib.get("versions")).contains(version) + ((List) lib.get("versions")).contains(version) ); if (found) { return testRoot().resolve((String) entry.get("test-project-path")); @@ -166,10 +161,8 @@ List diffCoordinates(String baseCommit, String newCommit) { return true; } Path testDir = getTestDir(c); - if (changed.get("test") != null && changed.get("test").stream().anyMatch(f -> f.startsWith(testDir))) { - return true; - } - return false; + return changed.get("test") != null && + changed.get("test").stream().anyMatch(f -> f.startsWith(testDir)); }).distinct().collect(Collectors.toList()); // if we detected changes in repo, but not in metadata/index.json file, we should throw an exception @@ -194,10 +187,11 @@ List diffCoordinates(String baseCommit, String newCommit) { private boolean metadataIndexContainsChangedEntries(Set changedCoordinates, List changedEntries) { boolean containsAll = true; for (var n : changedEntries) { - boolean containsCurrent =false; + boolean containsCurrent = false; for (var c : changedCoordinates) { - if (n.startsWith(c.replace(":", "/"))){ + if (n.startsWith(c.replace(":", "/"))) { containsCurrent = true; + break; } } @@ -249,7 +243,7 @@ Set getMatchingMetadataDirs(String groupId, String artifactId) { * @return path to metadata directory */ @SuppressWarnings("unchecked") - Path getMetadataDir(String coordinates) { + public Path getMetadataDir(String coordinates) { List strings = splitCoordinates(coordinates); String groupId = strings.get(0); String artifactId = strings.get(1); @@ -279,7 +273,7 @@ Path getMetadataDir(String coordinates) { * @return list of all coordinates that */ @SuppressWarnings("unchecked") - List getMatchingCoordinates(String coordinateFilter) { + public List getMatchingCoordinates(String coordinateFilter) { List strings = splitCoordinates(coordinateFilter); String groupId = strings.get(0); String artifactId = strings.get(1); @@ -313,11 +307,10 @@ List getMatchingCoordinates(String coordinateFilter) { /** * Returns a list of metadata files in a given directory. * - * @param directory * @return list of json files contained in it */ @SuppressWarnings("unchecked") - List getMetadataFileList(Path directory) throws IOException { + public List getMetadataFileList(Path directory) throws IOException { List foundFiles = new ArrayList<>(); try (Stream paths = Files.walk(directory)) { paths.filter(Files::isRegularFile) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy index 8940a5ca4..355623216 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy @@ -110,7 +110,6 @@ abstract class AbstractSubprojectTask extends DefaultTask { /** * Given project dir returns a tuple that contains a list of inputs. - * @param projectDir * @return lists of input files */ @Internal diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy index 745537207..f8452ea0b 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy @@ -1,9 +1,17 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + package org.graalvm.internal.tck.harness.tasks import org.gradle.api.tasks.Input import javax.inject.Inject +@SuppressWarnings('unused') abstract class CleanInvocationTask extends AbstractSubprojectTask { @Inject diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavacInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy similarity index 84% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavacInvocationTask.groovy rename to tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy index 63434e2eb..2ca7bc355 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavacInvocationTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy @@ -14,10 +14,10 @@ import javax.inject.Inject * Task that is used to compile subprojects with javac. */ @SuppressWarnings("unused") -abstract class JavacInvocationTask extends AbstractSubprojectTask { +abstract class CompileTestJavaInvocationTask extends AbstractSubprojectTask { @Inject - JavacInvocationTask(String coordinates) { + CompileTestJavaInvocationTask(String coordinates) { super(coordinates) } diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ComputeAndPullAllowedDockerImagesTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ComputeAndPullAllowedDockerImagesTask.java new file mode 100644 index 000000000..b0318f853 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ComputeAndPullAllowedDockerImagesTask.java @@ -0,0 +1,138 @@ +package org.graalvm.internal.tck.harness.tasks; + +import org.graalvm.internal.tck.DockerUtils; +import org.graalvm.internal.tck.harness.TckExtension; +import org.graalvm.internal.tck.utils.CoordinateUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.options.Option; +import org.gradle.process.ExecOperations; +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.util.*; + +/** + * Single task that: + * 1) Computes the union of required docker images across selected coordinates (supports fractional batching k/n) + * 2) Validates them against the allowed-docker-images list + * 3) Writes them to the provided output file + * 4) Immediately pulls those images + *

+ * Coordinates can be provided via: + * - -Pcoordinates= (preferred) + * - --coordinates= + * The filter can be group:artifact:version or a fractional batch in the form k/n (e.g., 1/16), or 'all'. + */ +public abstract class ComputeAndPullAllowedDockerImagesTask extends DefaultTask { + + @Input + @Optional + public abstract Property<@NotNull String> getCoordinates(); + + @Option(option = "coordinates", description = "Coordinate filter (group[:artifact[:version]] or k/n fractional batch)") + public void setCoordinatesOption(String value) { + getCoordinates().set(value); + } + + @Inject + protected abstract ExecOperations getExecOperations(); + + protected String effectiveCoordinateFilter() { + // Prefer task option, fallback to -Pcoordinates, then empty string (all) + String opt = getCoordinates().getOrNull(); + if (opt != null) { + return opt; + } + Object prop = getProject().findProperty("coordinates"); + return prop == null ? "" : prop.toString(); + } + + @TaskAction + public void run() throws IOException { + TckExtension tck = Objects.requireNonNull(getProject().getExtensions().findByType(TckExtension.class)); + + // Resolve coordinates + String filter = effectiveCoordinateFilter(); + + List matching; + if (CoordinateUtils.isFractionalBatch(filter)) { + int[] frac = CoordinateUtils.parseFraction(filter); + assert frac != null : "Already checked"; + List all = tck.getMatchingCoordinates("all"); + matching = CoordinateUtils.computeBatchedCoordinates(all, frac[0], frac[1]); + } else { + matching = tck.getMatchingCoordinates(filter); + } + + if (matching == null || matching.isEmpty()) { + throw new GradleException("No matching coordinates found. Provide --coordinates= (preferred) or -Pcoordinates=, or a fractional batch 'k/n'."); + } + + // Collect union of required docker images + Set requiredImages = new LinkedHashSet<>(); + for (String c : matching) { + String[] parts = c.split(":"); + if (parts.length < 3) { + throw new GradleException("Invalid coordinates: " + c); + } + String group = parts[0]; + String artifact = parts[1]; + String version = parts[2]; + File f = getProject().file("tests/src/" + group + "/" + artifact + "/" + version + "/required-docker-images.txt"); + if (f.exists()) { + Files.readAllLines(f.toPath()).stream() + .map(String::trim) + .filter(s -> !s.isEmpty()) + .filter(s -> !s.startsWith("#")) + .forEach(requiredImages::add); + } + } + + // Validate against allowed images + validateRequiredImages(requiredImages); + + // Pull images immediately + for (String image : requiredImages) { + getLogger().lifecycle("Pulling docker image {}", image); + try { + getExecOperations().exec(spec -> { + spec.setExecutable("docker"); + spec.args("pull", image); + }); + } catch (Exception e) { + throw new GradleException("Failed to pull image " + image + ": " + e.getMessage(), e); + } + } + + if (requiredImages.isEmpty()) { + getLogger().lifecycle("No required docker images found for coordinates filter '{}'. If your tests use docker, please read: {}", filter, + URI.create("https://github.com/oracle/graalvm-reachability-metadata/blob/master/CONTRIBUTING.md#providing-the-tests-that-use-docker")); + } + } + + private static void validateRequiredImages(Set requiredImages) { + Set allowed = DockerUtils.getAllAllowedImages(); + List notAllowed = new ArrayList<>(); + for (String img : requiredImages) { + if (!allowed.contains(img)) { + notAllowed.add(img); + } + } + if (!notAllowed.isEmpty()) { + throw new GradleException(""" + The following images are not in the allowed list: %s. \ + If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images \ + per docs/CONTRIBUTING.md, or adjust required-docker-images.txt files. + """.formatted(notAllowed)); + } + } +} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/FetchExistingLibrariesWithNewerVersionsTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy similarity index 97% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/FetchExistingLibrariesWithNewerVersionsTask.groovy rename to tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy index c230a5693..4982b9610 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/FetchExistingLibrariesWithNewerVersionsTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy @@ -1,4 +1,4 @@ -package org.graalvm.internal.tck.updaters +package org.graalvm.internal.tck.harness.tasks import com.fasterxml.jackson.annotation.JsonInclude @@ -9,16 +9,14 @@ import groovy.json.JsonOutput import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry import org.gradle.api.DefaultTask import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.options.Option import org.gradle.util.internal.VersionNumber import java.util.regex.Matcher import java.util.regex.Pattern - +@SuppressWarnings("unused") abstract class FetchExistingLibrariesWithNewerVersionsTask extends DefaultTask { @Input diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TeeOutputStream.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TeeOutputStream.java index dd296db7e..ffc6273b6 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TeeOutputStream.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TeeOutputStream.java @@ -7,6 +7,8 @@ package org.graalvm.internal.tck.harness.tasks; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.io.OutputStream; @@ -26,13 +28,13 @@ public void write(int b) throws IOException { } @Override - public void write(byte[] b) throws IOException { + public void write(byte @NotNull [] b) throws IOException { out1.write(b); out2.write(b); } @Override - public void write(byte[] b, int off, int len) throws IOException { + public void write(byte @NotNull [] b, int off, int len) throws IOException { out1.write(b, off, len); out2.write(b, off, len); } diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/GroupUnsupportedLibraries.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/GroupUnsupportedLibraries.groovy deleted file mode 100644 index edf3874f7..000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/updaters/GroupUnsupportedLibraries.groovy +++ /dev/null @@ -1,53 +0,0 @@ -package org.graalvm.internal.tck.updaters - -import org.gradle.api.DefaultTask -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.options.Option -import org.gradle.util.internal.VersionNumber - -abstract class GroupUnsupportedLibraries extends DefaultTask { - - @Input - @Option(option = "libraries", description = "Provides list of libraries that should be grouped.") - abstract Property getLibraries() - - @TaskAction - void action() { - def libraries = getLibraries().get().split("\n") - - Map> libraryGroups = new HashMap>() - for (def library : libraries) { - def coordinatesPart = library.split("_") - def artifactKey = coordinatesPart[0] + ":" + coordinatesPart[1] - def version = coordinatesPart[2] - - if (libraryGroups.get(artifactKey) == null) { - libraryGroups.put(artifactKey, new ArrayList()) - } - - libraryGroups.get(artifactKey).add(version) - libraryGroups.get(artifactKey).sort(Comparator.comparing(VersionNumber::parse)) - } - - print generateGroupedComment(libraryGroups) - } - - private static String generateGroupedComment(Map> groups) { - def sb = new StringBuilder("List of all unsupported libraries:\n") - - for (String library : groups.keySet()) { - sb.append("- ").append(library).append(":\n") - - for (String version : groups.get(library)) { - sb.append("\t").append("- ").append(version).append("\n") - } - - sb.append("\n") - } - - return sb.toString() - } - -} diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ContributionTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ContributionTask.java index c0b76b306..dbb687b1a 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ContributionTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ContributionTask.java @@ -10,6 +10,7 @@ import org.graalvm.internal.tck.model.MetadataIndexEntry; import org.graalvm.internal.tck.model.contributing.Question; import org.graalvm.internal.tck.utils.ConfigurationStringBuilder; +import org.graalvm.internal.tck.utils.CoordinateUtils; import org.graalvm.internal.tck.utils.FilesUtils; import org.graalvm.internal.tck.utils.InteractiveTaskUtils; import org.gradle.api.DefaultTask; diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/CoordinateUtils.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/CoordinateUtils.java deleted file mode 100644 index e71173e06..000000000 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/CoordinateUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.graalvm.internal.tck; - - -public abstract class CoordinateUtils { - public static String replace(String template, Coordinates coordinates) { - return template - .replace("$group$", coordinates.group()) - .replace("$sanitizedGroup$", coordinates.sanitizedGroup()) - .replace("$artifact$", coordinates.artifact()) - .replace("$sanitizedArtifact$", coordinates.sanitizedArtifact()) - .replace("$capitalizedSanitizedArtifact$", coordinates.capitalizedSanitizedArtifact()) - .replace("$version$", coordinates.version()); - } - - public static Coordinates fromString(String coordinates) throws IllegalArgumentException { - String[] coordinatesParts = coordinates.split(":"); - if (coordinatesParts.length != 3) { - throw new IllegalArgumentException("Maven coordinates not provided in the correct format."); - } - - String group = coordinatesParts[0]; - String artifact = coordinatesParts[1]; - String version = coordinatesParts[2]; - return new Coordinates(group, artifact, version); - } - -} diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/Coordinates.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/Coordinates.java index 83e90604b..e14a421ee 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/Coordinates.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/Coordinates.java @@ -1,16 +1,18 @@ package org.graalvm.internal.tck; +import org.jetbrains.annotations.NotNull; + import java.util.Set; /** * Dependency coordinates in the form 'group:artifact:version'. */ -record Coordinates(String group, String artifact, String version) { +public record Coordinates(String group, String artifact, String version) { private static final Set FORBIDDEN_CHARS = Set.of( ':', '-', '.' ); - Coordinates { + public Coordinates { if (group == null || group.isEmpty()) { throw new IllegalArgumentException("group must not be empty"); } @@ -22,15 +24,15 @@ record Coordinates(String group, String artifact, String version) { } } - String sanitizedGroup() { + public String sanitizedGroup() { return sanitize(group); } - String sanitizedArtifact() { + public String sanitizedArtifact() { return sanitize(artifact); } - String capitalizedSanitizedArtifact() { + public String capitalizedSanitizedArtifact() { String sanitizedArtifact = sanitizedArtifact(); if (sanitizedArtifact.isEmpty()) { return sanitizedArtifact; @@ -39,7 +41,7 @@ String capitalizedSanitizedArtifact() { } @Override - public String toString() { + public @NotNull String toString() { return group + ":" + artifact + ":" + version; } diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/DockerTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/DockerTask.java index b7321bd4f..ad07c5db9 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/DockerTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/DockerTask.java @@ -1,5 +1,6 @@ package org.graalvm.internal.tck; +import org.graalvm.internal.tck.utils.CoordinateUtils; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; @@ -20,6 +21,7 @@ import static org.graalvm.internal.tck.DockerUtils.getAllAllowedImages; +@SuppressWarnings("unused") public abstract class DockerTask extends DefaultTask { @InputFiles diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/GrypeTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/GrypeTask.java index 3d53fd987..8a6ffab66 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/GrypeTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/GrypeTask.java @@ -21,7 +21,7 @@ import java.util.*; import java.util.stream.Collectors; - +@SuppressWarnings("unused") public abstract class GrypeTask extends DefaultTask { @Inject @@ -181,13 +181,13 @@ private Vulnerabilities getVulnerabilities(String image) throws IOException { * Get all docker images introduced between two commits */ private Set getChangedImages() throws IOException, URISyntaxException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); getExecOperations().exec(spec -> { - spec.setStandardOutput(baos); + spec.setStandardOutput(outputStream); spec.commandLine("git", "diff", "--name-only", "--diff-filter=ACMRT", baseCommit, newCommit); }); - String output = baos.toString(StandardCharsets.UTF_8); + String output = outputStream.toString(StandardCharsets.UTF_8); List diffFiles = Arrays.stream(output.split("\\r?\\n")) .filter(path -> path.contains(DOCKERFILE_DIRECTORY)) .map(path -> path.substring(path.lastIndexOf("/") + 1)) diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ConfigFilesChecker.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java similarity index 92% rename from tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ConfigFilesChecker.java rename to tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java index b68e4e1ec..db2302d4e 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ConfigFilesChecker.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java @@ -1,30 +1,25 @@ package org.graalvm.internal.tck; import groovy.json.JsonSlurper; +import org.graalvm.internal.tck.utils.CoordinateUtils; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; -import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; -import java.util.Arrays; -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; -import java.util.Set; -import java.util.HashSet; +import java.util.*; import java.util.stream.Collectors; /** * Checks content of config files for a new library. *

- * Run with {@code gradle checkConfigFiles --coordinates com.example:library:1.0.0}. + * Run with {@code gradle checkMetadataFiles -Pcoordinates com.example:library:1.0.0}. */ -public abstract class ConfigFilesChecker extends DefaultTask { +@SuppressWarnings("unused") +public abstract class MetadataFilesCheckerTask extends DefaultTask { @InputFiles protected abstract RegularFileProperty getMetadataRoot(); @@ -32,12 +27,17 @@ public abstract class ConfigFilesChecker extends DefaultTask { @InputFiles protected abstract RegularFileProperty getIndexFile(); - private final Set EXPECTED_FILES = new HashSet<>(Arrays.asList("index.json", "reflect-config.json", "resource-config.json", "serialization-config.json", - "jni-config.json", "proxy-config.json", "predefined-classes-config.json")); + private final Set EXPECTED_FILES = new HashSet<>(List.of( + "index.json", + "reflect-config.json", + "resource-config.json", + "serialization-config.json", + "jni-config.json", + "proxy-config.json")); private final Set ILLEGAL_TYPE_VALUES = new HashSet<>(List.of("java.lang")); - private final Set PREDEFINED_ALLOWED_PACKAGES = new HashSet<>(Arrays.asList("java.lang", "java.util")); + private final Set PREDEFINED_ALLOWED_PACKAGES = new HashSet<>(List.of("java.lang", "java.util")); Coordinates coordinates; List allowedPackages; @@ -98,7 +98,7 @@ void run() throws IllegalArgumentException, FileNotFoundException { private List getConfigFilesForMetadataDir(File root) throws RuntimeException { List files = new ArrayList<>(); - File [] content = root.listFiles(); + File[] content = root.listFiles(); if (content == null) { throw new RuntimeException("ERROR: Failed to load content of " + root.toURI()); @@ -121,7 +121,7 @@ private List> getConfigEntries(File file) { return ((List) new JsonSlurper() .parse(file)) .stream() - .map(e -> (Map)e) + .map(e -> (Map) e) .collect(Collectors.toList()); } @@ -134,7 +134,7 @@ private Map getConfigEntry(File file) { private boolean reflectConfigFileContainsErrors(File file) { List> entries = getConfigEntries(file); - if (entries.size() == 0) { + if (entries.isEmpty()) { System.out.println("ERROR: empty reflect-config detected: " + file.toURI()); return true; } @@ -159,7 +159,7 @@ private boolean resourceConfigFileContainsErrors(File file) { List> includes = (List>) resources.get("includes"); List> excludes = (List>) resources.get("excludes"); - if (listNullOrEmpty(includes) && listNullOrEmpty(excludes) && listNullOrEmpty(bundles)){ + if (listNullOrEmpty(includes) && listNullOrEmpty(excludes) && listNullOrEmpty(bundles)) { System.out.println("ERROR: empty resource-config detected: " + file.toURI()); return true; } @@ -189,7 +189,7 @@ private boolean resourceConfigFileContainsErrors(File file) { private boolean checkOldSerializationConfig(File file) { List> entries = getConfigEntries(file); - + if (entries.isEmpty()) { System.out.println("ERROR: empty serialization-config detected: " + file.toURI()); return true; @@ -248,7 +248,7 @@ private boolean serializationConfigFileContainsErrors(File file) throws FileNotF private boolean proxyConfigFileContainsErrors(File file) { List> entries = getConfigEntries(file); - if (entries.size() == 0) { + if (entries.isEmpty()) { System.out.println("ERROR: empty proxy-config detected: " + file.toURI()); return true; } @@ -265,7 +265,7 @@ private boolean proxyConfigFileContainsErrors(File file) { private boolean jniConfigFileContainsErrors(File file) { List> entries = getConfigEntries(file); - if (entries.size() == 0) { + if (entries.isEmpty()) { System.out.println("ERROR: empty jni-config detected: " + file.toURI()); return true; } @@ -300,7 +300,6 @@ private boolean containsDuplicatedEntries(List> entries, Fil return containsDuplicates; } - @SuppressWarnings("unchecked") private boolean checkTypeReachable(Map entry, File file) { String typeReachable = getEntryTypeReachable(entry); String entryName = getEntryName(entry); @@ -366,15 +365,15 @@ private String getEntryTypeReachable(Map entry) { return null; } - return (String) condition.get("typeReachable"); + return (String) condition.get("typeReachable"); } private String getEntryName(Map entry) { - return (String) entry.get("name"); + return (String) entry.get("name"); } - private boolean listNullOrEmpty(List list) { - return list == null || list.size() == 0; + private boolean listNullOrEmpty(List list) { + return list == null || list.isEmpty(); } @SuppressWarnings("unchecked") diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java deleted file mode 100644 index 7b2d84751..000000000 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/PullImagesFromFileTask.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.graalvm.internal.tck; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.TaskAction; -import org.gradle.process.ExecOperations; -import org.gradle.api.GradleException; - -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -/** - * Pulls docker images listed in a text file (one image per line). - * Fails the task immediately if any image pull fails to ensure batch execution fails on single error. - */ -public abstract class PullImagesFromFileTask extends DefaultTask { - - @InputFile - public abstract RegularFileProperty getImagesFile(); - - @Inject - protected abstract ExecOperations getExecOperations(); - - @TaskAction - public void run() throws IOException { - File inFile = getImagesFile().get().getAsFile(); - if (!inFile.exists()) { - getLogger().lifecycle("No required docker images collected: {}", inFile.getAbsolutePath()); - return; - } - - List images = Files.readAllLines(inFile.toPath()).stream() - .map(String::trim) - .filter(s -> !s.isEmpty() && !s.startsWith("#")) - .toList(); - - for (String image : images) { - getLogger().lifecycle("Pulling image {}...", image); - try { - getExecOperations().exec(spec -> { - spec.setExecutable("docker"); - spec.args("pull", image); - }); - } catch (Exception e) { - throw new GradleException("Failed to pull image " + image + ": " + e.getMessage(), e); - } - } - } -} diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ScaffoldTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ScaffoldTask.java index 7eba4e8f8..3014e2482 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ScaffoldTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/ScaffoldTask.java @@ -7,6 +7,7 @@ import org.graalvm.internal.tck.model.MetadataIndexEntry; import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; import org.graalvm.internal.tck.model.TestIndexEntry; +import org.graalvm.internal.tck.utils.CoordinateUtils; import org.gradle.api.DefaultTask; import org.gradle.api.logging.LogLevel; import org.gradle.api.tasks.Input; @@ -30,6 +31,7 @@ * * @author Moritz Halbritter */ +@SuppressWarnings("unused") class ScaffoldTask extends DefaultTask { private final ObjectMapper objectMapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT) @@ -333,5 +335,3 @@ private void writeToFile(Path path, String content) throws IOException { Files.writeString(path, content, StandardCharsets.UTF_8); } } - - diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java index 1ec9a7346..5100d6b17 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; +import org.graalvm.internal.tck.utils.CoordinateUtils; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; @@ -16,6 +17,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; import org.gradle.util.internal.VersionNumber; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -23,6 +25,7 @@ import java.util.Comparator; import java.util.List; +@SuppressWarnings("unused") public abstract class TestedVersionUpdaterTask extends DefaultTask { @Option(option = "coordinates", description = "GAV coordinates of the library") @@ -42,10 +45,10 @@ void extractInformationFromCoordinates(String c) { @Input @Option(option = "lastSupportedVersion", description = "Last version of the library that passed tests") - protected abstract Property getLastSupportedVersion(); + protected abstract Property<@NotNull String> getLastSupportedVersion(); @Input - protected abstract Property getNewVersion(); + protected abstract Property<@NotNull String> getNewVersion(); @OutputFiles protected abstract RegularFileProperty getIndexFile(); diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/utils/CoordinateUtils.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/utils/CoordinateUtils.java new file mode 100644 index 000000000..fc1c49368 --- /dev/null +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/utils/CoordinateUtils.java @@ -0,0 +1,95 @@ +package org.graalvm.internal.tck.utils; + +import org.graalvm.internal.tck.Coordinates; +import org.gradle.api.GradleException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class CoordinateUtils { + public static String replace(String template, Coordinates coordinates) { + return template + .replace("$group$", coordinates.group()) + .replace("$sanitizedGroup$", coordinates.sanitizedGroup()) + .replace("$artifact$", coordinates.artifact()) + .replace("$sanitizedArtifact$", coordinates.sanitizedArtifact()) + .replace("$capitalizedSanitizedArtifact$", coordinates.capitalizedSanitizedArtifact()) + .replace("$version$", coordinates.version()); + } + + public static Coordinates fromString(String coordinates) throws IllegalArgumentException { + String[] coordinatesParts = coordinates.split(":"); + if (coordinatesParts.length != 3) { + throw new IllegalArgumentException("Maven coordinates not provided in the correct format."); + } + + String group = coordinatesParts[0]; + String artifact = coordinatesParts[1]; + String version = coordinatesParts[2]; + return new Coordinates(group, artifact, version); + } + + // Fractional batching utilities moved from CoordinateUtils + + private static final Pattern FRACTIONAL = Pattern.compile("(\\d+)/(\\d+)"); + + /** + * Returns true if the provided string matches the fractional batch form "k/n". + */ + public static boolean isFractionalBatch(String s) { + if (s == null) return false; + return FRACTIONAL.matcher(s).matches(); + } + + /** + * Parses a fractional batch "k/n" into an int[]{k, n}. + * Returns null if the string does not match the pattern. + * Throws GradleException on invalid values. + */ + public static int[] parseFraction(String s) { + Matcher m = FRACTIONAL.matcher(s); + if (!m.matches()) { + return null; + } + int k = Integer.parseInt(m.group(1)); + int n = Integer.parseInt(m.group(2)); + if (k <= 0) { + throw new GradleException("Cannot have a batch number less than 1"); + } + if (n <= 0) { + throw new GradleException("Cannot have a batch size less than 1"); + } + if (k > n) { + throw new GradleException("Cannot have a batch number larger than the batch size"); + } + return new int[]{k, n}; + } + + /** + * Given a list of coordinates, returns the k-th batch (1-based) out of n batches, + * by sorting and selecting every n-th element starting from index (k-1). + */ + public static List computeBatchedCoordinates(List coordinates, int index, int batches) { + if (batches <= 0) { + throw new GradleException("Invalid batches denominator: " + batches); + } + if (index < 1 || index > batches) { + throw new GradleException("Invalid batch index: " + index + "/" + batches); + } + List sorted = new ArrayList<>(coordinates); + Collections.sort(sorted); + int target = (index - 1); + List result = new ArrayList<>(); + int i = 0; + for (String c : sorted) { + if ((i % batches) == target) { + result.add(c); + } + i++; + } + return result; + } +} From aaf3d18b40cc5d09a3caef4d5eca48533d1cb783 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 20 Nov 2025 05:21:49 +0100 Subject: [PATCH 9/9] Abandon JDK 17-doesn't work for new libs --- ci.json | 10 +++++----- .../kotlin-reflect/1.7.10/build.gradle | 2 +- .../kotlin-stdlib/1.7.10/build.gradle | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci.json b/ci.json index 9e50fe296..0af01f0b1 100644 --- a/ci.json +++ b/ci.json @@ -1,19 +1,19 @@ { - "buildArgs": ["-verbose", "-Ob"], + "buildArgs": ["-verbose", "-Ob", "-H:+ReportExceptionStackTraces"], "generateMatrixBatchedCoordinates": { - "java": ["17", "21", "25", "latest-ea"], + "java": ["21", "25", "latest-ea"], "os": ["ubuntu-latest"] }, "generateChangedCoordinatesMatrix": { - "java": ["17", "21", "25", "latest-ea"], + "java": ["21", "25", "latest-ea"], "os": ["ubuntu-latest"] }, "generateInfrastructureChangedCoordinatesMatrix": { - "java": ["17", "21", "25", "latest-ea"], + "java": ["21", "25", "latest-ea"], "os": ["ubuntu-latest"] }, "generateMatrixMatchingCoordinates": { - "java": ["17", "latest-ea"], + "java": ["21", "25","latest-ea"], "os": ["ubuntu-latest"] } } diff --git a/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/build.gradle b/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/build.gradle index ef291d685..c7428d0fe 100644 --- a/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/build.gradle +++ b/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/build.gradle @@ -17,7 +17,7 @@ dependencies { } compileTestKotlin { - kotlinOptions.jvmTarget = "17" + kotlinOptions.jvmTarget = "21" } graalvmNative { diff --git a/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/build.gradle b/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/build.gradle index 9aeda18a0..154997eb5 100644 --- a/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/build.gradle +++ b/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/build.gradle @@ -20,7 +20,7 @@ dependencies { } compileTestKotlin { - kotlinOptions.jvmTarget = "17" + kotlinOptions.jvmTarget = "21" } graalvmNative {