Skip to content

Commit 6d740fe

Browse files
authored
Scan new versions for existing libraries (#552)
* Scan new versions for existing libraries * Remove redudant changes left from testing * Fix function call after refactoring * Fix task registration after refactoring * Reduce number of jobs to pass github limitations * Use simple matrix * Use env variable to specify library version * Find update entry based on the latest supported version * Use jackson to parse index file when fetching latest version * Remove unused import * Improve workflow steps titles * Remove suppress warnings after refactoring * Extract github limitations as parameter of the gradle task * Remove unused suppress * Use different PR branch name * Use bash instead of sh to invoke push script * Add comments into the tryPush script * Always extract coordinates part in the same way * Extract gradle task for fetching newer versions * Properly add Input and Output anotations to the updater task * Rename function that extracts information from provided coordinates * Use abstract getters for properties in the fetching task * Add a doc file that explains how the scan works
1 parent 8a3a3d2 commit 6d740fe

File tree

9 files changed

+492
-12
lines changed

9 files changed

+492
-12
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
name: "Check new library versions"
2+
3+
# The workflow runs bi-weekly alternating with the scheduled release workflow. This way we have enough time to provide metadata for failing tests.
4+
# In case we need more scans, there is a possibility to trigger the workflow manually.
5+
on:
6+
schedule:
7+
- cron: "0 0 8 * *"
8+
- cron: "0 0 22 * *"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write
13+
actions: write
14+
15+
concurrency:
16+
group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}"
17+
cancel-in-progress: true
18+
19+
jobs:
20+
get-all-libraries:
21+
if: github.repository == 'oracle/graalvm-reachability-metadata'
22+
name: "📋 Get list of all supported libraries with newer versions"
23+
permissions: write-all
24+
runs-on: "ubuntu-20.04"
25+
timeout-minutes: 5
26+
env:
27+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
outputs:
29+
matrix: ${{ steps.set-matrix.outputs.matrix }}
30+
issue: ${{ steps.set-issue.outputs.issue }}
31+
steps:
32+
- name: "☁️ Checkout repository"
33+
uses: actions/checkout@v4
34+
- name: "🔧 Prepare environment"
35+
uses: graalvm/setup-graalvm@v1
36+
with:
37+
java-version: '21'
38+
distribution: 'graalvm'
39+
github-token: ${{ secrets.GITHUB_TOKEN }}
40+
- name: "🕸️ Populate matrix"
41+
id: set-matrix
42+
run: |
43+
./gradlew fetchExistingLibrariesWithNewerVersions --matrixLimit=200
44+
- name: "🔨 Create branch"
45+
run: |
46+
git config --local user.email "actions@github.com"
47+
git config --local user.name "Github Actions"
48+
git switch -C check-new-library-versions/$(date '+%Y-%m-%d')
49+
git push origin check-new-library-versions/$(date '+%Y-%m-%d')
50+
- name: "🔨 Create issue"
51+
id: set-issue
52+
run: |
53+
git config --local user.email "actions@github.com"
54+
git config --local user.name "Github Actions"
55+
56+
issue_url=$(gh issue create --title "List unsupported library versions" --body "This issue lists unsupported versions of the existing libraries in the repo")
57+
echo "::set-output name=issue::$issue_url"
58+
59+
test-all-metadata:
60+
name: "🧪 ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})"
61+
permissions: write-all
62+
runs-on: ${{ matrix.os }}
63+
timeout-minutes: 20
64+
env:
65+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66+
needs: get-all-libraries
67+
strategy:
68+
fail-fast: false
69+
matrix: ${{ fromJson(needs.get-all-libraries.outputs.matrix) }}
70+
steps:
71+
- name: "☁️ Checkout repository"
72+
uses: actions/checkout@v4
73+
- name: "🔧 Setup java"
74+
uses: actions/setup-java@v4
75+
with:
76+
distribution: 'oracle'
77+
java-version: '21'
78+
- name: "🔧 Prepare environment"
79+
uses: graalvm/setup-graalvm@v1
80+
with:
81+
set-java-home: 'false'
82+
java-version: ${{ matrix.version }}
83+
distribution: 'graalvm'
84+
github-token: ${{ secrets.GITHUB_TOKEN }}
85+
native-image-job-reports: 'true'
86+
- name: "Extract test path and library version"
87+
run: |
88+
LIBRARY_PATH=$(echo ${{ matrix.coordinates }} | cut -d ':' -f1-2 | sed 's/:/\//g')
89+
LATEST_VERSION=$(find tests/src/$LIBRARY_PATH/* -maxdepth 1 -type d | sort -V | tail -1 | cut -d '/' -f5)
90+
TEST_PATH="$LIBRARY_PATH/$LATEST_VERSION"
91+
TEST_COORDINATES=$(echo "$TEST_PATH" | tr / :)
92+
93+
echo "LATEST_VERSION=$LATEST_VERSION" >> ${GITHUB_ENV}
94+
echo "TEST_PATH=$TEST_PATH" >> ${GITHUB_ENV}
95+
echo "TEST_COORDINATES=$TEST_COORDINATES" >> ${GITHUB_ENV}
96+
- name: "Pull allowed docker images"
97+
run: |
98+
./gradlew pullAllowedDockerImages --coordinates=${{ env.TEST_COORDINATES }}
99+
- name: "Disable docker"
100+
run: |
101+
sudo apt-get install openbsd-inetd
102+
sudo bash -c "cat ./.github/workflows/discard-port.conf >> /etc/inetd.conf"
103+
sudo systemctl start inetd
104+
sudo mkdir /etc/systemd/system/docker.service.d
105+
sudo bash -c "cat ./.github/workflows/dockerd.service > /etc/systemd/system/docker.service.d/http-proxy.conf"
106+
sudo systemctl daemon-reload
107+
sudo systemctl restart docker
108+
- name: "🧪 Run '${{ env.TEST_COORDINATES }}' tests"
109+
run: |
110+
TESTING_VERSION=$(echo ${{ matrix.coordinates }} | cut -d ":" -f3)
111+
export GVM_TCK_LV=$TESTING_VERSION
112+
113+
./gradlew test -Pcoordinates=${{ env.TEST_COORDINATES }}
114+
- name: "✔️ New library is supported"
115+
if: success()
116+
run: |
117+
bash ./.github/workflows/tryPushVersionsUpdate.sh ${{ matrix.coordinates }} ${{ env.LATEST_VERSION }}
118+
- name: "❗ New library is not supported"
119+
if: failure()
120+
run: |
121+
git config --local user.email "actions@github.com"
122+
git config --local user.name "Github Actions"
123+
gh issue comment "${{ needs.get-all-libraries.outputs.issue }}" --body "${{ matrix.coordinates }}"
124+
125+
process-results:
126+
name: "🧪 Process results"
127+
runs-on: "ubuntu-20.04"
128+
if: ${{ always() }}
129+
needs:
130+
- get-all-libraries
131+
- test-all-metadata
132+
permissions: write-all
133+
env:
134+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135+
steps:
136+
- name: "☁️ Checkout repository"
137+
uses: actions/checkout@v4
138+
- name: "✏️ PR for supported versions"
139+
run: |
140+
git config --local user.email "actions@github.com"
141+
git config --local user.name "Github Actions"
142+
git fetch origin check-new-library-versions/$(date '+%Y-%m-%d')
143+
git checkout check-new-library-versions/$(date '+%Y-%m-%d')
144+
gh pr create --title "Update supported library versions" --body "This pull request updates supported versions of the existing libraries in the repo"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
:' This script tries to run addTestedVersion gradle task which adds new version in the tested-versions list of the proper index.json file.
3+
Since the script could be executed from multiple parallel jobs, we want to avoid two things here: overwriting of previous changes and merge conflicts.
4+
To prevent overwriting of changes that some job already created, we only push changes from the current job if we are 0 commits behind the origin branch.
5+
Once that is achieved, we can try to push changes.
6+
If the push was rejected because of a merge conflict, we are: removing changes of the current job, rebasing, and doing the process again until it succeeds.
7+
'
8+
9+
set -x
10+
11+
git config --local user.email "actions@github.com"
12+
git config --local user.name "Github Actions"
13+
14+
BRANCH="check-new-library-versions/$(date '+%Y-%m-%d')"
15+
git fetch origin "$BRANCH"
16+
git checkout "$BRANCH"
17+
18+
while [ true ]
19+
do
20+
# update the list of tested versions
21+
./gradlew addTestedVersion --coordinates="$1" --lastSupportedVersion="$2"
22+
23+
# commit changes
24+
git add -u
25+
git commit -m "$1"
26+
27+
# only push changes if we are not behind the remote branch
28+
if [ "$(git rev-list --count origin/$BRANCH --not $BRANCH)" -eq 0 ]
29+
then
30+
# try to push changes
31+
git push origin "$BRANCH"
32+
PUSH_RETVAL=$?
33+
if [ "$PUSH_RETVAL" -eq 0 ]
34+
then
35+
# if the push was successful, we can exit the loop
36+
break
37+
fi
38+
fi
39+
40+
# we are either behind the remote branch or we have a merge conflict => remove changes and rebase accepting incoming changes
41+
git reset --hard HEAD~1
42+
git fetch origin "$BRANCH"
43+
git rebase -X theirs "origin/$BRANCH"
44+
done
45+
46+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Check new versions of existing libraries in the repository
2+
3+
As the number of libraries in the repository grow fast, it is hard to track new library versions for every library manually.
4+
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))
5+
that automatically scans MavenCentral repository for new versions of the libraries that we currently have.
6+
7+
## How it works
8+
9+
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.
10+
The whole process consists of the following parts:
11+
* Scanning of the MavenCentral
12+
* Running existing tests with newer versions of the library
13+
* Creating a pull-request that updates `tested-versions` field of the `index.json` file for libraries that passed tests with a new version
14+
* Creating an issue that lists all versions of libraries that failed their existing tests.
15+
16+
As a preparation for the whole process, we are creating a branch for all successful tests, and a single issue for all failed tests.
17+
18+
### Scanning the MavenCentral
19+
20+
At first, the workflow runs gradle task called `fetchExistingLibrariesWithNewerVersions`.
21+
The task itself does the following:
22+
1. Gets the list of all existing libraries in the repository
23+
2. For each library, it searches for the latest tested version in the corresponding library `index.json` file
24+
3. For the given library name, it fetches `maven-metadata.xml` file from the MavenCentral repository
25+
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
26+
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)
27+
28+
### Running existing tests with newer versions
29+
30+
Now that we have coordinates list, we are spawning a new job in GitHub workflow for each coordinate in the list.
31+
Each of the spawned jobs:
32+
1. Extracts the following parts from the given maven coordinates:
33+
1. Latest version that we have tests written for
34+
2. Path to the latest tests we have
35+
3. Maven coordinates of the latest tests
36+
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.
37+
3. Run the latest test with `./gradlew test -Pcoordinates=<testCoordinates>` (with `testCoordinates` calculated in the step 1)
38+
39+
### Aggregating results of the tests
40+
41+
Based on the outcome of the test we:
42+
* 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
43+
* Add a comment that explains which library version cannot pass the tests, in the issue we previously created
44+
45+
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.
46+
The whole process of synchronization is driven by the [tryPushVersionsUpdate](https://github.com/oracle/graalvm-reachability-metadata/blob/master/.github/workflows/tryPushVersionsUpdate.sh) script.
47+
48+
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.
49+
As a final result, we have:
50+
* a pull-request with updates of all new tested versions
51+
* an issue with list of all versions that doesn't work with existing metadata

tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import org.graalvm.internal.tck.DockerTask
1414
import org.graalvm.internal.tck.ConfigFilesChecker
1515
import org.graalvm.internal.tck.ScaffoldTask
1616
import org.graalvm.internal.tck.GrypeTask
17+
import org.graalvm.internal.tck.TestedVersionUpdaterTask
1718
import org.graalvm.internal.tck.harness.tasks.CheckstyleInvocationTask
19+
import org.graalvm.internal.tck.harness.tasks.FetchExistingLibrariesWithNewerVersionsTask
1820
import org.graalvm.internal.tck.harness.tasks.TestInvocationTask
1921

2022

@@ -161,6 +163,14 @@ Provider<Task> generateMatrixDiffCoordinates = tasks.register("generateMatrixDif
161163
}
162164
}
163165

166+
// groovy tasks
167+
tasks.register("fetchExistingLibrariesWithNewerVersions", FetchExistingLibrariesWithNewerVersionsTask.class) { task ->
168+
task.setGroup(METADATA_GROUP)
169+
task.setDescription("Returns list of all libraries coordinates")
170+
task.setAllLibraryCoordinates(matchingCoordinates)
171+
}
172+
173+
// java tasks
164174
tasks.register("checkAllowedDockerImages", GrypeTask.class) { task ->
165175
task.setDescription("Returns list of allowed docker images")
166176
task.setGroup(METADATA_GROUP)
@@ -182,3 +192,8 @@ tasks.register("checkConfigFiles", ConfigFilesChecker.class) { task ->
182192
task.setDescription("Checks content of config files for a new library.")
183193
task.setGroup(METADATA_GROUP)
184194
}
195+
196+
tasks.register("addTestedVersion", TestedVersionUpdaterTask.class) { task ->
197+
task.setDescription("Updates list of tested versions.")
198+
task.setGroup(METADATA_GROUP)
199+
}

tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,33 @@
66
*/
77
package org.graalvm.internal.tck.harness;
88

9+
import com.fasterxml.jackson.annotation.JsonInclude;
10+
import com.fasterxml.jackson.core.type.TypeReference;
11+
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import com.fasterxml.jackson.databind.SerializationFeature;
13+
import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry;
914
import org.gradle.api.Project;
1015
import org.gradle.api.file.Directory;
1116
import org.gradle.api.file.DirectoryProperty;
1217
import org.gradle.api.file.FileSystemLocation;
1318
import org.gradle.api.provider.Property;
1419
import org.gradle.api.provider.Provider;
1520
import org.gradle.process.ExecOperations;
21+
import org.gradle.util.internal.VersionNumber;
1622

1723
import javax.inject.Inject;
1824
import java.io.ByteArrayOutputStream;
25+
import java.io.File;
1926
import java.io.IOException;
2027
import java.net.URI;
2128
import java.nio.charset.StandardCharsets;
2229
import java.nio.file.Files;
2330
import java.nio.file.Path;
2431
import java.nio.file.Paths;
25-
import java.util.ArrayList;
26-
import java.util.Arrays;
27-
import java.util.Collection;
28-
import java.util.HashSet;
29-
import java.util.List;
30-
import java.util.Map;
31-
import java.util.Objects;
32-
import java.util.Set;
32+
import java.util.*;
3333
import java.util.concurrent.atomic.AtomicBoolean;
34+
import java.util.regex.Matcher;
35+
import java.util.regex.Pattern;
3436
import java.util.stream.Collectors;
3537
import java.util.stream.Stream;
3638

@@ -299,9 +301,7 @@ List<String> getMatchingCoordinates(String coordinateFilter) {
299301
String artifactId = strings.get(1);
300302
String version = strings.get(2);
301303

302-
303304
Set<String> matchingCoordinates = new HashSet<>();
304-
305305
for (String directory : getMatchingMetadataDirs(groupId, artifactId)) {
306306
Path index = metadataRoot().resolve(directory).resolve("index.json");
307307
List<Map<String, ?>> metadataIndex = (List<Map<String, ?>>) extractJsonFile(index);
@@ -322,7 +322,8 @@ List<String> getMatchingCoordinates(String coordinateFilter) {
322322
}
323323
}
324324
}
325-
return matchingCoordinates.stream().collect(Collectors.toList());
325+
326+
return new ArrayList<>(matchingCoordinates);
326327
}
327328

328329
/**

tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ abstract class AbstractSubprojectTask extends DefaultTask {
9393
// Environment variables for setting up TCK
9494
env.put("GVM_TCK_LC", coordinates)
9595
env.put("GVM_TCK_EXCLUDE", override.toString())
96-
env.put("GVM_TCK_LV", version)
96+
if (System.getenv("GVM_TCK_LV") == null) {
97+
// we only set this env variable if user didn't specify it manually
98+
env.put("GVM_TCK_LV", version)
99+
}
97100
env.put("GVM_TCK_MD", metadataDir.toAbsolutePath().toString())
98101
env.put("GVM_TCK_TCKDIR", tckExtension.getTckRoot().get().getAsFile().toPath().toAbsolutePath().toString())
99102
spec.environment(env)

0 commit comments

Comments
 (0)