Compare commits

...

146 Commits

Author SHA1 Message Date
bigdaz
833b05f3e6 [bot] Update dist directory 2024-08-01 17:05:22 +00:00
Daz DeBoer
06905c7a0f Enable wrapper-validation by default in setup-gradle (#318) 2024-08-01 11:04:20 -06:00
daz
73f1290de7 Improve docs linked for wrapper-validation failure 2024-08-01 10:59:51 -06:00
daz
b6395da67c Cache validated checksums for later executions
The most common case for validation will be that the wrapper jars are unchanged
from a previous workflow run. In this case, we cache the validated wrapper
checksums to minimise the work required on a subsequent run.

Fixes #172
2024-08-01 10:52:36 -06:00
daz
ce4c3a6c5e Move wrapper-validation into common setup code 2024-08-01 10:52:36 -06:00
daz
b644be617f Enable wrapper validation by default
- Add 'allow-snapshot-wrappers' input parameter
- Default 'validate-wrappers' to 'true'

Fixes #12
2024-08-01 10:51:02 -06:00
daz
7179909719 Verify wrappers for distribution-snapshots
By slurping the checksum URLs from https://services.gradle.org/distributions-snapshots/
we can include these unpublished wrapper checksums in validation.

Fixes #281
2024-07-31 21:26:55 -06:00
daz
c01aea0cb4 Introduce cheerio for fast HTML querying 2024-07-31 21:26:55 -06:00
bigdaz
479297d73e [bot] Update dist directory 2024-08-01 03:26:19 +00:00
daz
fe594a580d Group cache-cleanup log messages for clarity 2024-07-31 21:25:22 -06:00
daz
53f2a5657b Add newline to args message 2024-07-31 13:22:03 -06:00
bigdaz
fd87365911 [bot] Update dist directory 2024-07-23 02:56:59 +00:00
bot-githubaction
c3f989640d Bump references to Develocity Gradle plugin from 3.17.5 to 3.17.6 2024-07-22 20:56:04 -06:00
bigdaz
6c9e547314 [bot] Update dist directory 2024-07-22 09:30:39 -06:00
daz
1a11891cfe Add error to job summary 2024-07-22 09:30:39 -06:00
daz
fae6382622 Fail the build when arguments parameter is used 2024-07-22 09:30:39 -06:00
Daz DeBoer
81b4ac7741 Fix typo 2024-07-22 07:21:54 -06:00
daz
c9872874b0 Segregate cache key for quick and full integtest 2024-07-21 14:38:05 -06:00
bigdaz
561dcd8516 [bot] Update dist directory 2024-07-21 20:20:49 +00:00
daz
7bb45b1dbd Finish enabling cache-cleanup by default
- Add deprecation warning for `gradle-home-cache-cleanup`
- Change default for `dependency-submission` to `cache-cleanup: on-success`
- Update documentation for changed default
2024-07-21 14:19:54 -06:00
bigdaz
dc97151e55 [bot] Update dist directory 2024-07-20 01:41:36 +00:00
Daz DeBoer
2289da045c Introduce dependency graph params (#304)
Fixes #193
2024-07-19 19:40:42 -06:00
bigdaz
40a3605597 [bot] Update dist directory 2024-07-20 01:25:51 +00:00
daz
e6688f31b8 Include RUNNER_ARCH in cache key
Previously, including RUNNER_OS was enough to prevent leaking incompatible
content between Gradle User Homes. With the introduction of macos-14,
we now need to differentiate between different runner architectures as well.

Fixes #138
2024-07-19 19:24:50 -06:00
Daz DeBoer
46308b920a Document dependency verification requirements
Fixes #256
2024-07-19 19:24:12 -06:00
daz
7387edbbb3 Add more input parameters for dependency-graph generation
- dependency-graph-include-projects
- dependency-graph-exclude-projects
- dependency-graph include-configurations
- dependency-graph-exclude-configurations
2024-07-19 17:22:07 -06:00
daz
9e459adb11 Add 'dependency-graph-report-dir' input parameter 2024-07-19 17:21:10 -06:00
bigdaz
1371d49f1d [bot] Update dist directory 2024-07-19 23:14:37 +00:00
Daz DeBoer
8e4868ab4a Prepare for v4 release (#303)
Resolves #297
Resolves #26
2024-07-19 17:13:42 -06:00
daz
5acdee61f0 Make cache-cleanup compatible with cache-write-only 2024-07-19 16:16:59 -06:00
daz
32f9239e2c Remove the undocumented 'dependency-graph: clear' option 2024-07-19 16:16:13 -06:00
daz
12cb23c359 Enable cache-cleanup 'on-success' by default
Fixes #26
2024-07-19 15:28:39 -06:00
daz
238a3da6f4 Remove tests for deprecated features 2024-07-19 15:15:03 -06:00
daz
2041ce6ab1 Fail on delegation from legacy actions 2024-07-19 15:12:40 -06:00
daz
ded8009fcf Remove deprecated input parameters 2024-07-19 15:12:40 -06:00
bigdaz
db8e69bc03 [bot] Update dist directory 2024-07-19 20:14:55 +00:00
Daz DeBoer
4b56f19bda Use pre-installed Gradle when available (#301)
Fixes #270
2024-07-19 14:13:59 -06:00
daz
4576973cce Test provision with more Gradle versions
Include Gradle 8.8 since it is the pre-installed version on Windows runners.
2024-07-19 14:05:33 -06:00
daz
5d7c18409c Use pre-installed Gradle when version matches
By executing Gradle on the PATH, we can avoid downloading and installing
a Gradle version that is already available on the runner.

Fixes #270
2024-07-19 13:46:56 -06:00
daz
7da993afd5 Add 'which' NPM library 2024-07-19 13:46:55 -06:00
daz
de6862d826 Use bash to ensure env vars are expanded 2024-07-19 12:59:38 -06:00
bigdaz
4c453aec59 [bot] Update dist directory 2024-07-19 17:56:33 +00:00
daz
917439d87d Always set the GRADLE_USER_HOME env var 2024-07-19 11:55:34 -06:00
bigdaz
77266ec345 [bot] Update dist directory 2024-07-19 17:08:28 +00:00
Daz DeBoer
514ac344fc Use faster D: drive on windows for Gradle User Home and Gradle downloads (#299)
Fixes #290
2024-07-19 11:07:34 -06:00
daz
f8aa3ed8b4 Use faster D: drive for Gradle User Home on Windows 2024-07-19 10:04:41 -06:00
daz
b175189376 Download and install Gradle distributions into tmpdir 2024-07-19 09:14:37 -06:00
bigdaz
20e923b8cb [bot] Update dist directory 2024-07-19 15:06:31 +00:00
daz
e7f9f25d86 Delete excluded paths on restore Gradle Home
Fixes #296
2024-07-19 09:05:31 -06:00
bigdaz
f8f7d3e704 [bot] Update dist directory 2024-07-19 04:33:23 +00:00
Daz DeBoer
7a630bc41b Disable cache-cleanup on config-cache hit (#295)
Fixes #19
2024-07-18 22:32:30 -06:00
daz
a77cb2b0f8 Add test for no cache-cleanup with config-cache hit 2024-07-18 22:25:59 -06:00
daz
54f7dc55a5 Adapt for new structure of build results 2024-07-18 22:25:58 -06:00
daz
579a013225 Capture config-cache hit in build results 2024-07-18 22:25:58 -06:00
daz
94355bbb2f Split build-results and build-scan capture into separate files 2024-07-18 22:25:58 -06:00
daz
6d20c16462 Use settingsEvaluated in preference to projectsEvaluated in init-script 2024-07-18 22:25:53 -06:00
bigdaz
b7e399239c [bot] Update dist directory 2024-07-18 17:34:32 +00:00
Daz DeBoer
723ca4de01 Caching improvements (#294)
- Better reporting of cache status in Job Summary (including cache
cleanup)
- Allow cache cleanup to be skipped when Gradle builds fail
- Refactoring
2024-07-18 11:33:36 -06:00
daz
72dde7ef1e Move action entry-point sources 2024-07-18 11:09:54 -06:00
daz
3083f01451 Use new cache-cleanup param in integtest 2024-07-17 22:47:46 -06:00
daz
27dea2df09 Allow better control over cache-cleanup
Adds new 'cache-cleanup' parameter with 3 settings: 'never', 'on-success' and 'always'.
This gives users more control over whether cache cleanup should occur.

Fixes #71
2024-07-17 20:36:33 -06:00
daz
91a526b647 Refactor BuildResults 2024-07-17 20:35:14 -06:00
daz
d92de28b80 Improve cache reporting
- More succinct messages for cache-read-only and cache-disabled
- Report on cache-cleanup enabled/disabled status
2024-07-17 20:29:57 -06:00
daz
8d318190ad Add better explanation for cache status
- Clarify default settings for cache-read-only
- Explain why cache was disabled or read-only
- Provide links to documentation in Job Summary

Fixes #255
2024-07-17 15:17:09 -06:00
daz
a025cbe7ec Don't run test on windows 2024-07-16 22:43:22 -06:00
bigdaz
23dad2b1c7 [bot] Update dist directory 2024-07-17 04:13:39 +00:00
Daz DeBoer
4a315dceb2 Isolate 'dependency-submission' action from 'setup-gradle' (#293) 2024-07-16 22:12:40 -06:00
daz
bb7a843511 Reset dependency-graph after submission
Fixes #204
2024-07-16 21:15:51 -06:00
daz
9a57bcca96 Add test for combining setup-gradle and dependency-submission 2024-07-16 21:15:30 -06:00
daz
22818445b3 Restrict download-and-submit to selected file 2024-07-16 21:15:29 -06:00
bigdaz
36c24e793d [bot] Update dist directory 2024-07-16 19:05:54 +00:00
Daz DeBoer
dff3ef9b8d Only fetch checksums for unknown wrapper versions (#292)
The checksum values for most wrapper versions are hard-coded into the
action. These known checksum values are first used for validation: only
if none of the known values work do we download checksums.

Previously, we blindly downloaded all of the checksum values in this
case: we now only download the checksums for versions that are not in
our "known" set.

Fixes #171
2024-07-16 13:04:57 -06:00
Daz DeBoer
01254b3eaa Add additional references to dependency-submission docs
Fixes #276
2024-07-15 16:21:32 -06:00
bigdaz
d9c87d481d [bot] Update dist directory 2024-07-15 19:00:43 +00:00
Daz DeBoer
ff865cb801 Upload dependency graph on submission failure (#291) 2024-07-15 12:47:06 -06:00
daz
c3acd19a4a Update to Gradle 8.9 2024-07-14 08:12:33 -06:00
bigdaz
e5bbd4c742 [bot] Update dist directory 2024-07-12 17:38:55 +00:00
dependabot[bot]
d7cd9fc65c Bump com.fasterxml.jackson.dataformat:jackson-dataformat-smile
Bumps [com.fasterxml.jackson.dataformat:jackson-dataformat-smile](https://github.com/FasterXML/jackson-dataformats-binary) from 2.17.1 to 2.17.2.
- [Commits](https://github.com/FasterXML/jackson-dataformats-binary/compare/jackson-dataformats-binary-2.17.1...jackson-dataformats-binary-2.17.2)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-smile
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 11:30:33 -06:00
github-actions[bot]
6407986e96 Update known wrapper checksums (#278)
Automatically generated pull request to update the known wrapper
checksums.

Co-authored-by: bigdaz <179734+bigdaz@users.noreply.github.com>
2024-07-12 11:29:43 -06:00
daz
87bf5ca2ea Update DV plugin versions that were missed by automation 2024-07-05 11:33:00 -06:00
Daz DeBoer
81b4ece56a Clarify that tags are isolated in cache entries 2024-07-02 12:15:24 -06:00
bigdaz
cdbbabd09c [bot] Update dist directory 2024-06-28 19:47:07 +00:00
Daz DeBoer
dad038d88d Use Gradle 8.8 features for Gradle Home cleanup (#272)
Fixes #33
Fixes #24
Fixes #46 
Fixes #169
2024-06-28 13:46:10 -06:00
daz
621f3b3f79 Run cache-cleanup build with --info
Resolves #169
2024-06-28 13:25:56 -06:00
daz
4022faad7e Fix integ-test-cache-cleanup.yml for running on act 2024-06-28 13:21:54 -06:00
daz
95ef72241e Use Gradle 8.8 features for cleanup
Gradle 8.8 introduces new features that allow us to avoid using
timestamp manipulation to force the cleanup of the Gradle User Home directory.

This solution is simpler and more robust, but relies on Gradle 8.8+ always being
used for the cache cleanup operation.

Fixes #24
2024-06-28 13:06:23 -06:00
daz
169bec5d8b Provision latest Gradle for cache-cleanup
To cleanup Gradle User Home, a Gradle build must be executed.
Newer Gradle versions are able to cleanup the home directories of older versions,
but not vice-versa.

With this change, the latest version of Gradle is automatically provisioned
in order to run Gradle User Home cleanup. This ensures a consistent version of
Gradle is used for cleanup, and fixes #33 where Gradle is not pre-installed on
a custom runner.
2024-06-28 12:39:09 -06:00
Daz DeBoer
b9abb7b195 Use latest dependency graph plugin (#269) 2024-06-27 19:42:01 -06:00
Daz DeBoer
c04155e2ca Remove instructions for merging Dependabot PRs
These are no longer required, since the bot will update the `dist` directory.
2024-06-27 19:32:31 -06:00
daz
1da1cc97d5 Rename Develocity secret 2024-06-27 18:49:52 -06:00
dependabot[bot]
c401249391 Bump org.junit.jupiter:junit-jupiter
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 18:05:07 -06:00
bigdaz
a6a0c7dcef [bot] Update dist directory 2024-06-27 20:12:25 +00:00
bigdaz
3f3913eed0 Update known wrapper checksums 2024-06-27 14:11:23 -06:00
Eric Haag
2cd2a6e951 Fix grammar in docs 2024-06-20 07:58:28 -05:00
cdsap
dbbdc275be [bot] Update dist directory 2024-06-17 17:45:00 +00:00
Iñaki Villar
ae74429826 Fix race condition with fetching short lived token (#260)
This somehow worked before (and in our integ test) because the setup
action gave enough time to let the request short-lived token return in
time 🤷.
2024-06-17 10:44:01 -07:00
Alexis Tual
bdc7162ff9 Forward the setup short-lived token Promise
This caused a race condition not allowing the short-lived token to be returned in time before the setup finished.
2024-06-17 19:03:10 +02:00
bigdaz
31ae3562f6 [bot] Update dist directory 2024-06-15 03:19:11 +00:00
Daz DeBoer
719985db3d Simplify requesting short-lived Develocity access tokens (#259)
- Always fetch a token for every hostname in the access key
- Use any tokens that are successfully fetched
- Retain access key if no tokens can be fetched
2024-06-14 21:18:08 -06:00
bigdaz
b53238971c [bot] Update dist directory 2024-06-14 22:45:05 +00:00
Inaki Villar
5f1c5827bf handle missing access token 2024-06-14 16:44:06 -06:00
bigdaz
d9336dac04 [bot] Update dist directory 2024-06-13 19:36:18 +00:00
daz
8dbe9a3802 Update DV access key regex to be more selective
This should address the code-scanning alert
  https://github.com/gradle/actions/security/code-scanning/1
2024-06-13 13:35:19 -06:00
bigdaz
9c3430720d [bot] Update dist directory 2024-06-13 19:32:23 +00:00
daz
30c82f0068 Fail on invalid boolean for Develocity inputs 2024-06-13 13:31:25 -06:00
daz
e3bc05f224 Run CodeQL on PRs 2024-06-13 13:15:23 -06:00
daz
485ea107b7 Run CodeQL on dev/* branches 2024-06-13 13:01:56 -06:00
bigdaz
c1091c9c8e [bot] Update dist directory 2024-06-13 18:43:53 +00:00
Iñaki Villar
d0a116fff5 Adding Develocity input actions (#244)
Adding Develocity input actions. 

If an input is configured in the action, it will generate the environment variable, example:

```yaml
    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@v3
      with:
        develocity-injection-enabled: true
        develocity-url: https://develocity.your-server.com
        develocity-plugin-version: 3.17.4

    - name: Run a Gradle build with Develocity injection enabled from input actions
      run: ./gradlew build
```

This configuration will create the environment variables:
```
DEVELOCITY_INJECTION_ENABLED=true
DEVELOCITY_URL=https://develocity.your-server.com
DEVELOCITY_PLUGIN_VERSION=3.17.4
```

Relation variable-input available:

| Variable | Input |

|--------------------------------------|--------------------------------------|
| DEVELOCITY_INJECTION_ENABLED | develocity-injection-enabled |
| DEVELOCITY_URL | develocity-url |
| DEVELOCITY_ALLOW_UNTRUSTED_SERVER | develocity-allow-untrusted-server
|
| DEVELOCITY_CAPTURE_FILE_FINGERPRINTS |
develocity-capture-file-fingerprints |
| DEVELOCITY_ENFORCE_URL | develocity-enforce-url |
| DEVELOCITY_PLUGIN_VERSION | develocity-plugin-version |
| DEVELOCITY_CCUD_PLUGIN_VERSION | develocity-ccud-plugin-version |
| GRADLE_PLUGIN_REPOSITORY_URL | gradle-plugin-repository-url |
| GRADLE_PLUGIN_REPOSITORY_USERNAME | gradle-plugin-repository-username
|
| GRADLE_PLUGIN_REPOSITORY_PASSWORD | gradle-plugin-repository-password
|
2024-06-13 12:42:47 -06:00
bigdaz
e238a7ad22 [bot] Update dist directory 2024-06-13 16:01:59 +00:00
bot-githubaction
1d2ea6e5a8 Bump references to Develocity Gradle plugin from 3.17.4 to 3.17.5 2024-06-13 10:01:04 -06:00
dependabot[bot]
114c1c234e Bump braces from 3.0.2 to 3.0.3 in /sources
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 09:10:17 -06:00
daz
2db3ae936e Update to Gradle 8.8 2024-06-13 09:03:40 -06:00
dependabot[bot]
a68381d359 Bump com.google.guava:guava in /.github/workflow-samples/kotlin-dsl
Bumps [com.google.guava:guava](https://github.com/google/guava) from 33.2.0-jre to 33.2.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 07:41:41 -06:00
bigdaz
52ae27f7bb [bot] Update dist directory 2024-06-13 13:41:01 +00:00
bigdaz
d1cd62d80a Update known wrapper checksums 2024-06-13 07:40:03 -06:00
Anton Mostovoy
af6e576724 add missing permission for downloading dependency graph artifact
per https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts, `actions: read` is required. Without it the action fails after logging `Fetching artifact list for workflow ` due to `Error: HttpError: Resource not accessible by integration`
2024-05-23 09:09:36 -06:00
bigdaz
775b4d10d7 [bot] Update dist directory 2024-05-23 14:57:08 +00:00
Bot Githubaction
30610bc983 Bump references to Develocity Gradle plugin from 3.17.3 to 3.17.4 (#236)
This PR bumps references to Develocity Gradle plugin from 3.17.3 to 3.17.4.

---------

Co-authored-by: Eric Haag <ehaag@gradle.com>
2024-05-23 08:56:09 -06:00
bigdaz
d4d72c9934 [bot] Update dist directory 2024-05-17 21:08:44 +00:00
Alexis Tual
96b9cb4988 Set both DEVELOCITY_ACCESS_KEY and GRADLE_ENTERPRISE_ACCESS_KEY env vars (#225)
Follow up of https://github.com/gradle/actions/pull/224, we now attempt to set both old and new access key env variables to a short lived token.
If a short-lived token cannot be obtained, then:
- DEVELOCITY_ACCESS_KEY is set to an empty string, preventing this from being used.
- GRADLE_ENTERPRISE_ACCESS_KEY is left intact, with a deprecation warning being issued.
2024-05-17 15:07:50 -06:00
Bot Githubaction
db270b9337 Update develocity-injection init script to v1.0
Updates the develocity-injection init script to the latest reference script content
from https://github.com/gradle/develocity-ci-injection.
2024-05-17 14:18:19 -06:00
Bot Githubaction
d91e2960eb Update develocity-injection init script to v0.5.1
Updates the develocity-injection init script to the latest reference script content
from https://github.com/gradle/develocity-ci-injection.
2024-05-17 11:10:27 -06:00
bigdaz
0498421560 Update develocity-injection init script to v0.5.0 2024-05-17 07:27:33 -06:00
bigdaz
edb13383f3 Update develocity-injection init script to v0.4.0 2024-05-15 16:56:41 -06:00
bigdaz
cd560aa3ad [bot] Update dist directory 2024-05-15 22:50:56 +00:00
Alexis Tual
500e0ee5b3 Add support for short-lived tokens (#224)
The setup-gradle action tries to get a short-lived access token given the supplied Develocity access key.
This key can be passed either with the `DEVELOCITY_ACCESS_KEY` env var or via the  `develocity-access-key` input parameter.
If a token can be retrieved, then the `DEVELOCITY_ACCESS_KEY` env var will be set to the token. 
Otherwise the `DEVELOCITY_ACCESS_KEY` will be set to a blank string, to avoid a leak.

---------

Co-authored-by: daz <daz@gradle.com>
2024-05-15 16:49:55 -06:00
bigdaz
eb13cf7170 [bot] Update dist directory 2024-05-11 13:46:41 +00:00
Daz DeBoer
ea14aa9caf Dependency updates (#222)
- Bump to com.gradle.develocity plugin v3.17.3
- Bump JVM dependencies in sample projects
2024-05-11 07:45:47 -06:00
lokalpage-safe
063cfaf0eb Update dependency-submission.md
fix: Fixed document typo (additonal -> additional)
2024-05-09 15:54:39 -06:00
Maxim Mironyuk
35f9242e22 Update setup-gradle.md
repsitory -> repository
2024-05-09 15:54:08 -06:00
bigdaz
90f1de0556 [bot] Update dist directory 2024-04-28 14:50:56 +00:00
bigdaz
da512b52a5 Update known wrapper checksums 2024-04-28 15:49:59 +01:00
bigdaz
db19848a5f [bot] Update dist directory 2024-04-25 20:24:26 +00:00
daz
941b289d84 Avoid running Gradle 3.5.1 on MacOS where Java 8 is not available 2024-04-25 21:23:15 +01:00
Daz DeBoer
bce7daca54 Improve build scan badge readability with long tasks (#200)
Improve readability of build scan when requested tasks is very long, as
agreed in #175. HTML diff for each case of job summary is clearer in
cd62d9c9ef.

- Ensure a minimum size for the badge, at least the size of "Build
scan®", by preventing a line break with `&nbsp;`
- Reduce the size of the badge by tweaking the inner text

Also fix a typo in the build shell script.
2024-04-25 21:22:36 +01:00
daz
11eaed9738 Avoid Java 8 since it is not available on MacOS runners 2024-04-25 20:17:16 +01:00
Gabriel Feo
cd62d9c9ef Improve job summary readability with long tasks
Change tests for nbsp and concise badge

Make tests pass

Signed-off-by: Gabriel Feo <gabriel@gabrielfeo.com>
2024-04-25 19:40:04 +01:00
Gabriel Feo
a54fb6a5bb Add tests on current job-summary behavior
Signed-off-by: Gabriel Feo <gabriel@gabrielfeo.com>
2024-04-25 17:44:41 +01:00
Gabriel Feo
ef36f81b41 Fix typo in build shell script
Signed-off-by: Gabriel Feo <gabriel@gabrielfeo.com>
2024-04-25 17:42:29 +01:00
bigdaz
18998bc43e [bot] Update dist directory 2024-04-25 10:20:39 +00:00
daz
a772c14b33 Avoid updating real dependency graph in tests 2024-04-25 11:19:43 +01:00
daz
7763d71170 Set the report dir for download-and-submit
Fixes #196
2024-04-25 11:19:43 +01:00
bigdaz
9ab93ee864 [bot] Update dist directory 2024-04-19 16:25:39 +00:00
daz
d124ec149f Retain and log stacktrace for submission errors 2024-04-19 10:24:45 -06:00
Alex Serbin
6ccde15122 Use logger in dependency graph init script (#191)
Dependency graph init script now uses Gradle logger to emit warning/info messages.
This allows these messages to be suppressed using `--quiet`.
2024-04-19 07:03:17 -06:00
112 changed files with 112874 additions and 4423 deletions

View File

@@ -9,6 +9,11 @@ runs:
distribution: 'temurin' distribution: 'temurin'
java-version: 11 java-version: 11
- name: Configure environment
shell: bash
run: |
echo "ALLOWED_GRADLE_WRAPPER_CHECKSUMS=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" >> "$GITHUB_ENV"
# Downloads a 'dist' directory artifact that was uploaded in an earlier 'build-dist' step # Downloads a 'dist' directory artifact that was uploaded in an earlier 'build-dist' step
- name: Download dist - name: Download dist
if: ${{ env.SKIP_DIST != 'true' && !env.ACT }} if: ${{ env.SKIP_DIST != 'true' && !env.ACT }}

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.develocity" version "3.17.2" id "com.gradle.develocity" version "3.17.6"
id "com.gradle.common-custom-user-data-gradle-plugin" version "2.0.1" id "com.gradle.common-custom-user-data-gradle-plugin" version "2.0.1"
} }

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -8,9 +8,9 @@ repositories {
dependencies { dependencies {
api("org.apache.commons:commons-math3:3.6.1") api("org.apache.commons:commons-math3:3.6.1")
implementation("com.google.guava:guava:33.1.0-jre") implementation("com.google.guava:guava:33.2.1-jre")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
} }
tasks.test { tasks.test {

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id("com.gradle.develocity") version "3.17.2" id("com.gradle.develocity") version "3.17.6"
id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1"
} }

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.develocity" version "3.17.2" id "com.gradle.develocity" version "3.17.6"
} }
develocity { develocity {

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.develocity" version "3.17.2" id "com.gradle.develocity" version "3.17.6"
} }
develocity { develocity {

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id "com.gradle.develocity" version "3.17.2" id "com.gradle.develocity" version "3.17.6"
} }
develocity { develocity {

View File

@@ -5,6 +5,12 @@ on:
branches: branches:
- 'main' - 'main'
- 'release/**' - 'release/**'
- 'dev/**' # Allow running Code QL on dev branches without a PR
paths-ignore:
- 'dist/**'
pull_request:
branches:
- 'main'
paths-ignore: paths-ignore:
- 'dist/**' - 'dist/**'
schedule: schedule:

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: temurin distribution: temurin
java-version: 8 java-version: 11
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v3 # Use a released version to avoid breakages uses: gradle/actions/setup-gradle@v3 # Use a released version to avoid breakages
- name: Run integration tests - name: Run integration tests

View File

@@ -53,14 +53,6 @@ jobs:
if: ${{ needs.determine-suite.outputs.suite != 'full' }} if: ${{ needs.determine-suite.outputs.suite != 'full' }}
uses: ./.github/actions/build-dist uses: ./.github/actions/build-dist
action-inputs:
needs: [determine-suite, build-distribution]
uses: ./.github/workflows/integ-test-action-inputs.yml
with:
runner-os: '${{ needs.determine-suite.outputs.runner-os }}'
cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-'
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
build-scan-publish: build-scan-publish:
needs: [determine-suite, build-distribution] needs: [determine-suite, build-distribution]
uses: ./.github/workflows/integ-test-build-scan-publish.yml uses: ./.github/workflows/integ-test-build-scan-publish.yml
@@ -74,7 +66,7 @@ jobs:
uses: ./.github/workflows/integ-test-cache-cleanup.yml uses: ./.github/workflows/integ-test-cache-cleanup.yml
with: with:
runner-os: '${{ needs.determine-suite.outputs.runner-os }}' runner-os: '${{ needs.determine-suite.outputs.runner-os }}'
cache-key-prefix: '${{github.run_number}}-' # Requires a fresh cache entry each run cache-key-prefix: '${{ needs.determine-suite.outputs.suite}}-${{github.run_number}}-' # Requires a fresh cache entry each run
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }} skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
caching-config: caching-config:
@@ -118,22 +110,6 @@ jobs:
cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-' cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-'
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }} skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
execution-with-caching:
needs: [determine-suite, build-distribution]
uses: ./.github/workflows/integ-test-execution-with-caching.yml
with:
runner-os: '${{ needs.determine-suite.outputs.runner-os }}'
cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-'
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
execution:
needs: [determine-suite, build-distribution]
uses: ./.github/workflows/integ-test-execution.yml
with:
runner-os: '${{ needs.determine-suite.outputs.runner-os }}'
cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-'
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
develocity-injection: develocity-injection:
if: ${{ ! github.event.pull_request.head.repo.fork }} if: ${{ ! github.event.pull_request.head.repo.fork }}
needs: [determine-suite, build-distribution] needs: [determine-suite, build-distribution]
@@ -143,7 +119,7 @@ jobs:
cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-' cache-key-prefix: '${{ needs.determine-suite.outputs.cache-key-prefix }}-'
skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }} skip-dist: ${{ needs.determine-suite.outputs.suite == 'full' }}
secrets: secrets:
DEVELOCITY_ACCESS_KEY: ${{ secrets.GE_SOLUTIONS_ACCESS_TOKEN }} DEVELOCITY_ACCESS_KEY: ${{ secrets.DV_SOLUTIONS_ACCESS_KEY }}
provision-gradle-versions: provision-gradle-versions:
needs: [determine-suite, build-distribution] needs: [determine-suite, build-distribution]

View File

@@ -1,62 +0,0 @@
name: demo-failure-cases
on:
workflow_dispatch:
jobs:
build-distribution:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Build and upload distribution
uses: ./.github/actions/build-dist
failing-build:
needs: build-distribution
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Test build failure
uses: ./setup-gradle
continue-on-error: true
with:
build-root-directory: .github/workflow-samples/kotlin-dsl
arguments: not-a-valid-task
wrapper-missing:
needs: build-distribution
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Test wrapper missing
uses: ./setup-gradle
continue-on-error: true
with:
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help
bad-configuration:
needs: build-distribution
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Test bad config value
uses: ./setup-gradle
continue-on-error: true
with:
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help
cache-disabled: yes

View File

@@ -23,6 +23,9 @@ jobs:
- name: Setup Gradle - name: Setup Gradle
uses: ./setup-gradle uses: ./setup-gradle
with:
cache-read-only: false
cache-cleanup: 'on-success'
- name: Build kotlin-dsl project - name: Build kotlin-dsl project
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble run: ./gradlew assemble

View File

@@ -1,42 +0,0 @@
name: Test action inputs
on:
workflow_call:
inputs:
cache-key-prefix:
type: string
runner-os:
type: string
default: '["ubuntu-latest", "windows-latest", "macos-latest"]'
skip-dist:
type: boolean
default: false
env:
SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: action-inputs-${{ inputs.cache-key-prefix }}
jobs:
action-inputs:
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Invoke with multi-line arguments
uses: ./setup-gradle
with:
build-root-directory: .github/workflow-samples/groovy-dsl
arguments: |
--configuration-cache
--build-cache
-DsystemProperty=FOO
-PgradleProperty=BAR
test
jar

View File

@@ -34,7 +34,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: temurin distribution: temurin
java-version: 8 java-version: 11
- name: Setup Gradle - name: Setup Gradle
id: setup-gradle id: setup-gradle
uses: ./setup-gradle uses: ./setup-gradle

View File

@@ -35,7 +35,7 @@ jobs:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
- name: Build with 3.1 - name: Build with 3.1
working-directory: sources/test/jest/resources/cache-cleanup working-directory: sources/test/jest/resources/cache-cleanup
run: gradle --no-daemon --build-cache -Dcommons_math3_version="3.1" build run: ./gradlew --no-daemon --build-cache -Dcommons_math3_version="3.1" build
# Second build will use the cache from the first build, but cleanup should remove unused artifacts # Second build will use the cache from the first build, but cleanup should remove unused artifacts
assemble-build: assemble-build:
@@ -55,10 +55,10 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
cache-read-only: false cache-read-only: false
gradle-home-cache-cleanup: true cache-cleanup: 'on-success'
- name: Build with 3.1.1 - name: Build with 3.1.1
working-directory: sources/test/jest/resources/cache-cleanup working-directory: sources/test/jest/resources/cache-cleanup
run: gradle --no-daemon --build-cache -Dcommons_math3_version="3.1.1" build run: ./gradlew --no-daemon --build-cache -Dcommons_math3_version="3.1.1" build
check-clean-cache: check-clean-cache:
needs: assemble-build needs: assemble-build
@@ -78,15 +78,22 @@ jobs:
with: with:
cache-read-only: true cache-read-only: true
- name: Report Gradle User Home - name: Report Gradle User Home
run: du -hc ~/.gradle/caches/modules-2 shell: bash
run: |
du -hc $GRADLE_USER_HOME/caches/modules-2
du -hc $GRADLE_USER_HOME/wrapper/dists
- name: Verify cleaned cache - name: Verify cleaned cache
shell: bash shell: bash
run: | run: |
if [ ! -e ~/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.1.1 ]; then if [ ! -e $GRADLE_USER_HOME/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.1.1 ]; then
echo "::error ::Should find commons-math3 3.1.1 in cache" echo "::error ::Should find commons-math3 3.1.1 in cache"
exit 1 exit 1
fi fi
if [ -e ~/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.1 ]; then if [ -e $GRADLE_USER_HOME/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.1 ]; then
echo "::error ::Should NOT find commons-math3 3.1 in cache" echo "::error ::Should NOT find commons-math3 3.1 in cache"
exit 1 exit 1
fi fi
if [ ! -e $GRADLE_USER_HOME/wrapper/dists/gradle-8.0.2-bin ]; then
echo "::error ::Should find gradle-8.0.2 in wrapper/dists"
exit 1
fi

View File

@@ -18,9 +18,10 @@ permissions:
env: env:
SKIP_DIST: ${{ inputs.skip-dist }} SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }} GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-graph-${{ inputs.cache-key-prefix }}
GITHUB_DEPENDENCY_GRAPH_REF: 'refs/tags/v0.0.1' # Use a different ref to avoid updating the real dependency graph for the repository
jobs: jobs:
groovy-generate: groovy-upload:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -41,7 +42,7 @@ jobs:
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
groovy-submit: groovy-submit:
needs: [groovy-generate] needs: [groovy-upload]
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -53,6 +54,8 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
dependency-graph: download-and-submit dependency-graph: download-and-submit
env:
DEPENDENCY_GRAPH_DOWNLOAD_ARTIFACT_NAME: groovy-upload
kotlin-generate-and-submit: kotlin-generate-and-submit:
strategy: strategy:

View File

@@ -15,6 +15,7 @@ on:
env: env:
SKIP_DIST: ${{ inputs.skip-dist }} SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-submission-failures-${{ inputs.cache-key-prefix }} GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-submission-failures-${{ inputs.cache-key-prefix }}
GITHUB_DEPENDENCY_GRAPH_REF: 'refs/tags/v0.0.1' # Use a different ref to avoid updating the real dependency graph for the repository
jobs: jobs:
failing-build: failing-build:

View File

@@ -18,6 +18,7 @@ permissions:
env: env:
SKIP_DIST: ${{ inputs.skip-dist }} SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-submission-${{ inputs.cache-key-prefix }} GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: dependency-submission-${{ inputs.cache-key-prefix }}
GITHUB_DEPENDENCY_GRAPH_REF: 'refs/tags/v0.0.1' # Use a different ref to avoid updating the real dependency graph for the repository
jobs: jobs:
groovy-generate-and-upload: groovy-generate-and-upload:
@@ -79,6 +80,8 @@ jobs:
uses: ./dependency-submission uses: ./dependency-submission
with: with:
dependency-graph: download-and-submit dependency-graph: download-and-submit
env:
DEPENDENCY_GRAPH_DOWNLOAD_ARTIFACT_NAME: groovy-generate-and-upload-${{ matrix.os }}
kotlin-generate-and-submit: kotlin-generate-and-submit:
strategy: strategy:
@@ -223,15 +226,9 @@ jobs:
with: with:
gradle-version: ${{ matrix.gradle }} gradle-version: ${{ matrix.gradle }}
build-root-directory: .github/workflow-samples/no-wrapper${{ matrix.build-root-suffix }} build-root-directory: .github/workflow-samples/no-wrapper${{ matrix.build-root-suffix }}
env:
GITHUB_DEPENDENCY_GRAPH_REF: 'refs/tags/v0.0.1' # Use a different ref to avoid updating the real dependency graph for the repository
after-setup-gradle: with-setup-gradle:
strategy: runs-on: ubuntu-latest # Test is not compatible with Windows
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -245,6 +242,59 @@ jobs:
uses: ./dependency-submission uses: ./dependency-submission
with: with:
build-root-directory: .github/workflow-samples/groovy-dsl build-root-directory: .github/workflow-samples/groovy-dsl
- name: Check and delete generated dependency graph
shell: bash
run: |
if [ ! -e "${{ steps.dependency-submission.outputs.dependency-graph-file }}" ]; then
echo "Did not find generated dependency graph files"
exit 1
fi
rm ${{ steps.dependency-submission.outputs.dependency-graph-file }}*
- name: Run Gradle build
run: ./gradlew build
working-directory: .github/workflow-samples/groovy-dsl
- name: Check no dependency graph is generated
shell: bash
run: |
if [ ! -z "$(ls -A dependency-graph-reports)" ]; then
echo "Expected no dependency graph files to be generated"
ls -l dependency-graph-reports
exit 1
fi
with-includes-and-excludes:
runs-on: ubuntu-latest # Test is not compatible with Windows
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Generate and submit dependencies
id: dependency-submission
uses: ./dependency-submission
with:
build-root-directory: .github/workflow-samples/groovy-dsl
dependency-graph-exclude-projects: excluded-project
dependency-graph-include-projects: included-project
dependency-graph-exclude-configurations: excluded-configuration
dependency-graph-include-configurations: included-configuration
- name: Check generated dependency graph and env vars
shell: bash
run: |
if [ ! -e "${{ steps.dependency-submission.outputs.dependency-graph-file }}" ]; then
echo "Did not find generated dependency graph file"
exit 1
fi
if [ "$DEPENDENCY_GRAPH_EXCLUDE_PROJECTS" != "excluded-project" ] ||
[ "$DEPENDENCY_GRAPH_INCLUDE_PROJECTS" != "included-project" ] ||
[ "$DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS" != "excluded-configuration" ] ||
[ "$DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS" != "included-configuration" ]; then
echo "Did not set expected environment variables"
exit 1
fi
custom-report-dir-submit: custom-report-dir-submit:
strategy: strategy:
@@ -263,9 +313,8 @@ jobs:
uses: ./dependency-submission uses: ./dependency-submission
with: with:
dependency-graph: generate-and-submit dependency-graph: generate-and-submit
dependency-graph-report-dir: '${{ github.workspace }}/custom/report-dir'
build-root-directory: .github/workflow-samples/groovy-dsl build-root-directory: .github/workflow-samples/groovy-dsl
env:
DEPENDENCY_GRAPH_REPORT_DIR: '${{ github.workspace }}/custom/report-dir'
- name: Check generated dependency graphs - name: Check generated dependency graphs
shell: bash shell: bash
run: | run: |
@@ -294,9 +343,8 @@ jobs:
uses: ./dependency-submission uses: ./dependency-submission
with: with:
dependency-graph: generate-and-upload dependency-graph: generate-and-upload
dependency-graph-report-dir: '${{ github.workspace }}/custom/report-dir'
build-root-directory: .github/workflow-samples/groovy-dsl build-root-directory: .github/workflow-samples/groovy-dsl
env:
DEPENDENCY_GRAPH_REPORT_DIR: '${{ github.workspace }}/custom/report-dir'
custom-report-dir-download-and-submit: custom-report-dir-download-and-submit:
needs: custom-report-dir-upload needs: custom-report-dir-upload
@@ -311,9 +359,11 @@ jobs:
uses: ./dependency-submission uses: ./dependency-submission
with: with:
dependency-graph: download-and-submit dependency-graph: download-and-submit
dependency-graph-report-dir: '${{ github.workspace }}/custom/report-dir'
build-root-directory: .github/workflow-samples/groovy-dsl build-root-directory: .github/workflow-samples/groovy-dsl
env: env:
DEPENDENCY_GRAPH_REPORT_DIR: '${{ github.workspace }}/custom/report-dir' DEPENDENCY_GRAPH_DOWNLOAD_ARTIFACT_NAME: custom-report-dir-upload # For testing, to avoid downloading artifacts from other worklfows
- name: Check downloaded dependency graph - name: Check downloaded dependency graph
shell: bash shell: bash
run: | run: |

View File

@@ -1,60 +0,0 @@
name: Test execution with caching
on:
workflow_call:
inputs:
cache-key-prefix:
type: string
runner-os:
type: string
default: '["ubuntu-latest", "windows-latest", "macos-latest"]'
skip-dist:
type: boolean
default: false
env:
SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: execution-with-caching-${{ inputs.cache-key-prefix }}
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
seed-build:
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Execute Gradle build
uses: ./setup-gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
build-root-directory: .github/workflow-samples/groovy-dsl
arguments: test
# Test that the gradle-user-home is restored
verify-build:
needs: seed-build
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Execute Gradle build
uses: ./setup-gradle
with:
cache-read-only: true
build-root-directory: .github/workflow-samples/groovy-dsl
arguments: test --offline -DverifyCachedBuild=true

View File

@@ -1,97 +0,0 @@
name: Test execution
on:
workflow_call:
inputs:
cache-key-prefix:
type: string
runner-os:
type: string
default: '["ubuntu-latest", "windows-latest", "macos-latest"]'
skip-dist:
type: boolean
default: false
env:
SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: execution-${{ inputs.cache-key-prefix }}
jobs:
# Tests for executing with different Gradle versions.
# Each build verifies that it is executed with the expected Gradle version.
gradle-execution:
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Test use defined Gradle version
uses: ./setup-gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
gradle-version: 6.9
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help -DgradleVersionCheck=6.9
- name: Test use Gradle version alias
uses: ./setup-gradle
with:
gradle-version: release-candidate
build-root-directory: .github/workflow-samples/no-wrapper
arguments: help
- name: Test with non-executable wrapper
uses: ./setup-gradle
with:
gradle-version: wrapper
build-root-directory: .github/workflow-samples/non-executable-wrapper
arguments: help
gradle-versions:
strategy:
fail-fast: false
matrix:
gradle: [7.5.1, 6.9.2, 5.6.4, 4.10.3, 3.5.1]
os: ${{fromJSON(inputs.runner-os)}}
include:
- gradle: 5.6.4
build-root-suffix: -gradle-5
- gradle: 4.10.3
build-root-suffix: -gradle-4
- gradle: 3.5.1
build-root-suffix: -gradle-4
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
- name: Run Gradle build
uses: ./setup-gradle
id: gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
gradle-version: ${{matrix.gradle}}
build-root-directory: .github/workflow-samples/no-wrapper${{ matrix.build-root-suffix }}
arguments: help -DgradleVersionCheck=${{matrix.gradle}}
- name: Check Build Scan url
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v7
with:
script: |
core.setFailed('No Build Scan detected')

View File

@@ -26,14 +26,19 @@ jobs:
DEVELOCITY_URL: https://ge.solutions-team.gradle.com DEVELOCITY_URL: https://ge.solutions-team.gradle.com
DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }} DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }}
DEVELOCITY_CCUD_PLUGIN_VERSION: '2.0' DEVELOCITY_CCUD_PLUGIN_VERSION: '2.0'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} # required to test against GE plugin 3.16.2 ${{matrix.accessKeyEnv}}: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
gradle: [current, 7.6.2, 6.9.4, 5.6.4] gradle: [current, 7.6.2, 6.9.4, 5.6.4]
os: ${{fromJSON(inputs.runner-os)}} os: ${{fromJSON(inputs.runner-os)}}
plugin-version: [3.16.2, 3.17.2] plugin-version: [3.16.2, 3.17.6]
include:
- plugin-version: 3.16.2
accessKeyEnv: GRADLE_ENTERPRISE_ACCESS_KEY
- plugin-version: 3.17.6
accessKeyEnv: DEVELOCITY_ACCESS_KEY
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -45,7 +50,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: temurin distribution: temurin
java-version: 8 java-version: 11
- name: Setup Gradle - name: Setup Gradle
id: setup-gradle id: setup-gradle
uses: ./setup-gradle uses: ./setup-gradle
@@ -62,3 +67,135 @@ jobs:
with: with:
script: | script: |
core.setFailed('No Build Scan detected') core.setFailed('No Build Scan detected')
- name: Check short lived token (DEVELOCITY_ACCESS_KEY)
run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)"
- name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY)
run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)"
inject-develocity-with-access-key:
env:
DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: 'https://ge.solutions-team.gradle.com'
DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }}
DEVELOCITY_CCUD_PLUGIN_VERSION: '2.0'
strategy:
fail-fast: false
matrix:
gradle: [current, 7.6.2, 6.9.4, 5.6.4]
os: ${{fromJSON(inputs.runner-os)}}
plugin-version: [3.16.2, 3.17.6]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
- name: Setup Gradle
id: setup-gradle
uses: ./setup-gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
gradle-version: ${{ matrix.gradle }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
develocity-token-expiry: 1
- name: Run Gradle build
id: gradle
working-directory: .github/workflow-samples/no-ge
run: gradle help
- name: Check short lived token (DEVELOCITY_ACCESS_KEY)
run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)"
- name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY)
run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)"
- name: Check Build Scan url
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v7
with:
script: |
core.setFailed('No Build Scan detected')
inject-develocity-short-lived-token-failed:
env:
DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: 'https://localhost:3333/'
DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }}
DEVELOCITY_CCUD_PLUGIN_VERSION: '2.0'
# Access key also set as an env var, we want to check it does not leak
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
strategy:
fail-fast: false
matrix:
gradle: [ current, 7.6.2, 6.9.4, 5.6.4 ]
os: ${{fromJSON(inputs.runner-os)}}
plugin-version: [ 3.16.2, 3.17.6 ]
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
- name: Setup Gradle
id: setup-gradle
uses: ./setup-gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- name: Run Gradle build
id: gradle
working-directory: .github/workflow-samples/no-ge
run: gradle help
- name: Check access key is not blank (DEVELOCITY_ACCESS_KEY)
run: "[ \"${DEVELOCITY_ACCESS_KEY}\" != \"\" ] || (echo 'using DEVELOCITY_ACCESS_KEY!'; exit 1)"
- name: Check access key is not blank (GRADLE_ENTERPRISE_ACCESS_KEY)
run: "[ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"\" ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY is still supported in v3!'; exit 1)"
inject-develocity-with-access-key-from-input-actions:
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
strategy:
fail-fast: false
matrix:
gradle: [ current, 7.6.2, 6.9.4, 5.6.4 ]
os: ${{fromJSON(inputs.runner-os)}}
plugin-version: [ 3.16.2, 3.17.6 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 8
- name: Setup Gradle
id: setup-gradle
uses: ./setup-gradle
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
gradle-version: ${{ matrix.gradle }}
develocity-injection-enabled: true
develocity-url: 'https://ge.solutions-team.gradle.com'
develocity-plugin-version: ${{ matrix.plugin-version }}
- name: Run Gradle build
id: gradle
working-directory: .github/workflow-samples/no-ge
run: gradle help
- name: Check Build Scan url
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v7
with:
script: |
core.setFailed('No Build Scan detected')

View File

@@ -25,9 +25,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: ${{fromJSON(inputs.runner-os)}} os: ${{fromJSON(inputs.runner-os)}}
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -62,6 +59,9 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
gradle-version: current gradle-version: current
- name: Test use current
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
- name: Check current version output parameter - name: Check current version output parameter
if: ${{ !startsWith(steps.gradle-current.outputs.gradle-version , '8.') }} if: ${{ !startsWith(steps.gradle-current.outputs.gradle-version , '8.') }}
uses: actions/github-script@v7 uses: actions/github-script@v7
@@ -73,15 +73,20 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
gradle: [7.3, 6.9, 5.6.4, 4.10.3, 3.5.1] gradle: [8.9, 8.8, 7.6.4, 6.9.4, 5.6.4, 4.10.3, 3.5.1] # 8.8 is the latest installed on windows runners
os: ${{fromJSON(inputs.runner-os)}} os: ${{fromJSON(inputs.runner-os)}}
include: include:
- java-version: 11
- gradle: 5.6.4 - gradle: 5.6.4
build-root-suffix: -gradle-5 build-root-suffix: -gradle-5
- gradle: 4.10.3 - gradle: 4.10.3
build-root-suffix: -gradle-4 build-root-suffix: -gradle-4
- gradle: 3.5.1 - gradle: 3.5.1
build-root-suffix: -gradle-4 build-root-suffix: -gradle-4
java-version: 8
exclude:
- os: macos-latest # Java 8 is not supported on macos-latest, so we cannot test Gradle 3.5.1
gradle: 3.5.1
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
@@ -93,7 +98,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: temurin distribution: temurin
java-version: 8 java-version: ${{ matrix.java-version }}
- name: Setup Gradle - name: Setup Gradle
id: setup-gradle id: setup-gradle
uses: ./setup-gradle uses: ./setup-gradle

View File

@@ -43,6 +43,7 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-write-only: true # Ensure we start with a clean cache entry
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6 gradle-version: 8.6
- name: Groovy build with configuration-cache enabled - name: Groovy build with configuration-cache enabled
@@ -52,6 +53,7 @@ jobs:
verify-build-groovy: verify-build-groovy:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_1
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
fail-fast: false fail-fast: false
@@ -64,6 +66,47 @@ jobs:
- name: Initialize integ-test - name: Initialize integ-test
uses: ./.github/actions/init-integ-test uses: ./.github/actions/init-integ-test
- name: Setup Java to ensure consistency
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Setup Gradle
uses: ./setup-gradle
with:
cache-read-only: false
cache-cleanup: on-success
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6
- name: Groovy build with configuration-cache enabled
id: execute
working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache
- name: Verify configuration-cache hit
shell: bash
run: |
if [ -e ".github/workflow-samples/groovy-dsl/task-configured.txt" ]; then
echo "Configuration cache was not used - task was configured unexpectedly"
exit 1
fi
# Ensure that cache-cleanup doesn't remove all necessary files
verify-no-cache-cleanup-groovy:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_2
needs: verify-build-groovy
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java to ensure consistency - name: Setup Java to ensure consistency
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
@@ -79,19 +122,19 @@ jobs:
id: execute id: execute
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache run: gradle test --configuration-cache
- name: Check that configuration-cache was used - name: Verify configuration-cache hit
uses: actions/github-script@v7 shell: bash
with: run: |
script: | if [ -e ".github/workflow-samples/groovy-dsl/task-configured.txt" ]; then
const fs = require('fs') echo "Configuration cache was not used - task was configured unexpectedly"
if (fs.existsSync('.github/workflow-samples/groovy-dsl/task-configured.txt')) { exit 1
core.setFailed('Configuration cache was not used - task was configured unexpectedly') fi
}
# Check that the build can run when no extracted cache entries are restored # Check that the build can run when no extracted cache entries are restored
gradle-user-home-not-fully-restored: gradle-user-home-not-fully-restored:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_x
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
fail-fast: false fail-fast: false
@@ -144,6 +187,7 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-write-only: true # Ensure we start with a clean cache entry
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6 gradle-version: 8.6
- name: Execute 'help' with configuration-cache enabled - name: Execute 'help' with configuration-cache enabled
@@ -152,7 +196,8 @@ jobs:
modify-build-kotlin: modify-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin-modified GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_1
needs: seed-build-kotlin needs: seed-build-kotlin
strategy: strategy:
fail-fast: false fail-fast: false
@@ -183,7 +228,8 @@ jobs:
# Test restore configuration-cache from the third build invocation # Test restore configuration-cache from the third build invocation
verify-build-kotlin: verify-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin-modified GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_2
needs: modify-build-kotlin needs: modify-build-kotlin
strategy: strategy:
fail-fast: false fail-fast: false
@@ -211,12 +257,10 @@ jobs:
id: execute id: execute
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: gradle test --configuration-cache run: gradle test --configuration-cache
- name: Check that configuration-cache was used - name: Verify configuration-cache hit
uses: actions/github-script@v7 shell: bash
with: run: |
script: | if [ -e ".github/workflow-samples/kotlin-dsl/task-configured.txt" ]; then
const fs = require('fs') echo "Configuration cache was not used - task was configured unexpectedly"
if (fs.existsSync('.github/workflow-samples/kotlin-dsl/task-configured.txt')) { exit 1
core.setFailed('Configuration cache was not used - task was configured unexpectedly') fi
}

View File

@@ -29,8 +29,8 @@ jobs:
- name: Run wrapper-validation-action - name: Run wrapper-validation-action
id: setup-gradle id: setup-gradle
uses: ./setup-gradle uses: ./setup-gradle
with: env:
validate-wrappers: true ALLOWED_GRADLE_WRAPPER_CHECKSUMS: ''
continue-on-error: true continue-on-error: true
- name: Check failure - name: Check failure

View File

@@ -6,21 +6,6 @@ The `build` script in the project root provides a convenient way to perform many
3. `./build init-scripts` will run the init-script integration tests 3. `./build init-scripts` will run the init-script integration tests
4. `./build act <act-commands>` will run `act` after building local changes (see below) 4. `./build act <act-commands>` will run `act` after building local changes (see below)
## How to merge a Dependabot PR
The "distribution" for a GitHub Action is checked into the repository itself.
In the case of these actions, the transpiled sources are committed to the `dist` directory.
Any production dependencies are inlined into the distribution.
So if a Dependabot PR updates a production dependency (or a dev dependency that changes the distribution, like the Typescript compiler),
then a manual step is required to rebuild the dist and commit.
The simplest process to follow is:
1. Checkout the dependabot branch locally eg: `git checkout dependabot/npm_and_yarn/actions/github-5.1.0`
2. In the `sources` directory, run `npm install` to download NPM dependencies
3. In the `sources` directory, run `npm run build` to regenerate the distribution
4. Push the changes to the dependabot branch
5. If/when the checks pass, you can merge the dependabot PR
## Using `act` to run integ-test workflows locally ## Using `act` to run integ-test workflows locally
It's possible to run GitHub Actions workflows locally with https://nektosact.com/. It's possible to run GitHub Actions workflows locally with https://nektosact.com/.
@@ -36,7 +21,6 @@ Example running a single job:
`./build act -W .github/workflows/integ-test-caching-config.yml -j cache-disabled-pre-existing-gradle-home` `./build act -W .github/workflows/integ-test-caching-config.yml -j cache-disabled-pre-existing-gradle-home`
Known issues: Known issues:
- `integ-test-cache-cleanup.yml` fails because `gradle` is not installed on the runner. Should be fixed by #33.
- `integ-test-detect-java-toolchains.yml` fails when running on a `linux/amd64` container, since the expected pre-installed JDKs are not present. Should be fixed by #89. - `integ-test-detect-java-toolchains.yml` fails when running on a `linux/amd64` container, since the expected pre-installed JDKs are not present. Should be fixed by #89.
- `act` is not yet compatible with `actions/upload-artifact@v4` (or related toolkit functions) - `act` is not yet compatible with `actions/upload-artifact@v4` (or related toolkit functions)
- See https://github.com/nektos/act/pull/2224 - See https://github.com/nektos/act/pull/2224

10
build
View File

@@ -5,7 +5,7 @@ cd sources
case "$1" in case "$1" in
all) all)
npm clean-install npm clean-install
nprm run all npm run all
;; ;;
act) act)
# Build and copy outputs to the dist directory # Build and copy outputs to the dist directory
@@ -16,12 +16,18 @@ case "$1" in
# Run act # Run act
$@ $@
# Revert the changes to the dist directory # Revert the changes to the dist directory
git co -- dist git checkout -- dist
;; ;;
init-scripts) init-scripts)
cd test/init-scripts cd test/init-scripts
./gradlew check ./gradlew check
;; ;;
dist)
npm install
npm run build
cd ..
cp -r sources/dist .
;;
*) *)
npm install npm install
npm run build npm run build

View File

@@ -57,6 +57,19 @@ inputs:
Configuration-cache data will not be saved/restored without an encryption key being provided. Configuration-cache data will not be saved/restored without an encryption key being provided.
required: false required: false
cache-cleanup:
description: |
Specifies if the action should attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
By default, no cleanup is performed. It can be configured to run every time, or only when all Gradle builds succeed for the Job.
Valid values are 'never', 'on-success' and 'always'.
required: false
default: 'on-success'
gradle-home-cache-cleanup:
description: When 'true', the action will attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
required: false
deprecation-message: This input has been superceded by the 'cache-cleanup' input parameter.
gradle-home-cache-includes: gradle-home-cache-includes:
description: Paths within Gradle User Home to cache. description: Paths within Gradle User Home to cache.
required: false required: false
@@ -68,11 +81,6 @@ inputs:
description: Paths within Gradle User Home to exclude from cache. description: Paths within Gradle User Home to exclude from cache.
required: false required: false
gradle-home-cache-cleanup:
description: When 'true', the action will attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
required: false
default: false
# Job summary configuration # Job summary configuration
add-job-summary: add-job-summary:
description: Specifies when a Job Summary should be inluded in the action results. Valid values are 'never', 'always' (default), and 'on-failure'. description: Specifies when a Job Summary should be inluded in the action results. Valid values are 'never', 'always' (default), and 'on-failure'.
@@ -99,11 +107,42 @@ inputs:
required: false required: false
default: 'generate-and-submit' default: 'generate-and-submit'
dependency-graph-report-dir:
description: |
Specifies where the dependency graph report will be generated.
Paths can relative or absolute. Relative paths are resolved relative to the workspace directory.
required: false
default: 'dependency-graph-reports'
dependency-graph-continue-on-failure: dependency-graph-continue-on-failure:
description: When 'false' a failure to generate or submit a dependency graph will fail the Step or Job. When 'true' a warning will be emitted but no failure will result. description: When 'false' a failure to generate or submit a dependency graph will fail the Step or Job. When 'true' a warning will be emitted but no failure will result.
required: false required: false
default: false default: false
dependency-graph-exclude-projects:
description: |
Gradle projects that should be excluded from dependency graph (regular expression).
When set, any matching project will be excluded.
required: false
dependency-graph-include-projects:
description: |
Gradle projects that should be included in dependency graph (regular expression).
When set, only matching projects will be included.
required: false
dependency-graph-exclude-configurations:
description: |
Gradle configurations that should be included in dependency graph (regular expression).
When set, anymatching configurations will be excluded.
required: false
dependency-graph-include-configurations:
description: |
Gradle configurations that should be included in dependency graph (regular expression).
When set, only matching configurations will be included.
required: false
artifact-retention-days: artifact-retention-days:
description: Specifies the number of days to retain any artifacts generated by the action. If not set, the default retention settings for the repository will apply. description: Specifies the number of days to retain any artifacts generated by the action. If not set, the default retention settings for the repository will apply.
required: false required: false
@@ -125,22 +164,30 @@ inputs:
description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes". description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes".
required: false required: false
develocity-access-key:
description: Develocity access key. Should be set to a secret containing the Develocity Access key.
required: false
develocity-token-expiry:
description: The Develocity short-lived access tokens expiry in hours. Default is 2 hours.
required: false
# Wrapper validation configuration
validate-wrappers:
description: |
When 'true' the action will automatically validate all wrapper jars found in the repository.
If the wrapper checksums are not valid, the action will fail.
required: false
default: false
allow-snapshot-wrappers:
description: |
When 'true', wrapper validation will include the checksums of snapshot wrapper jars.
Use this if you are running with nightly or snapshot versions of the Gradle wrapper.
required: false
default: false
# DEPRECATED ACTION INPUTS # DEPRECATED ACTION INPUTS
build-scan-terms-of-service-url:
description: The URL to the Build Scan® terms of use. This input must be set to 'https://gradle.com/terms-of-service'.
required: false
deprecation-message: The input has been renamed to align with the Develocity API. Use 'build-scan-terms-of-use-url' instead.
build-scan-terms-of-service-agree:
description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes".
required: false
deprecation-message: The input has been renamed to align with the Develocity API. Use 'build-scan-terms-of-use-agree' instead.
generate-job-summary:
description: When 'false', no Job Summary will be generated for the Job.
required: false
default: true
deprecation-message: Superceded by the new 'add-job-summary' and 'add-job-summary-as-pr-comment' parameters.
# EXPERIMENTAL ACTION INPUTS # EXPERIMENTAL ACTION INPUTS
# The following action properties allow fine-grained tweaking of the action caching behaviour. # The following action properties allow fine-grained tweaking of the action caching behaviour.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ The generated dependency graph includes all of the dependencies in your build, a
for vulnerable dependencies, as well as to populate the for vulnerable dependencies, as well as to populate the
[Dependency Graph insights view](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/exploring-the-dependencies-of-a-repository#viewing-the-dependency-graph). [Dependency Graph insights view](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/exploring-the-dependencies-of-a-repository#viewing-the-dependency-graph).
If your confused by the behaviour you're seeing or have specific questions, please check out [the FAQ](dependency-submission-faq.md) before raising an issue. If you're confused by the behaviour you're seeing or have specific questions, please check out [the FAQ](dependency-submission-faq.md) before raising an issue.
## General usage ## General usage
@@ -95,13 +95,20 @@ In some cases, the default action configuration will not be sufficient, and addi
dependency-resolution-task: myDependencyResolutionTask dependency-resolution-task: myDependencyResolutionTask
# Additional arguments that should be passed to execute Gradle # Additional arguments that should be passed to execute Gradle
additonal-arguments: --no-configuration-cache additional-arguments: --no-configuration-cache
# Enable configuration-cache reuse for this build. # Enable configuration-cache reuse for this build.
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
# Do not attempt to submit the dependency-graph. Save it as a workflow artifact. # Do not attempt to submit the dependency-graph. Save it as a workflow artifact.
dependency-graph: generate-and-upload dependency-graph: generate-and-upload
# Specify the location where dependency graph files will be generated.
dependency-graph-report-dir: custom-report-dir
# By default, failure to generate a dependency graph will cause the workflow to fail
dependency-graph-continue-on-failure: true
``` ```
See the [Action Metadata file](../dependency-submission/action.yml) for a more detailed description of each input parameter. See the [Action Metadata file](../dependency-submission/action.yml) for a more detailed description of each input parameter.
@@ -235,26 +242,26 @@ contribute to the dependency graph.
> These dependencies would be assigned to different scopes (eg development, runtime, testing) and the GitHub UI would make it easy to opt-in to security alerts for different dependency scopes. > These dependencies would be assigned to different scopes (eg development, runtime, testing) and the GitHub UI would make it easy to opt-in to security alerts for different dependency scopes.
> However, this functionality does not yet exist. > However, this functionality does not yet exist.
### Excluding certain Gradle projects from the dependency graph ### Selecting Gradle projects that will contribute to the dependency graph
If you do not want the dependency graph to include dependencies from every project in your build, If you do not want the dependency graph to include dependencies from every project in your build,
you can easily exclude certain projects from the dependency extraction process. you can easily exclude or include certain projects from the dependency extraction process.
To restrict which Gradle subprojects contribute to the report, specify which projects to exclude via a regular expression. To restrict which Gradle subprojects contribute to the report, specify which projects to exclude or include via a regular expression.
You can provide this value via the `DEPENDENCY_GRAPH_EXCLUDE_PROJECTS` environment variable or system property. You can use the `dependency-graph-exclude-projects` and `dependency-graph-include-projects` input parameters for this purpose.
Note that excluding a project in this way only removes dependencies that are _resolved_ as part of that project, and may Note that excluding a project in this way only removes dependencies that are _resolved_ as part of that project, and may
not necessarily remove all dependencies _declared_ in that project. If another project depends on the excluded project not necessarily remove all dependencies _declared_ in that project. If another project depends on the excluded project
then it may transitively resolve dependencies declared in the excluded project: these dependencies will still be included then it may transitively resolve dependencies declared in the excluded project: these dependencies will still be included
in the generated dependency graph. in the generated dependency graph.
### Excluding certain Gradle configurations from the dependency graph ### Selecting Gradle configurations that will contribute to the dependency graph
Similarly to Gradle projects, it is possible to exclude a set of configuration instances from dependency graph generation, Similarly to Gradle projects, it is possible to exclude or include a set of dependency configurations from dependency graph generation,
so that dependencies resolved by those configurations are not included. so that only dependencies resolved by the included configurations are reported.
To restrict which Gradle configurations contribute to the report, specify which configurations to exclude via a regular expression. To restrict which Gradle configurations contribute to the report, specify which configurations to exclude or include via a regular expression.
You can provide this value via the `DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS` environment variable or system property. You can use the `dependency-graph-exclude-configurations` and `dependency-graph-include-configurations` input parameters for this purpose.
Note that configuration exclusion applies to the configuration in which the dependency is _resolved_ which is not necessarily Note that configuration exclusion applies to the configuration in which the dependency is _resolved_ which is not necessarily
the configuration where the dependency is _declared_. For example if you decare a dependency as `implementation` in the configuration where the dependency is _declared_. For example if you decare a dependency as `implementation` in
@@ -262,24 +269,18 @@ a Java project, that dependency will be resolved in `compileClasspath`, `runtime
### Example of project and configuration filtering ### Example of project and configuration filtering
For example, if you want to exclude dependencies in the `buildSrc` project, and exclude dependencies from the `testCompileClasspath` and `testRuntimeClasspath` configurations, you would use the following configuration: For example, if you want to exclude dependencies resolved by the `buildSrc` project, and exclude dependencies from the `testCompileClasspath` and `testRuntimeClasspath` configurations, you would use the following configuration:
```yaml ```yaml
- name: Generate and submit dependency graph - name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v3 uses: gradle/actions/dependency-submission@v3
env: with:
# Exclude all dependencies that originate solely in the 'buildSrc' project # Exclude all dependencies that originate solely in the 'buildSrc' project
DEPENDENCY_GRAPH_EXCLUDE_PROJECTS: ':buildSrc' dependency-graph-exclude-projets: ':buildSrc'
# Exclude dependencies that are only resolved in test classpaths # Exclude dependencies that are only resolved in test classpaths
DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS: '.*[Tt]est(Compile|Runtime)Classpath' dependency-graph-exclude-configurations: '.*[Tt]est(Compile|Runtime)Classpath'
``` ```
### Other filtering options
The [GitHub Dependency Graph Gradle Plugin](https://plugins.gradle.org/plugin/org.gradle.github-dependency-graph-gradle-plugin)
has other filtering options that may be useful.
See [the docs](https://github.com/gradle/github-dependency-graph-gradle-plugin?tab=readme-ov-file#filtering-which-gradle-configurations-contribute-to-the-dependency-graph) for details.
# Advance usage scenarios # Advance usage scenarios
## Using a custom plugin repository ## Using a custom plugin repository
@@ -367,6 +368,7 @@ on:
types: [completed] types: [completed]
permissions: permissions:
actions: read
contents: write contents: write
jobs: jobs:
@@ -417,3 +419,10 @@ Gradle versions `5.2.1`, `5.6.4`, `6.0.1`, `6.9.4`, `7.1.1` and `7.6.3`, as well
A known exception to this is that Gradle `7.0`, `7.0.1` and `7.0.2` are not supported. A known exception to this is that Gradle `7.0`, `7.0.1` and `7.0.2` are not supported.
See [here](https://github.com/gradle/github-dependency-graph-gradle-plugin?tab=readme-ov-file#gradle-compatibility) for complete compatibility information. See [here](https://github.com/gradle/github-dependency-graph-gradle-plugin?tab=readme-ov-file#gradle-compatibility) for complete compatibility information.
# Additional references
- Dependency Submission Demo repository: https://github.com/gradle/github-dependency-submission-demo
- GitHub Dependency Graph Gradle Plugin: https://github.com/gradle/github-dependency-graph-gradle-plugin
- Webinar - Gradle at Scale with GitHub and GitHub Actions at Allegro: https://www.youtube.com/watch?v=gV94I28FPos

View File

@@ -101,7 +101,7 @@ The exact syntax depends on whether or not your project is configured with the [
- name: Setup Gradle for a non-wrapper project - name: Setup Gradle for a non-wrapper project
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v3
with: with:
gradle-version: 8.7 gradle-version: 8.9
- name: Assemble the project - name: Assemble the project
run: gradle assemble run: gradle assemble
@@ -143,3 +143,20 @@ to this:
build-scan-terms-of-use-agree: "yes" build-scan-terms-of-use-agree: "yes"
``` ```
These deprecated build-scan parameters are scheduled to be removed in `setup-gradle@v4` and `dependency-submission@v4`. These deprecated build-scan parameters are scheduled to be removed in `setup-gradle@v4` and `dependency-submission@v4`.
## The GRADLE_ENTERPRISE_ACCESS_KEY env var is deprecated
Gradle Enterprise has been renamed to Develocity starting from Gradle plugin 3.17 and Develocity server 2024.1.
In v4 release of the action, it will require setting the access key with the `develocity-access-key` input and Develocity 2024.1 at least to generate short-lived tokens.
If those requirements are not met, the `GRADLE_ENTERPRISE_ACCESS_KEY` env var will be cleared out and build scan publication or other authenticated Develocity operations won't be possible.
## The `gradle-home-cache-cleanup` input parameter has been replaced by `cache-cleanup`
In versions of the action prior to `v4`, the boolean `gradle-home-cache-cleanup` parameter allows users to opt-in
to cache cleanup, removing unused files in Gradle User Home prior to saving to the cache.
With `v4`, cache-cleanup is enabled by default, and controlled by the `cache-cleanup` input parameter.
To remove this deprecation:
- If you are using `gradle-home-cache-cleanup: true` in your workflow, you can remove this option as this is now enabled by default.
- If you want cache-cleanup to run even when a Gradle build fails, then add the `cache-cleanup: always` input.
- If cache-cleanup is causing problems with your workflow, you can disable it with `cache-cleanup: never`.

View File

@@ -153,9 +153,32 @@ In certain circumstances it may be desirable to start with a clean Gradle User H
cache-write-only: true cache-write-only: true
``` ```
### Configuring cache cleanup
The Gradle User Home directory tends to grow over time. When you switch to a new Gradle wrapper version
or upgrade a dependency version the old files are not automatically and immediately removed.
While this can make sense in a local environment, in a GitHub Actions environment
it can lead to ever-larger Gradle User Home cache entries being saved and restored.
To avoid this situation, the `setup-gradle` and `dependency-submission` actions will perform "cache-cleanup",
purging any unused files from the Gradle User Home before saving it to the GitHub Actions cache.
Cache cleanup will attempt to remove any files that are initially restored to the Gradle User Home directory
but that are not used used by Gradle during the GitHub Actions Workflow.
If a Gradle build fails when running the Job, then it is possible that some required files and dependencies
will not be touched during the Job. To prevent these files from being purged, the default behavior is for
cache cleanup to run only when all Gradle builds in the Job are successful.
Gradle Home cache cleanup is enabled by default, and can be controlled by the `cache-cleanup` parameter as follows:
- `cache-cleanup: always`: Always run cache cleanup, even when a Gradle build fails in the Job.
- `cache-cleanup: on-success` (default): Run cache cleanup when the Job contains no failing Gradle builds.
- `cache-cleanup: never`: Disable cache cleanup for the Job.
Cache cleanup will never run when the cache is configured as read-only or disabled.
### Overwriting an existing Gradle User Home ### Overwriting an existing Gradle User Home
When the action detects that the Gradle User Home caches directory already exists (`~/.gradle/caches`), then by default it will not overwrite the existing content of this directory. When the action detects that the Gradle User Home caches directory already exists (`$GRADLE_USER_HOME/caches`), then by default it will not overwrite the existing content of this directory.
This can occur when a prior action initializes this directory, or when using a self-hosted runner that retains this directory between uses. This can occur when a prior action initializes this directory, or when using a self-hosted runner that retains this directory between uses.
In this case, the Job Summary will display a message like: In this case, the Job Summary will display a message like:
@@ -198,7 +221,7 @@ jobs:
``` ```
> [!IMPORTANT] > [!IMPORTANT]
> The configuration cache cannot be saved or restored in workflows triggered by a pull requests from a repsitory fork. > The configuration cache cannot be saved or restored in workflows triggered by a pull requests from a repository fork.
> This is because [GitHub secrets are not passed to workflows triggered by PRs from forks](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow). > This is because [GitHub secrets are not passed to workflows triggered by PRs from forks](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow).
> This prevents a malicious PR from reading the configuration-cache data, which may encode secrets read by Gradle. > This prevents a malicious PR from reading the configuration-cache data, which may encode secrets read by Gradle.
@@ -218,7 +241,7 @@ Using either of these mechanisms may interfere with the caching provided by this
The GitHub Actions cache has some properties that present problems for efficient caching of the Gradle User Home. The GitHub Actions cache has some properties that present problems for efficient caching of the Gradle User Home.
- Immutable entries: once a cache entry is written for a key, it cannot be overwritten or changed. - Immutable entries: once a cache entry is written for a key, it cannot be overwritten or changed.
- Branch scope: cache entries written for a Git branch are not visible from actions running against different branches. Entries written for the default branch are visible to all. https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache - Branch scope: cache entries written for a Git branch are not visible from actions running against different branches or tags. Entries written for the default branch are visible to all. https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
- Restore keys: if no exact match is found, a set of partial keys can be provided that will match by cache key prefix. https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key - Restore keys: if no exact match is found, a set of partial keys can be provided that will match by cache key prefix. https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key
Each of these properties has influenced the design and implementation of the caching in `setup-gradle`, as described below. Each of these properties has influenced the design and implementation of the caching in `setup-gradle`, as described below.
@@ -316,8 +339,8 @@ Some techniques can be used to avoid/mitigate this issue:
### Select which branches should write to the cache ### Select which branches should write to the cache
GitHub cache entries are not shared between builds on different branches. GitHub cache entries are not shared between builds on different branches or tags.
Workflow runs can restore caches created in either the current branch or the default branch (usually main). Workflow runs can _only_ restore caches created in either the same branch or the default branch (usually `main`).
This means that each branch will have its own Gradle User Home cache scope, and will not benefit from cache entries written for other (non-default) branches. This means that each branch will have its own Gradle User Home cache scope, and will not benefit from cache entries written for other (non-default) branches.
By default, The `setup-gradle` action will only _write_ to the cache for builds run on the default (`master`/`main`) branch. By default, The `setup-gradle` action will only _write_ to the cache for builds run on the default (`master`/`main`) branch.
@@ -356,20 +379,6 @@ gradle-home-cache-excludes: |
You can specify any number of fixed paths or patterns to include or exclude. You can specify any number of fixed paths or patterns to include or exclude.
File pattern support is documented at https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#patterns-to-match-file-paths. File pattern support is documented at https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#patterns-to-match-file-paths.
### Remove unused files from Gradle User Home before saving to the cache
The Gradle User Home directory tends to grow over time. When you switch to a new Gradle wrapper version or upgrade a dependency version
the old files are not automatically and immediately removed. While this can make sense in a local environment, in a GitHub Actions environment
it can lead to ever-larger Gradle User Home cache entries being saved and restored.
To avoid this situation, The `setup-gradle` action supports the `gradle-home-cache-cleanup` parameter.
When enabled, this feature will attempt to delete any files in the Gradle User Home that were not used by Gradle during the GitHub Actions Workflow, before saving the Gradle User Home to the GitHub Actions cache.
Gradle Home cache cleanup is considered experimental and is disabled by default. You can enable this feature for the action as follows:
```yaml
gradle-home-cache-cleanup: true
```
### Disable local build-cache when remote build-cache is available ### Disable local build-cache when remote build-cache is available
If you have a remote build-cache available for your build, then it is recommended to do the following: If you have a remote build-cache available for your build, then it is recommended to do the following:
@@ -506,14 +515,30 @@ Since Gradle applies init scripts in alphabetical order, one way to ensure this
## Gradle Wrapper validation ## Gradle Wrapper validation
Instead of using the [wrapper-validation action](./wrapper-validation.md) separately, you can enable By default, this action will perform the same wrapper validation as is performed by the dedicated
wrapper validation directly in your Setup Gradle step. [wrapper-validation action](./wrapper-validation.md).
This means that invalid wrapper jars will be automatically detected when using `setup-gradle`.
If you do not want wrapper-validation to occur automatically, you can disable it:
```yaml
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
validate-wrappers: false
```
If your repository uses snapshot versions of the Gradle wrapper, such as nightly builds, then you'll need to
explicitly allow snapshot wrappers in wrapper validation.
These are not allowed by default.
```yaml ```yaml
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v3
with: with:
validate-wrappers: true validate-wrappers: true
allow-snapshot-wrappers: true
``` ```
If you need more advanced configuration, then you're advised to continue using a separate workflow step If you need more advanced configuration, then you're advised to continue using a separate workflow step
@@ -708,27 +733,49 @@ The same auto-injection behavior is available for the Common Custom User Data Gr
## Enabling Develocity injection ## Enabling Develocity injection
To enable Develocity injection for your build, you must provide the required configuration via environment variables. To enable Develocity injection for your build, you must provide the required configuration via inputs.
Here's a minimal example: Here's a minimal example:
```yaml ```yaml
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v3
with:
develocity-injection-enabled: true
develocity-url: https://develocity.your-server.com
develocity-plugin-version: 3.17.5
- name: Run a Gradle build with Develocity injection enabled
run: ./gradlew build
```
This configuration will automatically apply `v3.17.6` of the [Develocity Gradle plugin](https://docs.gradle.com/develocity/gradle-plugin/), and publish build scans to https://develocity.your-server.com.
This example assumes that the `develocity.your-server.com` server allows anonymous publishing of build scans.
In the likely scenario that your Develocity server requires authentication, you will also need to pass a valid [Develocity access key](https://docs.gradle.com/develocity/gradle-plugin/#via_environment_variable) taken from a secret:
```yaml
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
develocity-access-key: ${{ secrets.MY_DEVELOCITY_ACCESS_KEY }}
- name: Run a Gradle build with Develocity injection enabled - name: Run a Gradle build with Develocity injection enabled
run: ./gradlew build run: ./gradlew build
env: env:
DEVELOCITY_INJECTION_ENABLED: true DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: https://develocity.your-server.com DEVELOCITY_URL: https://develocity.your-server.com
DEVELOCITY_PLUGIN_VERSION: 3.17.2 DEVELOCITY_PLUGIN_VERSION: 3.17
``` ```
This configuration will automatically apply `v3.17.2` of the [Develocity Gradle plugin](https://docs.gradle.com/develocity/gradle-plugin/), and publish build scans to https://develocity.your-server.com. This access key will be used during the action execution to get a short-lived token and set it to the DEVELOCITY_ACCESS_KEY environment variable.
This example assumes that the `develocity.your-server.com` server allows anonymous publishing of build scans. ### Short-lived access tokens
In the likely scenario that your Develocity server requires authentication, you will also need to configure an additional environment variable Develocity access keys are long-lived, creating risks if they are leaked. To avoid this, users can use short-lived access tokens to authenticate with Develocity. Access tokens can be used wherever an access key would be used. Access tokens are only valid for the Develocity instance that created them.
with a valid [Develocity access key](https://docs.gradle.com/develocity/gradle-plugin/#via_environment_variable). If a short-lived token fails to be retrieved (for example, if the Develocity server version is lower than `2024.1`):
- if a `GRADLE_ENTERPRISE_ACCESS_KEY` env var has been set, we're falling back to it with a deprecation warning
- otherwise no access key env var will be set. In that case Develocity authenticated operations like build cache read/write and build scan publication will fail without failing the build.
For more information on short-lived tokens, see [Develocity API documentation](https://docs.gradle.com/develocity/api-manual/#short_lived_access_tokens).
## Configuring Develocity injection ## Configuring Develocity injection
@@ -736,16 +783,46 @@ The `init-script` supports several additional configuration parameters that you
| Variable | Required | Description | | Variable | Required | Description |
|--------------------------------------| --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------------------------------| --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DEVELOCITY_INJECTION_ENABLED | :white_check_mark: | enables Develocity injection | | develocity-injection-enabled | :white_check_mark: | enables Develocity injection |
| DEVELOCITY_URL | :white_check_mark: | the URL of the Develocity server | | develocity-url | :white_check_mark: | the URL of the Develocity server |
| DEVELOCITY_ALLOW_UNTRUSTED_SERVER | | allow communication with an untrusted server; set to _true_ if your Develocity instance is using a self-signed certificate | | develocity-allow-untrusted-server | | allow communication with an untrusted server; set to _true_ if your Develocity instance is using a self-signed certificate |
| DEVELOCITY_CAPTURE_FILE_FINGERPRINTS | | enables capturing the paths and content hashes of each individual input file | | develocity-capture-file-fingerprints | | enables capturing the paths and content hashes of each individual input file |
| DEVELOCITY_ENFORCE_URL | | enforce the configured Develocity URL over a URL configured in the project's build; set to _true_ to enforce publication of build scans to the configured Develocity URL | | develocity-enforce-url | | enforce the configured Develocity URL over a URL configured in the project's build; set to _true_ to enforce publication of build scans to the configured Develocity URL |
| DEVELOCITY_PLUGIN_VERSION | :white_check_mark: | the version of the [Develocity Gradle plugin](https://docs.gradle.com/develocity/gradle-plugin/) to apply | | develocity-plugin-version | :white_check_mark: | the version of the [Develocity Gradle plugin](https://docs.gradle.com/develocity/gradle-plugin/) to apply |
| DEVELOCITY_CCUD_PLUGIN_VERSION | | the version of the [Common Custom User Data Gradle plugin](https://github.com/gradle/common-custom-user-data-gradle-plugin) to apply, if any | | develocity-ccud-plugin-version | | the version of the [Common Custom User Data Gradle plugin](https://github.com/gradle/common-custom-user-data-gradle-plugin) to apply, if any |
| GRADLE_PLUGIN_REPOSITORY_URL | | the URL of the repository to use when resolving the Develocity and CCUD plugins; the Gradle Plugin Portal is used by default | | gradle-plugin-repository-url | | the URL of the repository to use when resolving the Develocity and CCUD plugins; the Gradle Plugin Portal is used by default |
| GRADLE_PLUGIN_REPOSITORY_USERNAME | | the username for the repository URL to use when resolving the Develocity and CCUD plugins | | gradle-plugin-repository-username | | the username for the repository URL to use when resolving the Develocity and CCUD plugins |
| GRADLE_PLUGIN_REPOSITORY_PASSWORD | | the password for the repository URL to use when resolving the Develocity and CCUD plugins; Consider using secrets to pass the value to this variable | | gradle-plugin-repository-password | | the password for the repository URL to use when resolving the Develocity and CCUD plugins; Consider using secrets to pass the value to this variable |
The input parameters can be expressed as environment variables following the relationships outlined in the table below:
| Input | Environment Variable |
|--------------------------------------|--------------------------------------|
| develocity-injection-enabled | DEVELOCITY_INJECTION_ENABLED |
| develocity-url | DEVELOCITY_URL |
| develocity-allow-untrusted-server | DEVELOCITY_ALLOW_UNTRUSTED_SERVER |
| develocity-capture-file-fingerprints | DEVELOCITY_CAPTURE_FILE_FINGERPRINTS |
| develocity-enforce-url | DEVELOCITY_ENFORCE_URL |
| develocity-plugin-version | DEVELOCITY_PLUGIN_VERSION |
| develocity-ccud-plugin-version | DEVELOCITY_CCUD_PLUGIN_VERSION |
| gradle-plugin-repository-url | GRADLE_PLUGIN_REPOSITORY_URL |
| gradle-plugin-repository-username | GRADLE_PLUGIN_REPOSITORY_USERNAME |
| gradle-plugin-repository-password | GRADLE_PLUGIN_REPOSITORY_PASSWORD |
Here's an example using the env vars:
```yaml
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Run a Gradle build with Develocity injection enabled with environment variables
run: ./gradlew build
env:
DEVELOCITY_INJECTION_ENABLED: true
DEVELOCITY_URL: https://develocity.your-server.com
DEVELOCITY_PLUGIN_VERSION: 3.17.6
```
## Publishing to scans.gradle.com ## Publishing to scans.gradle.com
@@ -765,3 +842,21 @@ To publish to https://scans.gradle.com, you must specify in your workflow that y
- name: Run a Gradle build - a build scan will be published automatically - name: Run a Gradle build - a build scan will be published automatically
run: ./gradlew build run: ./gradlew build
``` ```
# Dependency verification
Develocity injection, Build Scan publishing and Dependency Graph generation all work by applying external plugins to your build.
If you project has [dependency verification enabled](https://docs.gradle.org/current/userguide/dependency_verification.html#sec:signature-verification),
then you'll need to update your verification metadata to trust these plugins.
Each of the plugins is signed by Gradle, and you can simply add the following snippet to your `dependency-verificaton.xml` file:
```xml
<trusted-keys>
<trusted-key id="7B79ADD11F8A779FE90FD3D0893A028475557671">
<trusting group="com.gradle"/>
<trusting group="org.gradle"/>
</trusted-key>
</trusted-keys>
```

View File

@@ -4,6 +4,9 @@ This action validates the checksums of _all_ [Gradle Wrapper](https://docs.gradl
The action should be run in the root of the repository, as it will recursively search for any files named `gradle-wrapper.jar`. The action should be run in the root of the repository, as it will recursively search for any files named `gradle-wrapper.jar`.
The `setup-gradle` action will perform wrapper validation on each execution. If you are using `setup-gradle` in your
workflows, it is unlikely that you will need to use this action.
## The Gradle Wrapper Problem in Open Source ## The Gradle Wrapper Problem in Open Source
The `gradle-wrapper.jar` is a binary blob of executable code that is checked into nearly The `gradle-wrapper.jar` is a binary blob of executable code that is checked into nearly
@@ -90,18 +93,22 @@ We recommend the message commit contents of:
From there, you can easily follow the rest of the prompts to create a Pull Request against the project. From there, you can easily follow the rest of the prompts to create a Pull Request against the project.
## Reporting Failures ## Validation Failures
If this GitHub action fails because a `gradle-wrapper.jar` doesn't match one of our published SHA-256 checksums, A wrapper jar can fail validation for a few reasons:
1. The wrapper is from a snapshot build of Gradle (nightly or release nightly) and you have not set `allow-snapshots`
or `allow-snapshot-wrappers` to `true`.
2. The wrapper jar is from a version of Gradle with an unverifiable wrapper jar (see below).
3. The wrapper jar was not published by Gradle, and could be compromised.
If this GitHub action fails because a `gradle-wrapper.jar` was not published by Gradle,
we highly recommend that you reach out to us at [security@gradle.com](mailto:security@gradle.com). we highly recommend that you reach out to us at [security@gradle.com](mailto:security@gradle.com).
**Note:** `gradle-wrapper.jar` generated by Gradle 3.3 to 4.0 are not verifiable because those files were dynamically generated by Gradle in a non-reproducible way. It's not possible to verify the `gradle-wrapper.jar` for those versions are legitimate using a hash comparison. You should try to determine if the `gradle-wrapper.jar` was generated by one of these versions before running the build. #### Unverifiable Wrapper Jars
Wrapper Jars generated by Gradle versions `3.3` to `4.0` are not verifiable because those files were dynamically generated by Gradle in a non-reproducible way. It's not possible to verify the `gradle-wrapper.jar` for those versions are legitimate using a hash comparison. If you have a validation failure, you should try to determine if the `gradle-wrapper.jar` was generated by one of these versions before running the build.
If the Gradle version in `gradle-wrapper.properties` is out of this range, you may need to regenerate the `gradle-wrapper.jar` by running `./gradlew wrapper`. If you need to use a version of Gradle between 3.3 and 4.0, you can use a newer version of Gradle to generate the `gradle-wrapper.jar`. - If the Gradle version in `gradle-wrapper.properties` is outside of this range, you can regenerate the `gradle-wrapper.jar` by running `./gradlew wrapper`. This will generate a new, verifiable wrapper jar.
- If you need to run your build with a version of Gradle between 3.3 and 4.0, you can use a newer version of Gradle to generate the `gradle-wrapper.jar`.
If you're curious and want to explore what the differences are between the `gradle-wrapper.jar` in your possession
and one of our valid release, you can compare them using this online utility: [diffoscope](https://try.diffoscope.org/).
Regardless of what you find, we still kindly request that you reach out to us and let us know.
## Resources ## Resources

View File

@@ -40,6 +40,19 @@ inputs:
Configuration-cache data will not be saved/restored without an encryption key being provided. Configuration-cache data will not be saved/restored without an encryption key being provided.
required: false required: false
cache-cleanup:
description: |
Specifies if the action should attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
By default, no cleanup is performed. It can be configured to run every time, or only when all Gradle builds succeed for the Job.
Valid values are 'never', 'on-success' and 'always'.
required: false
default: 'on-success'
gradle-home-cache-cleanup:
description: When 'true', the action will attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
required: false
deprecation-message: This input has been superceded by the 'cache-cleanup' input parameter.
gradle-home-cache-includes: gradle-home-cache-includes:
description: Paths within Gradle User Home to cache. description: Paths within Gradle User Home to cache.
required: false required: false
@@ -51,11 +64,6 @@ inputs:
description: Paths within Gradle User Home to exclude from cache. description: Paths within Gradle User Home to exclude from cache.
required: false required: false
gradle-home-cache-cleanup:
description: When 'true', the action will attempt to remove any stale/unused entries from the Gradle User Home prior to saving to the GitHub Actions cache.
required: false
default: false
# Job summary configuration # Job summary configuration
add-job-summary: add-job-summary:
description: Specifies when a Job Summary should be inluded in the action results. Valid values are 'never', 'always' (default), and 'on-failure'. description: Specifies when a Job Summary should be inluded in the action results. Valid values are 'never', 'always' (default), and 'on-failure'.
@@ -71,15 +79,46 @@ inputs:
dependency-graph: dependency-graph:
description: | description: |
Specifies if a GitHub dependency snapshot should be generated for each Gradle build, and if so, how. Specifies if a GitHub dependency snapshot should be generated for each Gradle build, and if so, how.
Valid values are 'disabled' (default), 'generate', 'generate-and-submit', 'generate-and-upload', 'download-and-submit' and 'clear'. Valid values are 'disabled' (default), 'generate', 'generate-and-submit', 'generate-and-upload', and 'download-and-submit'.
required: false required: false
default: 'disabled' default: 'disabled'
dependency-graph-report-dir:
description: |
Specifies where the dependency graph report will be generated.
Paths can relative or absolute. Relative paths are resolved relative to the workspace directory.
required: false
default: 'dependency-graph-reports'
dependency-graph-continue-on-failure: dependency-graph-continue-on-failure:
description: When 'false' a failure to generate or submit a dependency graph will fail the Step or Job. When 'true' a warning will be emitted but no failure will result. description: When 'false' a failure to generate or submit a dependency graph will fail the Step or Job. When 'true' a warning will be emitted but no failure will result.
required: false required: false
default: true default: true
dependency-graph-exclude-projects:
description: |
Gradle projects that should be excluded from dependency graph (regular expression).
When set, any matching project will be excluded.
required: false
dependency-graph-include-projects:
description: |
Gradle projects that should be included in dependency graph (regular expression).
When set, only matching projects will be included.
required: false
dependency-graph-exclude-configurations:
description: |
Gradle configurations that should be included in dependency graph (regular expression).
When set, anymatching configurations will be excluded.
required: false
dependency-graph-include-configurations:
description: |
Gradle configurations that should be included in dependency graph (regular expression).
When set, only matching configurations will be included.
required: false
artifact-retention-days: artifact-retention-days:
description: Specifies the number of days to retain any artifacts generated by the action. If not set, the default retention settings for the repository will apply. description: Specifies the number of days to retain any artifacts generated by the action. If not set, the default retention settings for the repository will apply.
required: false required: false
@@ -100,40 +139,74 @@ inputs:
description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes". description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes".
required: false required: false
develocity-access-key:
description: Develocity access key. Should be set to a secret containing the Develocity Access key.
required: false
develocity-token-expiry:
description: The Develocity short-lived access tokens expiry in hours. Default is 2 hours.
required: false
develocity-injection-enabled:
description: Enables Develocity injection.
required: false
develocity-url:
description: The URL for the Develocity server.
required: false
develocity-allow-untrusted-server:
description: Allow communication with an untrusted server; set to _true_ if your Develocity instance is using a self-signed.
required: false
develocity-capture-file-fingerprints:
description: Enables capturing the paths and content hashes of each individual input file.
required: false
develocity-enforce-url:
description: Enforce the configured Develocity URL over a URL configured in the project's build; set to _true_ to enforce publication of build scans to the configured Develocity URL.
required: false
develocity-plugin-version:
description: The version of the Develocity Gradle plugin to apply.
required: false
develocity-ccud-plugin-version:
description: The version of the Common Custom User Data Gradle plugin to apply, if any.
required: false
gradle-plugin-repository-url:
description: The URL of the repository to use when resolving the Develocity and CCUD plugins; the Gradle Plugin Portal is used by default.
required: false
gradle-plugin-repository-username:
description: The username for the repository URL to use when resolving the Develocity and CCUD.
required: false
gradle-plugin-repository-password:
description: The password for the repository URL to use when resolving the Develocity and CCUD plugins; Consider using secrets to pass the value to this variable.
required: false
# Wrapper validation configuration # Wrapper validation configuration
validate-wrappers: validate-wrappers:
description: | description: |
When 'true', the action will perform the 'wrapper-validation' action automatically. When 'true' (the default) the action will automatically validate all wrapper jars found in the repository.
If the wrapper checksums are not valid, the action will fail. If the wrapper checksums are not valid, the action will fail.
required: false required: false
default: true
allow-snapshot-wrappers:
description: |
When 'true', wrapper validation will include the checksums of snapshot wrapper jars.
Use this if you are running with nightly or snapshot versions of the Gradle wrapper.
required: false
default: false default: false
# DEPRECATED ACTION INPUTS # DEPRECATED ACTION INPUTS
build-scan-terms-of-service-url:
description: The URL to the Build Scan® terms of use. This input must be set to 'https://gradle.com/terms-of-service'.
required: false
deprecation-message: The input has been renamed to align with the Develocity API. Use 'build-scan-terms-of-use-url' instead.
build-scan-terms-of-service-agree:
description: Indicate that you agree to the Build Scan® terms of use. This input value must be "yes".
required: false
deprecation-message: The input has been renamed to align with the Develocity API. Use 'build-scan-terms-of-use-agree' instead.
generate-job-summary:
description: When 'false', no Job Summary will be generated for the Job.
required: false
default: true
deprecation-message: Superceded by the new 'add-job-summary' and 'add-job-summary-as-pr-comment' parameters.
arguments: arguments:
description: Gradle command line arguments (supports multi-line input) description: Gradle command line arguments (supports multi-line input)
required: false required: false
deprecation-message: Using the action to execute Gradle directly is deprecated in favor of using the action to setup Gradle, and executing Gradle in a subsequent Step. deprecation-message: This parameter has been deprecated and removed. It is only left here to allow for better reporting to assist users to migrate.
build-root-directory:
description: Path to the root directory of the build. Default is the root of the GitHub workspace.
required: false
deprecation-message: Using the action to execute Gradle directly is deprecated in favor of using the action to setup Gradle, and executing Gradle in a subsequent Step.
# EXPERIMENTAL ACTION INPUTS # EXPERIMENTAL ACTION INPUTS
# The following action properties allow fine-grained tweaking of the action caching behaviour. # The following action properties allow fine-grained tweaking of the action caching behaviour.

View File

@@ -1,3 +1,3 @@
# Configuration file for asdf version manager # Configuration file for asdf version manager
nodejs 20.10.0 nodejs 20.10.0
gradle 8.7 gradle 8.9

View File

@@ -20,15 +20,18 @@
"@actions/tool-cache": "2.0.1", "@actions/tool-cache": "2.0.1",
"@octokit/rest": "20.1.0", "@octokit/rest": "20.1.0",
"@octokit/webhooks-types": "7.5.0", "@octokit/webhooks-types": "7.5.0",
"cheerio": "^1.0.0-rc.12",
"semver": "7.6.0", "semver": "7.6.0",
"string-argv": "0.3.2", "string-argv": "0.3.2",
"typed-rest-client": "1.8.11", "typed-rest-client": "1.8.11",
"unhomoglyph": "1.0.6" "unhomoglyph": "1.0.6",
"which": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/node": "20.12.4", "@types/node": "20.12.4",
"@types/unzipper": "0.10.9", "@types/unzipper": "0.10.9",
"@types/which": "3.0.4",
"@typescript-eslint/parser": "7.5.0", "@typescript-eslint/parser": "7.5.0",
"@vercel/ncc": "0.38.1", "@vercel/ncc": "0.38.1",
"eslint": "8.57.0", "eslint": "8.57.0",
@@ -2215,6 +2218,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/which": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz",
"integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==",
"dev": true
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.23", "version": "17.0.23",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz",
@@ -3301,6 +3310,11 @@
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
} }
}, },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/bottleneck": { "node_modules/bottleneck": {
"version": "2.19.5", "version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -3316,12 +3330,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -3518,6 +3532,42 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"htmlparser2": "^8.0.1",
"parse5": "^7.0.0",
"parse5-htmlparser2-tree-adapter": "^7.0.0"
},
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/ci-info": { "node_modules/ci-info": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -3694,12 +3744,53 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/cross-spawn/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypto": { "node_modules/crypto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
}, },
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -3877,6 +3968,57 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dot-object": { "node_modules/dot-object": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz", "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
@@ -3921,6 +4063,17 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": { "node_modules/error-ex": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -4736,9 +4889,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@@ -5164,6 +5317,24 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true "dev": true
}, },
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -7037,6 +7208,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -7270,6 +7452,29 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"dependencies": {
"entities": "^4.4.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
"dependencies": {
"domhandler": "^5.0.2",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/pascal-case": { "node_modules/pascal-case": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
@@ -8885,18 +9090,17 @@
} }
}, },
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"dev": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^3.1.1"
}, },
"bin": { "bin": {
"node-which": "bin/node-which" "node-which": "bin/which.js"
}, },
"engines": { "engines": {
"node": ">= 8" "node": "^16.13.0 || >=18.0.0"
} }
}, },
"node_modules/which-boxed-primitive": { "node_modules/which-boxed-primitive": {
@@ -8949,6 +9153,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/which/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"engines": {
"node": ">=16"
}
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -10875,6 +11087,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/which": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz",
"integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==",
"dev": true
},
"@types/yargs": { "@types/yargs": {
"version": "17.0.23", "version": "17.0.23",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz",
@@ -11613,6 +11831,11 @@
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
} }
}, },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"bottleneck": { "bottleneck": {
"version": "2.19.5", "version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -11628,12 +11851,12 @@
} }
}, },
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
} }
}, },
"browserslist": { "browserslist": {
@@ -11754,6 +11977,33 @@
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true "dev": true
}, },
"cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
"requires": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"htmlparser2": "^8.0.1",
"parse5": "^7.0.0",
"parse5-htmlparser2-tree-adapter": "^7.0.0"
}
},
"cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"requires": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
}
},
"ci-info": { "ci-info": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -11882,6 +12132,17 @@
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
"which": "^2.0.1" "which": "^2.0.1"
},
"dependencies": {
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
} }
}, },
"crypto": { "crypto": {
@@ -11889,6 +12150,23 @@
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
}, },
"css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"requires": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
}
},
"css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
},
"damerau-levenshtein": { "damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -12015,6 +12293,39 @@
"esutils": "^2.0.2" "esutils": "^2.0.2"
} }
}, },
"dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
}
},
"domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
},
"domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"requires": {
"domelementtype": "^2.3.0"
}
},
"domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"requires": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
}
},
"dot-object": { "dot-object": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz", "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
@@ -12050,6 +12361,11 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
},
"error-ex": { "error-ex": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -12659,9 +12975,9 @@
} }
}, },
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"requires": { "requires": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@@ -12960,6 +13276,17 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true "dev": true
}, },
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"human-signals": { "human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -14379,6 +14706,14 @@
"path-key": "^3.0.0" "path-key": "^3.0.0"
} }
}, },
"nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"requires": {
"boolbase": "^1.0.0"
}
},
"object-inspect": { "object-inspect": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -14537,6 +14872,23 @@
"lines-and-columns": "^1.1.6" "lines-and-columns": "^1.1.6"
} }
}, },
"parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"requires": {
"entities": "^4.4.0"
}
},
"parse5-htmlparser2-tree-adapter": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
"requires": {
"domhandler": "^5.0.2",
"parse5": "^7.0.0"
}
},
"pascal-case": { "pascal-case": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
@@ -15700,12 +16052,18 @@
} }
}, },
"which": { "which": {
"version": "2.0.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"dev": true,
"requires": { "requires": {
"isexe": "^2.0.0" "isexe": "^3.1.1"
},
"dependencies": {
"isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="
}
} }
}, },
"which-boxed-primitive": { "which-boxed-primitive": {

View File

@@ -8,11 +8,11 @@
"prettier-write": "prettier --write 'src/**/*.ts'", "prettier-write": "prettier --write 'src/**/*.ts'",
"prettier-check": "prettier --check 'src/**/*.ts'", "prettier-check": "prettier --check 'src/**/*.ts'",
"lint": "eslint 'src/**/*.ts'", "lint": "eslint 'src/**/*.ts'",
"compile-dependency-submission-main": "ncc build src/dependency-submission/main.ts --out dist/dependency-submission/main --source-map --no-source-map-register", "compile-dependency-submission-main": "ncc build src/actions/dependency-submission/main.ts --out dist/dependency-submission/main --source-map --no-source-map-register",
"compile-dependency-submission-post": "ncc build src/dependency-submission/post.ts --out dist/dependency-submission/post --source-map --no-source-map-register", "compile-dependency-submission-post": "ncc build src/actions/dependency-submission/post.ts --out dist/dependency-submission/post --source-map --no-source-map-register",
"compile-setup-gradle-main": "ncc build src/setup-gradle/main.ts --out dist/setup-gradle/main --source-map --no-source-map-register", "compile-setup-gradle-main": "ncc build src/actions/setup-gradle/main.ts --out dist/setup-gradle/main --source-map --no-source-map-register",
"compile-setup-gradle-post": "ncc build src/setup-gradle/post.ts --out dist/setup-gradle/post --source-map --no-source-map-register", "compile-setup-gradle-post": "ncc build src/actions/setup-gradle/post.ts --out dist/setup-gradle/post --source-map --no-source-map-register",
"compile-wrapper-validation-main": "ncc build src/wrapper-validation/main.ts --out dist/wrapper-validation/main --source-map --no-source-map-register", "compile-wrapper-validation-main": "ncc build src/actions/wrapper-validation/main.ts --out dist/wrapper-validation/main --source-map --no-source-map-register",
"compile": "npm-run-all --parallel compile-*", "compile": "npm-run-all --parallel compile-*",
"check": "npm-run-all --parallel prettier-check lint", "check": "npm-run-all --parallel prettier-check lint",
"format": "npm-run-all --parallel prettier-write lint", "format": "npm-run-all --parallel prettier-write lint",
@@ -42,15 +42,18 @@
"@actions/tool-cache": "2.0.1", "@actions/tool-cache": "2.0.1",
"@octokit/rest": "20.1.0", "@octokit/rest": "20.1.0",
"@octokit/webhooks-types": "7.5.0", "@octokit/webhooks-types": "7.5.0",
"cheerio": "^1.0.0-rc.12",
"semver": "7.6.0", "semver": "7.6.0",
"string-argv": "0.3.2", "string-argv": "0.3.2",
"typed-rest-client": "1.8.11", "typed-rest-client": "1.8.11",
"unhomoglyph": "1.0.6" "unhomoglyph": "1.0.6",
}, "which": "4.0.0"
},
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/node": "20.12.4", "@types/node": "20.12.4",
"@types/unzipper": "0.10.9", "@types/unzipper": "0.10.9",
"@types/which": "3.0.4",
"@typescript-eslint/parser": "7.5.0", "@typescript-eslint/parser": "7.5.0",
"@vercel/ncc": "0.38.1", "@vercel/ncc": "0.38.1",
"eslint": "8.57.0", "eslint": "8.57.0",
@@ -59,11 +62,11 @@
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.1.3",
"jest": "29.7.0", "jest": "29.7.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"nock": "13.5.4",
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"patch-package": "8.0.0", "patch-package": "8.0.0",
"prettier": "3.2.5", "prettier": "3.2.5",
"ts-jest": "29.1.2", "ts-jest": "29.1.2",
"typescript": "5.4.3", "typescript": "5.4.3"
"nock": "13.5.4"
} }
} }

View File

@@ -1,6 +1,7 @@
import * as setupGradle from '../setup-gradle' import * as core from '@actions/core'
import * as gradle from '../execution/gradle' import * as setupGradle from '../../setup-gradle'
import * as dependencyGraph from '../dependency-graph' import * as gradle from '../../execution/gradle'
import * as dependencyGraph from '../../dependency-graph'
import {parseArgsStringToArgv} from 'string-argv' import {parseArgsStringToArgv} from 'string-argv'
import { import {
@@ -9,10 +10,11 @@ import {
DependencyGraphConfig, DependencyGraphConfig,
DependencyGraphOption, DependencyGraphOption,
GradleExecutionConfig, GradleExecutionConfig,
setActionId setActionId,
} from '../configuration' WrapperValidationConfig
import {saveDeprecationState} from '../deprecation-collector' } from '../../configuration'
import {handleMainActionError} from '../errors' import {saveDeprecationState} from '../../deprecation-collector'
import {handleMainActionError} from '../../errors'
/** /**
* The main entry point for the action, called by Github Actions for the step. * The main entry point for the action, called by Github Actions for the step.
@@ -22,7 +24,10 @@ export async function run(): Promise<void> {
setActionId('gradle/actions/dependency-submission') setActionId('gradle/actions/dependency-submission')
// Configure Gradle environment (Gradle User Home) // Configure Gradle environment (Gradle User Home)
await setupGradle.setup(new CacheConfig(), new BuildScanConfig()) await setupGradle.setup(new CacheConfig(), new BuildScanConfig(), new WrapperValidationConfig())
// Capture the enabled state of dependency-graph
const originallyEnabled = process.env['GITHUB_DEPENDENCY_GRAPH_ENABLED']
// Configure the dependency graph submission // Configure the dependency graph submission
const config = new DependencyGraphConfig() const config = new DependencyGraphConfig()
@@ -53,6 +58,9 @@ export async function run(): Promise<void> {
await dependencyGraph.complete(config) await dependencyGraph.complete(config)
// Reset the enabled state of dependency graph
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', originallyEnabled)
saveDeprecationState() saveDeprecationState()
} catch (error) { } catch (error) {
handleMainActionError(error) handleMainActionError(error)

View File

@@ -1,7 +1,7 @@
import * as setupGradle from '../setup-gradle' import * as setupGradle from '../../setup-gradle'
import {CacheConfig, SummaryConfig} from '../configuration' import {CacheConfig, SummaryConfig} from '../../configuration'
import {handlePostActionError} from '../errors' import {handlePostActionError} from '../../errors'
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in // Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to

View File

@@ -1,17 +1,17 @@
import * as setupGradle from '../setup-gradle' import * as setupGradle from '../../setup-gradle'
import * as gradle from '../execution/gradle' import * as provisioner from '../../execution/provision'
import * as dependencyGraph from '../dependency-graph' import * as dependencyGraph from '../../dependency-graph'
import { import {
BuildScanConfig, BuildScanConfig,
CacheConfig, CacheConfig,
DependencyGraphConfig, DependencyGraphConfig,
GradleExecutionConfig, GradleExecutionConfig,
doValidateWrappers, WrapperValidationConfig,
getActionId, getActionId,
setActionId setActionId
} from '../configuration' } from '../../configuration'
import {recordDeprecation, saveDeprecationState} from '../deprecation-collector' import {failOnUseOfRemovedFeature, saveDeprecationState} from '../../deprecation-collector'
import {handleMainActionError} from '../errors' import {handleMainActionError} from '../../errors'
/** /**
* The main entry point for the action, called by Github Actions for the step. * The main entry point for the action, called by Github Actions for the step.
@@ -19,30 +19,22 @@ import {handleMainActionError} from '../errors'
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
if (getActionId() === 'gradle/gradle-build-action') { if (getActionId() === 'gradle/gradle-build-action') {
recordDeprecation( failOnUseOfRemovedFeature(
'The action `gradle/gradle-build-action` has been replaced by `gradle/actions/setup-gradle`' 'The action `gradle/gradle-build-action` has been replaced by `gradle/actions/setup-gradle`'
) )
} else {
setActionId('gradle/actions/setup-gradle')
} }
// Check for invalid wrapper JARs if requested setActionId('gradle/actions/setup-gradle')
if (doValidateWrappers()) {
await setupGradle.checkNoInvalidWrapperJars()
}
// Configure Gradle environment (Gradle User Home) // Configure Gradle environment (Gradle User Home)
await setupGradle.setup(new CacheConfig(), new BuildScanConfig()) await setupGradle.setup(new CacheConfig(), new BuildScanConfig(), new WrapperValidationConfig())
// Configure the dependency graph submission // Configure the dependency graph submission
await dependencyGraph.setup(new DependencyGraphConfig()) await dependencyGraph.setup(new DependencyGraphConfig())
const config = new GradleExecutionConfig() const config = new GradleExecutionConfig()
await gradle.provisionAndMaybeExecute( config.verifyNoArguments()
config.getGradleVersion(), await provisioner.provisionGradle(config.getGradleVersion())
config.getBuildRootDirectory(),
config.getArguments()
)
saveDeprecationState() saveDeprecationState()
} catch (error) { } catch (error) {

View File

@@ -1,9 +1,9 @@
import * as setupGradle from '../setup-gradle' import * as setupGradle from '../../setup-gradle'
import * as dependencyGraph from '../dependency-graph' import * as dependencyGraph from '../../dependency-graph'
import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../configuration' import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../../configuration'
import {handlePostActionError} from '../errors' import {handlePostActionError} from '../../errors'
import {emitDeprecationWarnings, restoreDeprecationState} from '../deprecation-collector' import {emitDeprecationWarnings, restoreDeprecationState} from '../../deprecation-collector'
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in // Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to

View File

@@ -1,21 +1,21 @@
import * as path from 'path' import * as path from 'path'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as validate from './validate' import * as validate from '../../wrapper-validation/validate'
import {getActionId, setActionId} from '../configuration' import {getActionId, setActionId} from '../../configuration'
import {recordDeprecation, emitDeprecationWarnings} from '../deprecation-collector' import {failOnUseOfRemovedFeature, emitDeprecationWarnings} from '../../deprecation-collector'
import {handleMainActionError} from '../errors' import {handleMainActionError} from '../../errors'
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
if (getActionId() === 'gradle/wrapper-validation-action') { if (getActionId() === 'gradle/wrapper-validation-action') {
recordDeprecation( failOnUseOfRemovedFeature(
'The action `gradle/wrapper-validation-action` has been replaced by `gradle/actions/wrapper-validation`' 'The action `gradle/wrapper-validation-action` has been replaced by `gradle/actions/wrapper-validation`'
) )
} else {
setActionId('gradle/actions/wrapper-validation')
} }
setActionId('gradle/actions/wrapper-validation')
const result = await validate.findInvalidWrapperJars( const result = await validate.findInvalidWrapperJars(
path.resolve('.'), path.resolve('.'),
+core.getInput('min-wrapper-count'), +core.getInput('min-wrapper-count'),

View File

@@ -8,15 +8,40 @@ export interface BuildResult {
get gradleVersion(): string get gradleVersion(): string
get gradleHomeDir(): string get gradleHomeDir(): string
get buildFailed(): boolean get buildFailed(): boolean
get configCacheHit(): boolean
get buildScanUri(): string get buildScanUri(): string
get buildScanFailed(): boolean get buildScanFailed(): boolean
} }
export function loadBuildResults(): BuildResult[] { export class BuildResults {
return getUnprocessedResults().map(filePath => { results: BuildResult[]
constructor(results: BuildResult[]) {
this.results = results
}
anyFailed(): boolean {
return this.results.some(result => result.buildFailed)
}
anyConfigCacheHit(): boolean {
return this.results.some(result => result.configCacheHit)
}
uniqueGradleHomes(): string[] {
const allHomes = this.results.map(buildResult => buildResult.gradleHomeDir)
return Array.from(new Set(allHomes))
}
}
export function loadBuildResults(): BuildResults {
const results = getUnprocessedResults().map(filePath => {
const content = fs.readFileSync(filePath, 'utf8') const content = fs.readFileSync(filePath, 'utf8')
return JSON.parse(content) as BuildResult const buildResult = JSON.parse(content) as BuildResult
addScanResults(filePath, buildResult)
return buildResult
}) })
return new BuildResults(results)
} }
export function markBuildResultsProcessed(): void { export function markBuildResultsProcessed(): void {
@@ -24,7 +49,7 @@ export function markBuildResultsProcessed(): void {
} }
function getUnprocessedResults(): string[] { function getUnprocessedResults(): string[] {
const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.build-results') const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.gradle-actions', 'build-results')
if (!fs.existsSync(buildResultsDir)) { if (!fs.existsSync(buildResultsDir)) {
return [] return []
} }
@@ -39,6 +64,22 @@ function getUnprocessedResults(): string[] {
}) })
} }
function addScanResults(buildResultsFile: string, buildResult: BuildResult): void {
const buildScansDir = path.resolve(process.env['RUNNER_TEMP']!, '.gradle-actions', 'build-scans')
if (!fs.existsSync(buildScansDir)) {
return
}
const buildScanResults = path.resolve(buildScansDir, path.basename(buildResultsFile))
if (fs.existsSync(buildScanResults)) {
const content = fs.readFileSync(buildScanResults, 'utf8')
const scanResults = JSON.parse(content)
Object.assign(buildResult, scanResults)
}
return
}
function isProcessed(resultFile: string): boolean { function isProcessed(resultFile: string): boolean {
const markerFile = `${resultFile}.processed` const markerFile = `${resultFile}.processed`
return fs.existsSync(markerFile) return fs.existsSync(markerFile)

View File

@@ -1,20 +0,0 @@
import * as core from '@actions/core'
import {BuildScanConfig} from './configuration'
export function setup(config: BuildScanConfig): void {
maybeExportVariable('DEVELOCITY_INJECTION_INIT_SCRIPT_NAME', 'gradle-actions.inject-develocity.init.gradle')
maybeExportVariable('DEVELOCITY_AUTO_INJECTION_CUSTOM_VALUE', 'gradle-actions')
if (config.getBuildScanPublishEnabled()) {
maybeExportVariable('DEVELOCITY_INJECTION_ENABLED', 'true')
maybeExportVariable('DEVELOCITY_PLUGIN_VERSION', '3.17.2')
maybeExportVariable('DEVELOCITY_CCUD_PLUGIN_VERSION', '2.0')
maybeExportVariable('DEVELOCITY_TERMS_OF_USE_URL', config.getBuildScanTermsOfUseUrl())
maybeExportVariable('DEVELOCITY_TERMS_OF_USE_AGREE', config.getBuildScanTermsOfUseAgree())
}
}
function maybeExportVariable(variableName: string, value: unknown): void {
if (!process.env[variableName]) {
core.exportVariable(variableName, value)
}
}

View File

@@ -1,8 +1,9 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as glob from '@actions/glob'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import * as provisioner from '../execution/provision'
export class CacheCleaner { export class CacheCleaner {
private readonly gradleUserHome: string private readonly gradleUserHome: string
@@ -13,26 +14,20 @@ export class CacheCleaner {
this.tmpDir = tmpDir this.tmpDir = tmpDir
} }
async prepare(): Promise<void> { async prepare(): Promise<string> {
// Reset the file-access journal so that files appear not to have been used recently // Save the current timestamp
fs.rmSync(path.resolve(this.gradleUserHome, 'caches/journal-1'), {recursive: true, force: true}) const timestamp = Date.now().toString()
fs.mkdirSync(path.resolve(this.gradleUserHome, 'caches/journal-1'), {recursive: true}) core.saveState('clean-timestamp', timestamp)
fs.writeFileSync( return timestamp
path.resolve(this.gradleUserHome, 'caches/journal-1/file-access.properties'),
'inceptionTimestamp=0'
)
// Set the modification time of all files to the past: this timestamp is used when there is no matching entry in the journal
await this.ageAllFiles()
// Touch all 'gc' files so that cache cleanup won't run immediately.
await this.touchAllFiles('gc.properties')
} }
async forceCleanup(): Promise<void> { async forceCleanup(): Promise<void> {
// Age all 'gc' files so that cache cleanup will run immediately. const cleanTimestamp = core.getState('clean-timestamp')
await this.ageAllFiles('gc.properties') await this.forceCleanupFilesOlderThan(cleanTimestamp)
}
// Visible for testing
async forceCleanupFilesOlderThan(cleanTimestamp: string): Promise<void> {
// Run a dummy Gradle build to trigger cache cleanup // Run a dummy Gradle build to trigger cache cleanup
const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project') const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project')
fs.mkdirSync(cleanupProjectDir, {recursive: true}) fs.mkdirSync(cleanupProjectDir, {recursive: true})
@@ -40,30 +35,53 @@ export class CacheCleaner {
path.resolve(cleanupProjectDir, 'settings.gradle'), path.resolve(cleanupProjectDir, 'settings.gradle'),
'rootProject.name = "dummy-cleanup-project"' 'rootProject.name = "dummy-cleanup-project"'
) )
fs.writeFileSync(
path.resolve(cleanupProjectDir, 'init.gradle'),
`
beforeSettings { settings ->
def cleanupTime = ${cleanTimestamp}
settings.caches {
cleanup = Cleanup.ALWAYS
releasedWrappers.removeUnusedEntriesOlderThan.set(cleanupTime)
snapshotWrappers.removeUnusedEntriesOlderThan.set(cleanupTime)
downloadedResources.removeUnusedEntriesOlderThan.set(cleanupTime)
createdResources.removeUnusedEntriesOlderThan.set(cleanupTime)
buildCache.removeUnusedEntriesOlderThan.set(cleanupTime)
}
}
`
)
fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}') fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}')
const gradleCommand = `gradle -g ${this.gradleUserHome} --no-daemon --build-cache --no-scan --quiet -DGITHUB_DEPENDENCY_GRAPH_ENABLED=false noop` const executable = await provisioner.provisionGradle('current')
await exec.exec(gradleCommand, [], {
cwd: cleanupProjectDir await core.group('Executing Gradle to clean up caches', async () => {
core.info(`Cleaning up caches last used before ${cleanTimestamp}`)
await this.executeCleanupBuild(executable!, cleanupProjectDir)
}) })
} }
private async ageAllFiles(fileName = '*'): Promise<void> { private async executeCleanupBuild(executable: string, cleanupProjectDir: string): Promise<void> {
core.debug(`Aging all files in Gradle User Home with name ${fileName}`) const args = [
await this.setUtimes(`${this.gradleUserHome}/**/${fileName}`, new Date(0)) '-g',
} this.gradleUserHome,
'-I',
'init.gradle',
'--info',
'--no-daemon',
'--no-scan',
'--build-cache',
'-DGITHUB_DEPENDENCY_GRAPH_ENABLED=false',
'noop'
]
private async touchAllFiles(fileName = '*'): Promise<void> { const result = await exec.getExecOutput(executable, args, {
core.debug(`Touching all files in Gradle User Home with name ${fileName}`) cwd: cleanupProjectDir,
await this.setUtimes(`${this.gradleUserHome}/**/${fileName}`, new Date()) silent: true
}
private async setUtimes(pattern: string, timestamp: Date): Promise<void> {
const globber = await glob.create(pattern, {
implicitDescendants: false
}) })
for await (const file of globber.globGenerator()) {
fs.utimesSync(file, timestamp, timestamp) core.info(result.stdout)
}
} }
} }

View File

@@ -73,7 +73,8 @@ export function getCacheKeyBase(cacheName: string, cacheProtocolVersion: string)
function getCacheKeyEnvironment(): string { function getCacheKeyEnvironment(): string {
const runnerOs = process.env['RUNNER_OS'] || '' const runnerOs = process.env['RUNNER_OS'] || ''
return process.env[CACHE_KEY_OS_VAR] || runnerOs const runnerArch = process.env['RUNNER_ARCH'] || ''
return process.env[CACHE_KEY_OS_VAR] || `${runnerOs}-${runnerArch}`
} }
function getCacheKeyJob(): string { function getCacheKeyJob(): string {

View File

@@ -1,5 +1,27 @@
import * as cache from '@actions/cache' import * as cache from '@actions/cache'
export const DEFAULT_CACHE_ENABLED_REASON = `[Cache was enabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#caching-build-state-between-jobs). Action attempted to both restore and save the Gradle User Home.`
export const DEFAULT_READONLY_REASON = `[Cache was read-only](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#using-the-cache-read-only). By default, the action will only write to the cache for Jobs running on the default branch.`
export const DEFAULT_DISABLED_REASON = `[Cache was disabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#disabling-caching) via action confiugration. Gradle User Home was not restored from or saved to the cache.`
export const DEFAULT_WRITEONLY_REASON = `[Cache was set to write-only](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#using-the-cache-write-only) via action configuration. Gradle User Home was not restored from cache.`
export const EXISTING_GRADLE_HOME = `[Cache was disabled to avoid overwriting a pre-existing Gradle User Home](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#overwriting-an-existing-gradle-user-home). Gradle User Home was not restored from or saved to the cache.`
export const CLEANUP_DISABLED_READONLY = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) is always disabled when cache is read-only or disabled.`
export const DEFAULT_CLEANUP_ENABLED_REASON = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) was enabled. Stale files in Gradle User Home were purged before saving to the cache.`
export const DEFAULT_CLEANUP_DISABLED_REASON = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) was disabled via action parameter. No cleanup of Gradle User Home was performed.`
export const CLEANUP_DISABLED_DUE_TO_FAILURE =
'[Cache cleanup was disabled due to build failure](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup). Use `cache-cleanup: always` to override this behavior.'
export const CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT =
'[Cache cleanup was disabled due to configuration-cache reuse](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup). This is expected.'
/** /**
* Collects information on what entries were saved and restored during the action. * Collects information on what entries were saved and restored during the action.
* This information is used to generate a summary of the cache usage. * This information is used to generate a summary of the cache usage.
@@ -9,7 +31,8 @@ export class CacheListener {
cacheReadOnly = false cacheReadOnly = false
cacheWriteOnly = false cacheWriteOnly = false
cacheDisabled = false cacheDisabled = false
cacheDisabledReason = 'disabled' cacheStatusReason: string = DEFAULT_CACHE_ENABLED_REASON
cacheCleanupMessage: string = DEFAULT_CLEANUP_DISABLED_REASON
get fullyRestored(): boolean { get fullyRestored(): boolean {
return this.cacheEntries.every(x => !x.wasRequestedButNotRestored()) return this.cacheEntries.every(x => !x.wasRequestedButNotRestored())
@@ -17,12 +40,37 @@ export class CacheListener {
get cacheStatus(): string { get cacheStatus(): string {
if (!cache.isFeatureAvailable()) return 'not available' if (!cache.isFeatureAvailable()) return 'not available'
if (this.cacheDisabled) return this.cacheDisabledReason if (this.cacheDisabled) return 'disabled'
if (this.cacheWriteOnly) return 'write-only' if (this.cacheWriteOnly) return 'write-only'
if (this.cacheReadOnly) return 'read-only' if (this.cacheReadOnly) return 'read-only'
return 'enabled' return 'enabled'
} }
setReadOnly(reason: string = DEFAULT_READONLY_REASON): void {
this.cacheReadOnly = true
this.cacheStatusReason = reason
this.cacheCleanupMessage = CLEANUP_DISABLED_READONLY
}
setDisabled(reason: string = DEFAULT_DISABLED_REASON): void {
this.cacheDisabled = true
this.cacheStatusReason = reason
this.cacheCleanupMessage = CLEANUP_DISABLED_READONLY
}
setWriteOnly(reason: string = DEFAULT_WRITEONLY_REASON): void {
this.cacheWriteOnly = true
this.cacheStatusReason = reason
}
setCacheCleanupEnabled(): void {
this.cacheCleanupMessage = DEFAULT_CLEANUP_ENABLED_REASON
}
setCacheCleanupDisabled(reason: string = DEFAULT_CLEANUP_DISABLED_REASON): void {
this.cacheCleanupMessage = reason
}
entry(name: string): CacheEntryListener { entry(name: string): CacheEntryListener {
for (const entry of this.cacheEntries) { for (const entry of this.cacheEntries) {
if (entry.entryName === name) { if (entry.entryName === name) {
@@ -117,6 +165,10 @@ export function generateCachingReport(listener: CacheListener): string {
return ` return `
<details> <details>
<summary><h4>Caching for Gradle actions was ${listener.cacheStatus} - expand for details</h4></summary> <summary><h4>Caching for Gradle actions was ${listener.cacheStatus} - expand for details</h4></summary>
- ${listener.cacheStatusReason}
- ${listener.cacheCleanupMessage}
${renderEntryTable(entries)} ${renderEntryTable(entries)}
<h5>Cache Entry Details</h5> <h5>Cache Entry Details</h5>

View File

@@ -1,9 +1,15 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {CacheListener} from './cache-reporting' import {
CacheListener,
EXISTING_GRADLE_HOME,
CLEANUP_DISABLED_DUE_TO_FAILURE,
CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT
} from './cache-reporting'
import {GradleUserHomeCache} from './gradle-user-home-cache' import {GradleUserHomeCache} from './gradle-user-home-cache'
import {CacheCleaner} from './cache-cleaner' import {CacheCleaner} from './cache-cleaner'
import {DaemonController} from '../daemon-controller' import {DaemonController} from '../daemon-controller'
import {CacheConfig} from '../configuration' import {CacheConfig} from '../configuration'
import {BuildResults} from '../build-results'
const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED' const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED'
@@ -26,7 +32,7 @@ export async function restore(
core.info('Cache is disabled: will not restore state from previous builds.') core.info('Cache is disabled: will not restore state from previous builds.')
// Initialize the Gradle User Home even when caching is disabled. // Initialize the Gradle User Home even when caching is disabled.
gradleStateCache.init() gradleStateCache.init()
cacheListener.cacheDisabled = true cacheListener.setDisabled()
return return
} }
@@ -35,8 +41,7 @@ export async function restore(
core.info('Gradle User Home already exists: will not restore from cache.') core.info('Gradle User Home already exists: will not restore from cache.')
// Initialize pre-existing Gradle User Home. // Initialize pre-existing Gradle User Home.
gradleStateCache.init() gradleStateCache.init()
cacheListener.cacheDisabled = true cacheListener.setDisabled(EXISTING_GRADLE_HOME)
cacheListener.cacheDisabledReason = 'disabled due to pre-existing Gradle User Home'
return return
} }
core.info('Gradle User Home already exists: will overwrite with cached contents.') core.info('Gradle User Home already exists: will overwrite with cached contents.')
@@ -46,21 +51,21 @@ export async function restore(
// Mark the state as restored so that post-action will perform save. // Mark the state as restored so that post-action will perform save.
core.saveState(CACHE_RESTORED_VAR, true) core.saveState(CACHE_RESTORED_VAR, true)
if (cacheConfig.isCacheCleanupEnabled()) {
core.info('Preparing cache for cleanup.')
const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!)
await cacheCleaner.prepare()
}
if (cacheConfig.isCacheWriteOnly()) { if (cacheConfig.isCacheWriteOnly()) {
core.info('Cache is write-only: will not restore from cache.') core.info('Cache is write-only: will not restore from cache.')
cacheListener.cacheWriteOnly = true cacheListener.setWriteOnly()
return return
} }
await core.group('Restore Gradle state from cache', async () => { await core.group('Restore Gradle state from cache', async () => {
await gradleStateCache.restore(cacheListener) await gradleStateCache.restore(cacheListener)
}) })
if (cacheConfig.isCacheCleanupEnabled()) {
core.info('Preparing cache for cleanup.')
const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!)
await cacheCleaner.prepare()
}
} }
export async function save( export async function save(
@@ -68,6 +73,7 @@ export async function save(
gradleUserHome: string, gradleUserHome: string,
cacheListener: CacheListener, cacheListener: CacheListener,
daemonController: DaemonController, daemonController: DaemonController,
buildResults: BuildResults,
cacheConfig: CacheConfig cacheConfig: CacheConfig
): Promise<void> { ): Promise<void> {
if (cacheConfig.isCacheDisabled()) { if (cacheConfig.isCacheDisabled()) {
@@ -82,19 +88,24 @@ export async function save(
if (cacheConfig.isCacheReadOnly()) { if (cacheConfig.isCacheReadOnly()) {
core.info('Cache is read-only: will not save state for use in subsequent builds.') core.info('Cache is read-only: will not save state for use in subsequent builds.')
cacheListener.cacheReadOnly = true cacheListener.setReadOnly()
return return
} }
await core.group('Stopping Gradle daemons', async () => {
await daemonController.stopAllDaemons() await daemonController.stopAllDaemons()
})
if (cacheConfig.isCacheCleanupEnabled()) { if (cacheConfig.isCacheCleanupEnabled()) {
core.info('Forcing cache cleanup.') if (buildResults.anyConfigCacheHit()) {
const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!) core.info('Not performing cache-cleanup due to config-cache reuse')
try { cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT)
await cacheCleaner.forceCleanup() } else if (cacheConfig.shouldPerformCacheCleanup(buildResults.anyFailed())) {
} catch (e) { cacheListener.setCacheCleanupEnabled()
core.warning(`Cache cleanup failed. Will continue. ${String(e)}`) await performCacheCleanup(gradleUserHome)
} else {
core.info('Not performing cache-cleanup due to build failure')
cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_FAILURE)
} }
} }
@@ -102,3 +113,12 @@ export async function save(
return new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig).save(cacheListener) return new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig).save(cacheListener)
}) })
} }
async function performCacheCleanup(gradleUserHome: string): Promise<void> {
const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!)
try {
await cacheCleaner.forceCleanup()
} catch (e) {
core.warning(`Cache cleanup failed. Will continue. ${String(e)}`)
}
}

View File

@@ -4,12 +4,11 @@ import * as core from '@actions/core'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as semver from 'semver' import * as semver from 'semver'
import {META_FILE_DIR} from './gradle-user-home-cache'
import {CacheEntryListener, CacheListener} from './cache-reporting' import {CacheEntryListener, CacheListener} from './cache-reporting'
import {cacheDebug, hashFileNames, isCacheDebuggingEnabled, restoreCache, saveCache, tryDelete} from './cache-utils' import {cacheDebug, hashFileNames, isCacheDebuggingEnabled, restoreCache, saveCache, tryDelete} from './cache-utils'
import {BuildResult, loadBuildResults} from '../build-results' import {BuildResult, loadBuildResults} from '../build-results'
import {CacheConfig} from '../configuration' import {CacheConfig, ACTION_METADATA_DIR} from '../configuration'
import {getCacheKeyBase} from './cache-key' import {getCacheKeyBase} from './cache-key'
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE' const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE'
@@ -298,7 +297,7 @@ abstract class AbstractEntryExtractor {
} }
private getCacheMetadataFile(): string { private getCacheMetadataFile(): string {
const actionMetadataDirectory = path.resolve(this.gradleUserHome, META_FILE_DIR) const actionMetadataDirectory = path.resolve(this.gradleUserHome, ACTION_METADATA_DIR)
fs.mkdirSync(actionMetadataDirectory, {recursive: true}) fs.mkdirSync(actionMetadataDirectory, {recursive: true})
return path.resolve(actionMetadataDirectory, `${this.extractorName}-entry-metadata.json`) return path.resolve(actionMetadataDirectory, `${this.extractorName}-entry-metadata.json`)
@@ -449,7 +448,7 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
} }
private getConfigCacheDirectoriesWithAssociatedBuildResults(): Record<string, BuildResult[]> { private getConfigCacheDirectoriesWithAssociatedBuildResults(): Record<string, BuildResult[]> {
return loadBuildResults().reduce( return loadBuildResults().results.reduce(
(acc, buildResult) => { (acc, buildResult) => {
// For each build result, find the config-cache dir // For each build result, find the config-cache dir
const configCachePath = path.resolve(buildResult.rootProjectDir, '.gradle/configuration-cache') const configCachePath = path.resolve(buildResult.rootProjectDir, '.gradle/configuration-cache')

View File

@@ -7,14 +7,12 @@ import fs from 'fs'
import {generateCacheKey} from './cache-key' import {generateCacheKey} from './cache-key'
import {CacheListener} from './cache-reporting' import {CacheListener} from './cache-reporting'
import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete} from './cache-utils' import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete} from './cache-utils'
import {CacheConfig} from '../configuration' import {CacheConfig, ACTION_METADATA_DIR} from '../configuration'
import {GradleHomeEntryExtractor, ConfigurationCacheEntryExtractor} from './gradle-home-extry-extractor' import {GradleHomeEntryExtractor, ConfigurationCacheEntryExtractor} from './gradle-home-extry-extractor'
import {getPredefinedToolchains, mergeToolchainContent, readResourceFileAsString} from './gradle-user-home-utils' import {getPredefinedToolchains, mergeToolchainContent, readResourceFileAsString} from './gradle-user-home-utils'
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key' const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'
export const META_FILE_DIR = '.setup-gradle'
export class GradleUserHomeCache { export class GradleUserHomeCache {
private readonly cacheName = 'home' private readonly cacheName = 'home'
private readonly cacheDescription = 'Gradle User Home' private readonly cacheDescription = 'Gradle User Home'
@@ -86,6 +84,7 @@ export class GradleUserHomeCache {
await this.debugReportGradleUserHomeSize('as restored from cache') await this.debugReportGradleUserHomeSize('as restored from cache')
await new GradleHomeEntryExtractor(this.gradleUserHome, this.cacheConfig).restore(listener) await new GradleHomeEntryExtractor(this.gradleUserHome, this.cacheConfig).restore(listener)
await new ConfigurationCacheEntryExtractor(this.gradleUserHome, this.cacheConfig).restore(listener) await new ConfigurationCacheEntryExtractor(this.gradleUserHome, this.cacheConfig).restore(listener)
await this.deleteExcludedPaths()
await this.debugReportGradleUserHomeSize('after restoring common artifacts') await this.debugReportGradleUserHomeSize('after restoring common artifacts')
} }
@@ -171,7 +170,7 @@ export class GradleUserHomeCache {
*/ */
protected getCachePath(): string[] { protected getCachePath(): string[] {
const rawPaths: string[] = this.cacheConfig.getCacheIncludes() const rawPaths: string[] = this.cacheConfig.getCacheIncludes()
rawPaths.push(META_FILE_DIR) rawPaths.push(ACTION_METADATA_DIR)
const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x)) const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x))
cacheDebug(`Using cache paths: ${resolvedPaths}`) cacheDebug(`Using cache paths: ${resolvedPaths}`)
return resolvedPaths return resolvedPaths
@@ -187,7 +186,7 @@ export class GradleUserHomeCache {
private initializeGradleUserHome(): void { private initializeGradleUserHome(): void {
// Create a directory for storing action metadata // Create a directory for storing action metadata
const actionCacheDir = path.resolve(this.gradleUserHome, META_FILE_DIR) const actionCacheDir = path.resolve(this.gradleUserHome, ACTION_METADATA_DIR)
fs.mkdirSync(actionCacheDir, {recursive: true}) fs.mkdirSync(actionCacheDir, {recursive: true})
this.copyInitScripts() this.copyInitScripts()

View File

@@ -4,11 +4,12 @@ import * as cache from '@actions/cache'
import * as deprecator from './deprecation-collector' import * as deprecator from './deprecation-collector'
import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary' import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary'
import {parseArgsStringToArgv} from 'string-argv'
import path from 'path' import path from 'path'
const ACTION_ID_VAR = 'GRADLE_ACTION_ID' const ACTION_ID_VAR = 'GRADLE_ACTION_ID'
export const ACTION_METADATA_DIR = '.setup-gradle'
export class DependencyGraphConfig { export class DependencyGraphConfig {
getDependencyGraphOption(): DependencyGraphOption { getDependencyGraphOption(): DependencyGraphOption {
const val = core.getInput('dependency-graph') const val = core.getInput('dependency-graph')
@@ -23,11 +24,9 @@ export class DependencyGraphConfig {
return DependencyGraphOption.GenerateAndUpload return DependencyGraphOption.GenerateAndUpload
case 'download-and-submit': case 'download-and-submit':
return DependencyGraphOption.DownloadAndSubmit return DependencyGraphOption.DownloadAndSubmit
case 'clear':
return DependencyGraphOption.Clear
} }
throw TypeError( throw TypeError(
`The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit, clear]. The default value is 'disabled'.` `The value '${val}' is not valid for 'dependency-graph'. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`
) )
} }
@@ -46,7 +45,28 @@ export class DependencyGraphConfig {
} }
getReportDirectory(): string { getReportDirectory(): string {
return path.resolve(getWorkspaceDirectory(), 'dependency-graph-reports') const param = core.getInput('dependency-graph-report-dir')
return path.resolve(getWorkspaceDirectory(), param)
}
getDownloadArtifactName(): string | undefined {
return process.env['DEPENDENCY_GRAPH_DOWNLOAD_ARTIFACT_NAME']
}
getExcludeProjects(): string | undefined {
return getOptionalInput('dependency-graph-exclude-projects')
}
getIncludeProjects(): string | undefined {
return getOptionalInput('dependency-graph-include-projects')
}
getExcludeConfigurations(): string | undefined {
return getOptionalInput('dependency-graph-exclude-configurations')
}
getIncludeConfigurations(): string | undefined {
return getOptionalInput('dependency-graph-include-configurations')
} }
static constructJobCorrelator(workflow: string, jobId: string, matrixJson: string): string { static constructJobCorrelator(workflow: string, jobId: string, matrixJson: string): string {
@@ -77,8 +97,7 @@ export enum DependencyGraphOption {
Generate = 'generate', Generate = 'generate',
GenerateAndSubmit = 'generate-and-submit', GenerateAndSubmit = 'generate-and-submit',
GenerateAndUpload = 'generate-and-upload', GenerateAndUpload = 'generate-and-upload',
DownloadAndSubmit = 'download-and-submit', DownloadAndSubmit = 'download-and-submit'
Clear = 'clear'
} }
export class CacheConfig { export class CacheConfig {
@@ -107,7 +126,45 @@ export class CacheConfig {
} }
isCacheCleanupEnabled(): boolean { isCacheCleanupEnabled(): boolean {
return getBooleanInput('gradle-home-cache-cleanup') && !this.isCacheReadOnly() if (this.isCacheReadOnly()) {
return false
}
const cleanupOption = this.getCacheCleanupOption()
return cleanupOption === CacheCleanupOption.Always || cleanupOption === CacheCleanupOption.OnSuccess
}
shouldPerformCacheCleanup(hasFailure: boolean): boolean {
const cleanupOption = this.getCacheCleanupOption()
if (cleanupOption === CacheCleanupOption.Always) {
return true
}
if (cleanupOption === CacheCleanupOption.OnSuccess) {
return !hasFailure
}
return false
}
private getCacheCleanupOption(): CacheCleanupOption {
const legacyVal = getOptionalBooleanInput('gradle-home-cache-cleanup')
if (legacyVal !== undefined) {
deprecator.recordDeprecation(
'The `gradle-home-cache-cleanup` input parameter has been replaced by `cache-cleanup`'
)
return legacyVal ? CacheCleanupOption.Always : CacheCleanupOption.Never
}
const val = core.getInput('cache-cleanup')
switch (val.toLowerCase().trim()) {
case 'always':
return CacheCleanupOption.Always
case 'on-success':
return CacheCleanupOption.OnSuccess
case 'never':
return CacheCleanupOption.Never
}
throw TypeError(
`The value '${val}' is not valid for cache-cleanup. Valid values are: [never, always, on-success].`
)
} }
getCacheEncryptionKey(): string { getCacheEncryptionKey(): string {
@@ -123,6 +180,12 @@ export class CacheConfig {
} }
} }
export enum CacheCleanupOption {
Never = 'never',
OnSuccess = 'on-success',
Always = 'always'
}
export class SummaryConfig { export class SummaryConfig {
shouldGenerateJobSummary(hasFailure: boolean): boolean { shouldGenerateJobSummary(hasFailure: boolean): boolean {
// Check if Job Summary is supported on this platform // Check if Job Summary is supported on this platform
@@ -130,11 +193,6 @@ export class SummaryConfig {
return false return false
} }
// Check if Job Summary is disabled using the deprecated input
if (!this.isJobSummaryEnabled()) {
return false
}
return this.shouldAddJobSummary(this.getJobSummaryOption(), hasFailure) return this.shouldAddJobSummary(this.getJobSummaryOption(), hasFailure)
} }
@@ -153,10 +211,6 @@ export class SummaryConfig {
} }
} }
private isJobSummaryEnabled(): boolean {
return getBooleanInput('generate-job-summary', true)
}
private getJobSummaryOption(): JobSummaryOption { private getJobSummaryOption(): JobSummaryOption {
return this.parseJobSummaryOption('add-job-summary') return this.parseJobSummaryOption('add-job-summary')
} }
@@ -188,16 +242,72 @@ export enum JobSummaryOption {
} }
export class BuildScanConfig { export class BuildScanConfig {
static DevelocityAccessKeyEnvVar = 'DEVELOCITY_ACCESS_KEY'
static GradleEnterpriseAccessKeyEnvVar = 'GRADLE_ENTERPRISE_ACCESS_KEY'
getBuildScanPublishEnabled(): boolean { getBuildScanPublishEnabled(): boolean {
return getBooleanInput('build-scan-publish') && this.verifyTermsOfUseAgreement() return getBooleanInput('build-scan-publish') && this.verifyTermsOfUseAgreement()
} }
getBuildScanTermsOfUseUrl(): string { getBuildScanTermsOfUseUrl(): string {
return this.getTermsOfUseProp('build-scan-terms-of-use-url', 'build-scan-terms-of-service-url') return core.getInput('build-scan-terms-of-use-url')
} }
getBuildScanTermsOfUseAgree(): string { getBuildScanTermsOfUseAgree(): string {
return this.getTermsOfUseProp('build-scan-terms-of-use-agree', 'build-scan-terms-of-service-agree') return core.getInput('build-scan-terms-of-use-agree')
}
getDevelocityAccessKey(): string {
return (
core.getInput('develocity-access-key') ||
process.env[BuildScanConfig.DevelocityAccessKeyEnvVar] ||
process.env[BuildScanConfig.GradleEnterpriseAccessKeyEnvVar] ||
''
)
}
getDevelocityTokenExpiry(): string {
return core.getInput('develocity-token-expiry')
}
getDevelocityInjectionEnabled(): boolean | undefined {
return getOptionalBooleanInput('develocity-injection-enabled')
}
getDevelocityUrl(): string {
return core.getInput('develocity-url')
}
getDevelocityAllowUntrustedServer(): boolean | undefined {
return getOptionalBooleanInput('develocity-allow-untrusted-server')
}
getDevelocityCaptureFileFingerprints(): boolean | undefined {
return getOptionalBooleanInput('develocity-capture-file-fingerprints')
}
getDevelocityEnforceUrl(): boolean | undefined {
return getOptionalBooleanInput('develocity-enforce-url')
}
getDevelocityPluginVersion(): string {
return core.getInput('develocity-plugin-version')
}
getDevelocityCcudPluginVersion(): string {
return core.getInput('develocity-ccud-plugin-version')
}
getGradlePluginRepositoryUrl(): string {
return core.getInput('gradle-plugin-repository-url')
}
getGradlePluginRepositoryUsername(): string {
return core.getInput('gradle-plugin-repository-username')
}
getGradlePluginRepositoryPassword(): string {
return core.getInput('gradle-plugin-repository-password')
} }
private verifyTermsOfUseAgreement(): boolean { private verifyTermsOfUseAgreement(): boolean {
@@ -213,22 +323,6 @@ export class BuildScanConfig {
} }
return true return true
} }
/**
* TODO @bigdaz: remove support for the deprecated input property in the next major release of the action
*/
private getTermsOfUseProp(newPropName: string, oldPropName: string): string {
const newProp = core.getInput(newPropName)
if (newProp !== '') {
return newProp
}
const oldProp = core.getInput(oldPropName)
if (oldProp !== '') {
deprecator.recordDeprecation('The `build-scan-terms-of-service` input parameters have been renamed')
return oldProp
}
return core.getInput(oldPropName)
}
} }
export class GradleExecutionConfig { export class GradleExecutionConfig {
@@ -246,16 +340,6 @@ export class GradleExecutionConfig {
return resolvedBuildRootDirectory return resolvedBuildRootDirectory
} }
getArguments(): string[] {
const input = core.getInput('arguments')
if (input.length !== 0) {
deprecator.recordDeprecation(
'Using the action to execute Gradle via the `arguments` parameter is deprecated'
)
}
return parseArgsStringToArgv(input)
}
getDependencyResolutionTask(): string { getDependencyResolutionTask(): string {
return core.getInput('dependency-resolution-task') || ':ForceDependencyResolutionPlugin_resolveAllDependencies' return core.getInput('dependency-resolution-task') || ':ForceDependencyResolutionPlugin_resolveAllDependencies'
} }
@@ -263,10 +347,26 @@ export class GradleExecutionConfig {
getAdditionalArguments(): string { getAdditionalArguments(): string {
return core.getInput('additional-arguments') return core.getInput('additional-arguments')
} }
verifyNoArguments(): void {
const input = core.getInput('arguments')
if (input.length !== 0) {
deprecator.failOnUseOfRemovedFeature(
`The 'arguments' parameter is no longer supported for ${getActionId()}`,
'Using the action to execute Gradle via the `arguments` parameter is deprecated'
)
}
}
} }
export function doValidateWrappers(): boolean { export class WrapperValidationConfig {
doValidateWrappers(): boolean {
return getBooleanInput('validate-wrappers') return getBooleanInput('validate-wrappers')
}
allowSnapshotWrappers(): boolean {
return getBooleanInput('allow-snapshot-wrappers')
}
} }
// Internal parameters // Internal parameters
@@ -301,6 +401,14 @@ export function parseNumericInput(paramName: string, paramValue: string, paramDe
return numericValue return numericValue
} }
function getOptionalInput(paramName: string): string | undefined {
const paramValue = core.getInput(paramName)
if (paramValue.length > 0) {
return paramValue
}
return undefined
}
function getBooleanInput(paramName: string, paramDefault = false): boolean { function getBooleanInput(paramName: string, paramDefault = false): boolean {
const paramValue = core.getInput(paramName) const paramValue = core.getInput(paramName)
switch (paramValue.toLowerCase().trim()) { switch (paramValue.toLowerCase().trim()) {
@@ -313,3 +421,11 @@ function getBooleanInput(paramName: string, paramDefault = false): boolean {
} }
throw TypeError(`The value '${paramValue} is not valid for '${paramName}. Valid values are: [true, false]`) throw TypeError(`The value '${paramValue} is not valid for '${paramName}. Valid values are: [true, false]`)
} }
function getOptionalBooleanInput(paramName: string): boolean | undefined {
const paramValue = core.getInput(paramName)
if (paramValue === '') {
return undefined
}
return getBooleanInput(paramName)
}

View File

@@ -2,19 +2,16 @@ import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import {BuildResult} from './build-results' import {BuildResults} from './build-results'
export class DaemonController { export class DaemonController {
private readonly gradleHomes private readonly gradleHomes
constructor(buildResults: BuildResult[]) { constructor(buildResults: BuildResults) {
const allHomes = buildResults.map(buildResult => buildResult.gradleHomeDir) this.gradleHomes = buildResults.uniqueGradleHomes()
this.gradleHomes = Array.from(new Set(allHomes))
} }
async stopAllDaemons(): Promise<void> { async stopAllDaemons(): Promise<void> {
core.info('Stopping all Gradle daemons before saving Gradle User Home state')
const executions: Promise<number>[] = [] const executions: Promise<number>[] = []
const args = ['--stop'] const args = ['--stop']

View File

@@ -22,6 +22,7 @@ export async function setup(config: DependencyGraphConfig): Promise<void> {
} }
// Download and submit early, for compatability with dependency review. // Download and submit early, for compatability with dependency review.
if (option === DependencyGraphOption.DownloadAndSubmit) { if (option === DependencyGraphOption.DownloadAndSubmit) {
maybeExportVariable('DEPENDENCY_GRAPH_REPORT_DIR', config.getReportDirectory())
await downloadAndSubmitDependencyGraphs(config) await downloadAndSubmitDependencyGraphs(config)
return return
} }
@@ -30,23 +31,24 @@ export async function setup(config: DependencyGraphConfig): Promise<void> {
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true') core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', config.getDependencyGraphContinueOnFailure()) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE', config.getDependencyGraphContinueOnFailure())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', config.getJobCorrelator()) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', config.getJobCorrelator())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId.toString())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext()) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', getWorkspaceDirectory()) maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', getWorkspaceDirectory())
maybeExportVariable('DEPENDENCY_GRAPH_REPORT_DIR', config.getReportDirectory()) maybeExportVariable('DEPENDENCY_GRAPH_REPORT_DIR', config.getReportDirectory())
// To clear the dependency graph, we generate an empty graph by excluding all projects and configurations maybeExportVariable('DEPENDENCY_GRAPH_EXCLUDE_PROJECTS', config.getExcludeProjects())
if (option === DependencyGraphOption.Clear) { maybeExportVariable('DEPENDENCY_GRAPH_INCLUDE_PROJECTS', config.getIncludeProjects())
core.exportVariable('DEPENDENCY_GRAPH_INCLUDE_PROJECTS', '') maybeExportVariable('DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS', config.getExcludeConfigurations())
core.exportVariable('DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS', '') maybeExportVariable('DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS', config.getIncludeConfigurations())
}
} }
function maybeExportVariable(variableName: string, value: unknown): void { function maybeExportVariable(variableName: string, value: string | boolean | undefined): void {
if (!process.env[variableName]) { if (!process.env[variableName]) {
if (value !== undefined) {
core.exportVariable(variableName, value) core.exportVariable(variableName, value)
} }
}
} }
export async function complete(config: DependencyGraphConfig): Promise<void> { export async function complete(config: DependencyGraphConfig): Promise<void> {
@@ -58,26 +60,104 @@ export async function complete(config: DependencyGraphConfig): Promise<void> {
case DependencyGraphOption.DownloadAndSubmit: // Performed in setup case DependencyGraphOption.DownloadAndSubmit: // Performed in setup
return return
case DependencyGraphOption.GenerateAndSubmit: case DependencyGraphOption.GenerateAndSubmit:
case DependencyGraphOption.Clear: // Submit the empty dependency graph await findAndSubmitDependencyGraphs(config)
await submitDependencyGraphs(await findDependencyGraphFiles())
return return
case DependencyGraphOption.GenerateAndUpload: case DependencyGraphOption.GenerateAndUpload:
await uploadDependencyGraphs(await findDependencyGraphFiles(), config) await findAndUploadDependencyGraphs(config)
} }
} catch (e) { } catch (e) {
warnOrFail(config, option, e) warnOrFail(config, option, e)
} }
} }
async function uploadDependencyGraphs(dependencyGraphFiles: string[], config: DependencyGraphConfig): Promise<void> { async function downloadAndSubmitDependencyGraphs(config: DependencyGraphConfig): Promise<void> {
if (dependencyGraphFiles.length === 0) { if (isRunningInActEnvironment()) {
core.info('No dependency graph files found to upload.') core.info('Dependency graph not supported in the ACT environment.')
return return
} }
try {
await submitDependencyGraphs(await downloadDependencyGraphs(config))
} catch (e) {
warnOrFail(config, DependencyGraphOption.DownloadAndSubmit, e)
}
}
async function findAndSubmitDependencyGraphs(config: DependencyGraphConfig): Promise<void> {
if (isRunningInActEnvironment()) { if (isRunningInActEnvironment()) {
core.info('Dependency graph upload not supported in the ACT environment.') core.info('Dependency graph not supported in the ACT environment.')
core.info(`Would upload: ${dependencyGraphFiles.join(', ')}`) return
}
const dependencyGraphFiles = await findDependencyGraphFiles()
try {
await submitDependencyGraphs(dependencyGraphFiles)
} catch (e) {
try {
await uploadDependencyGraphs(dependencyGraphFiles, config)
} catch (uploadError) {
core.info(String(uploadError))
}
throw e
}
}
async function findAndUploadDependencyGraphs(config: DependencyGraphConfig): Promise<void> {
if (isRunningInActEnvironment()) {
core.info('Dependency graph not supported in the ACT environment.')
return
}
await uploadDependencyGraphs(await findDependencyGraphFiles(), config)
}
async function downloadDependencyGraphs(config: DependencyGraphConfig): Promise<string[]> {
const findBy = github.context.payload.workflow_run
? {
token: getGithubToken(),
workflowRunId: github.context.payload.workflow_run.id,
repositoryName: github.context.repo.repo,
repositoryOwner: github.context.repo.owner
}
: undefined
const artifactClient = new DefaultArtifactClient()
let dependencyGraphArtifacts = (
await artifactClient.listArtifacts({
latest: true,
findBy
})
).artifacts.filter(artifact => artifact.name.startsWith(DEPENDENCY_GRAPH_PREFIX))
const artifactName = config.getDownloadArtifactName()
if (artifactName) {
core.info(`Filtering for artifacts ending with ${artifactName}`)
dependencyGraphArtifacts = dependencyGraphArtifacts.filter(artifact => artifact.name.includes(artifactName))
}
for (const artifact of dependencyGraphArtifacts) {
const downloadedArtifact = await artifactClient.downloadArtifact(artifact.id, {
findBy
})
core.info(`Downloading dependency-graph artifact ${artifact.name} to ${downloadedArtifact.downloadPath}`)
}
return findDependencyGraphFiles()
}
async function findDependencyGraphFiles(): Promise<string[]> {
const globber = await glob.create(`${getReportDirectory()}/**/*.json`)
const allFiles = await globber.glob()
const unprocessedFiles = allFiles.filter(file => !isProcessed(file))
unprocessedFiles.forEach(markProcessed)
core.info(`Found dependency graph files: ${unprocessedFiles.join(', ')}`)
return unprocessedFiles
}
async function uploadDependencyGraphs(dependencyGraphFiles: string[], config: DependencyGraphConfig): Promise<void> {
if (dependencyGraphFiles.length === 0) {
core.info('No dependency graph files found to upload.')
return return
} }
@@ -94,47 +174,27 @@ async function uploadDependencyGraphs(dependencyGraphFiles: string[], config: De
} }
} }
async function downloadAndSubmitDependencyGraphs(config: DependencyGraphConfig): Promise<void> {
if (isRunningInActEnvironment()) {
core.info('Dependency graph download and submit not supported in the ACT environment.')
return
}
try {
await submitDependencyGraphs(await downloadDependencyGraphs())
} catch (e) {
warnOrFail(config, DependencyGraphOption.DownloadAndSubmit, e)
}
}
async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> { async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
if (dependencyGraphFiles.length === 0) { if (dependencyGraphFiles.length === 0) {
core.info('No dependency graph files found to submit.') core.info('No dependency graph files found to submit.')
return return
} }
if (isRunningInActEnvironment()) {
core.info('Dependency graph submit not supported in the ACT environment.')
core.info(`Would submit: ${dependencyGraphFiles.join(', ')}`)
return
}
for (const dependencyGraphFile of dependencyGraphFiles) { for (const dependencyGraphFile of dependencyGraphFiles) {
try { try {
await submitDependencyGraphFile(dependencyGraphFile) await submitDependencyGraphFile(dependencyGraphFile)
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {
throw new Error(translateErrorMessage(dependencyGraphFile, error)) error.message = translateErrorMessage(dependencyGraphFile, error)
} else {
throw error
} }
throw error
} }
} }
} }
function translateErrorMessage(jsonFile: string, error: RequestError): string { function translateErrorMessage(jsonFile: string, error: RequestError): string {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile) const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${String(error)}` const mainWarning = `Dependency submission failed for ${relativeJsonFile}.\n${error.message}`
if (error.message === 'Resource not accessible by integration') { if (error.message === 'Resource not accessible by integration') {
return `${mainWarning} return `${mainWarning}
Please ensure that the 'contents: write' permission is available for the workflow job. Please ensure that the 'contents: write' permission is available for the workflow job.
@@ -156,45 +216,6 @@ async function submitDependencyGraphFile(jsonFile: string): Promise<void> {
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile) const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`) core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
} }
async function downloadDependencyGraphs(): Promise<string[]> {
const findBy = github.context.payload.workflow_run
? {
token: getGithubToken(),
workflowRunId: github.context.payload.workflow_run.id,
repositoryName: github.context.repo.repo,
repositoryOwner: github.context.repo.owner
}
: undefined
const artifactClient = new DefaultArtifactClient()
const dependencyGraphArtifacts = (
await artifactClient.listArtifacts({
latest: true,
findBy
})
).artifacts.filter(artifact => artifact.name.startsWith(DEPENDENCY_GRAPH_PREFIX))
for (const artifact of dependencyGraphArtifacts) {
const downloadedArtifact = await artifactClient.downloadArtifact(artifact.id, {
findBy
})
core.info(`Downloading dependency-graph artifact ${artifact.name} to ${downloadedArtifact.downloadPath}`)
}
return findDependencyGraphFiles()
}
async function findDependencyGraphFiles(): Promise<string[]> {
const globber = await glob.create(`${getReportDirectory()}/**/*.json`)
const allFiles = await globber.glob()
const unprocessedFiles = allFiles.filter(file => !isProcessed(file))
unprocessedFiles.forEach(markProcessed)
core.info(`Found dependency graph files: ${unprocessedFiles.join(', ')}`)
return unprocessedFiles
}
function getReportDirectory(): string { function getReportDirectory(): string {
return process.env.DEPENDENCY_GRAPH_REPORT_DIR! return process.env.DEPENDENCY_GRAPH_REPORT_DIR!
} }

View File

@@ -3,6 +3,7 @@ import {getActionId} from './configuration'
const DEPRECATION_UPGRADE_PAGE = 'https://github.com/gradle/actions/blob/main/docs/deprecation-upgrade-guide.md' const DEPRECATION_UPGRADE_PAGE = 'https://github.com/gradle/actions/blob/main/docs/deprecation-upgrade-guide.md'
const recordedDeprecations: Deprecation[] = [] const recordedDeprecations: Deprecation[] = []
const recordedErrors: string[] = []
export class Deprecation { export class Deprecation {
constructor(readonly message: string) {} constructor(readonly message: string) {}
@@ -22,10 +23,21 @@ export function recordDeprecation(message: string): void {
} }
} }
export function failOnUseOfRemovedFeature(removalMessage: string, deprecationMessage: string = removalMessage): void {
const deprecation = new Deprecation(deprecationMessage)
const errorMessage = `${removalMessage}.\nSee ${deprecation.getDocumentationLink()}`
recordedErrors.push(errorMessage)
core.setFailed(errorMessage)
}
export function getDeprecations(): Deprecation[] { export function getDeprecations(): Deprecation[] {
return recordedDeprecations return recordedDeprecations
} }
export function getErrors(): string[] {
return recordedErrors
}
export function emitDeprecationWarnings(hasJobSummary = true): void { export function emitDeprecationWarnings(hasJobSummary = true): void {
if (recordedDeprecations.length > 0) { if (recordedDeprecations.length > 0) {
core.warning( core.warning(
@@ -38,17 +50,21 @@ export function emitDeprecationWarnings(hasJobSummary = true): void {
} }
export function saveDeprecationState(): void { export function saveDeprecationState(): void {
core.saveState('deprecations', JSON.stringify(recordedDeprecations)) core.saveState('deprecation-collector_deprecations', JSON.stringify(recordedDeprecations))
core.saveState('deprecation-collector_errors', JSON.stringify(recordedErrors))
} }
export function restoreDeprecationState(): void { export function restoreDeprecationState(): void {
const stringRep = core.getState('deprecations') const savedDeprecations = core.getState('deprecation-collector_deprecations')
if (stringRep === '') { if (savedDeprecations) {
return
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
JSON.parse(stringRep).forEach((obj: any) => { JSON.parse(savedDeprecations).forEach((obj: any) => {
recordedDeprecations.push(new Deprecation(obj.message)) recordedDeprecations.push(new Deprecation(obj.message))
}) })
}
const savedErrors = core.getState('deprecation-collector_errors')
if (savedErrors) {
recordedErrors.push(...JSON.parse(savedErrors))
}
} }

View File

@@ -0,0 +1,39 @@
import * as core from '@actions/core'
import {BuildScanConfig} from '../configuration'
import {setupToken} from './short-lived-token'
export async function setup(config: BuildScanConfig): Promise<void> {
maybeExportVariable('DEVELOCITY_INJECTION_INIT_SCRIPT_NAME', 'gradle-actions.inject-develocity.init.gradle')
maybeExportVariable('DEVELOCITY_AUTO_INJECTION_CUSTOM_VALUE', 'gradle-actions')
if (config.getBuildScanPublishEnabled()) {
maybeExportVariable('DEVELOCITY_INJECTION_ENABLED', 'true')
maybeExportVariable('DEVELOCITY_PLUGIN_VERSION', '3.17.6')
maybeExportVariable('DEVELOCITY_CCUD_PLUGIN_VERSION', '2.0')
maybeExportVariable('DEVELOCITY_TERMS_OF_USE_URL', config.getBuildScanTermsOfUseUrl())
maybeExportVariable('DEVELOCITY_TERMS_OF_USE_AGREE', config.getBuildScanTermsOfUseAgree())
}
maybeExportVariableNotEmpty('DEVELOCITY_INJECTION_ENABLED', config.getDevelocityInjectionEnabled())
maybeExportVariableNotEmpty('DEVELOCITY_URL', config.getDevelocityUrl())
maybeExportVariableNotEmpty('DEVELOCITY_ALLOW_UNTRUSTED_SERVER', config.getDevelocityAllowUntrustedServer())
maybeExportVariableNotEmpty('DEVELOCITY_CAPTURE_FILE_FINGERPRINTS', config.getDevelocityCaptureFileFingerprints())
maybeExportVariableNotEmpty('DEVELOCITY_ENFORCE_URL', config.getDevelocityEnforceUrl())
maybeExportVariableNotEmpty('DEVELOCITY_PLUGIN_VERSION', config.getDevelocityPluginVersion())
maybeExportVariableNotEmpty('GRADLE_PLUGIN_REPOSITORY_URL', config.getGradlePluginRepositoryUrl())
maybeExportVariableNotEmpty('GRADLE_PLUGIN_REPOSITORY_USERNAME', config.getGradlePluginRepositoryUsername())
maybeExportVariableNotEmpty('GRADLE_PLUGIN_REPOSITORY_PASSWORD', config.getGradlePluginRepositoryPassword())
return setupToken(config.getDevelocityAccessKey(), config.getDevelocityTokenExpiry())
}
function maybeExportVariable(variableName: string, value: unknown): void {
if (!process.env[variableName]) {
core.exportVariable(variableName, value)
}
}
function maybeExportVariableNotEmpty(variableName: string, value: unknown): void {
if (value !== null && value !== undefined && value !== '') {
maybeExportVariable(variableName, value)
}
}

View File

@@ -0,0 +1,160 @@
import * as httpm from 'typed-rest-client/HttpClient'
import * as core from '@actions/core'
import {BuildScanConfig} from '../configuration'
import {recordDeprecation} from '../deprecation-collector'
export async function setupToken(develocityAccessKey: string, develocityTokenExpiry: string): Promise<void> {
if (develocityAccessKey) {
try {
core.debug('Fetching short-lived token...')
const tokens = await getToken(develocityAccessKey, develocityTokenExpiry)
if (tokens != null && !tokens.isEmpty()) {
core.debug(`Got token(s), setting the access key env vars`)
const token = tokens.raw()
core.setSecret(token)
exportAccessKeyEnvVars(token)
} else {
handleMissingAccessToken()
}
} catch (e) {
handleMissingAccessToken()
core.warning(`Failed to fetch short-lived token, reason: ${e}`)
}
}
}
function exportAccessKeyEnvVars(value: string): void {
;[BuildScanConfig.DevelocityAccessKeyEnvVar, BuildScanConfig.GradleEnterpriseAccessKeyEnvVar].forEach(key =>
core.exportVariable(key, value)
)
}
function handleMissingAccessToken(): void {
core.warning(`Failed to fetch short-lived token for Develocity`)
if (process.env[BuildScanConfig.GradleEnterpriseAccessKeyEnvVar]) {
// We do not clear the GRADLE_ENTERPRISE_ACCESS_KEY env var in v3, to let the users upgrade to DV 2024.1
recordDeprecation(`The ${BuildScanConfig.GradleEnterpriseAccessKeyEnvVar} env var is deprecated`)
}
if (process.env[BuildScanConfig.DevelocityAccessKeyEnvVar]) {
core.warning(`The ${BuildScanConfig.DevelocityAccessKeyEnvVar} env var should be mapped to a short-lived token`)
}
}
export async function getToken(accessKey: string, expiry: string): Promise<DevelocityAccessCredentials | null> {
const empty: Promise<DevelocityAccessCredentials | null> = new Promise(r => r(null))
const develocityAccessKey = DevelocityAccessCredentials.parse(accessKey)
const shortLivedTokenClient = new ShortLivedTokenClient()
if (develocityAccessKey == null) {
return empty
}
const tokens = new Array<HostnameAccessKey>()
for (const k of develocityAccessKey.keys) {
try {
core.info(`Requesting short-lived Develocity access token for ${k.hostname}`)
const token = await shortLivedTokenClient.fetchToken(`https://${k.hostname}`, k, expiry)
tokens.push(token)
} catch (e) {
// Ignore failure to obtain token
core.info(`Failed to obtain short-lived Develocity access token for ${k.hostname}: ${e}`)
}
}
if (tokens.length > 0) {
return DevelocityAccessCredentials.of(tokens)
}
return empty
}
class ShortLivedTokenClient {
httpc = new httpm.HttpClient('gradle/actions/setup-gradle')
maxRetries = 3
retryInterval = 1000
async fetchToken(serverUrl: string, accessKey: HostnameAccessKey, expiry: string): Promise<HostnameAccessKey> {
const queryParams = expiry ? `?expiresInHours${expiry}` : ''
const sanitizedServerUrl = !serverUrl.endsWith('/') ? `${serverUrl}/` : serverUrl
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessKey.key}`
}
let attempts = 0
while (attempts < this.maxRetries) {
try {
const requestUrl = `${sanitizedServerUrl}api/auth/token${queryParams}`
core.debug(`Attempt ${attempts} to fetch short lived token at ${requestUrl}`)
const response = await this.httpc.post(requestUrl, '', headers)
if (response.message.statusCode === 200) {
const text = await response.readBody()
return new Promise<HostnameAccessKey>(resolve => resolve({hostname: accessKey.hostname, key: text}))
}
// This should be only 404
attempts++
if (attempts === this.maxRetries) {
return new Promise((resolve, reject) =>
reject(
new Error(
`Develocity short lived token request failed ${serverUrl} with status code ${response.message.statusCode}`
)
)
)
}
} catch (error) {
attempts++
if (attempts === this.maxRetries) {
return new Promise((resolve, reject) => reject(error))
}
}
await new Promise(resolve => setTimeout(resolve, this.retryInterval))
}
return new Promise((resolve, reject) => reject(new Error('Illegal state')))
}
}
type HostnameAccessKey = {
hostname: string
key: string
}
export class DevelocityAccessCredentials {
static readonly accessKeyRegexp = /^([^;=\s]+=\w+)(;[^;=\s]+=\w+)*$/
readonly keys: HostnameAccessKey[]
private constructor(allKeys: HostnameAccessKey[]) {
this.keys = allKeys
}
static of(allKeys: HostnameAccessKey[]): DevelocityAccessCredentials {
return new DevelocityAccessCredentials(allKeys)
}
private static readonly keyDelimiter = ';'
private static readonly hostDelimiter = '='
static parse(rawKey: string): DevelocityAccessCredentials | null {
if (!this.isValid(rawKey)) {
return null
}
return new DevelocityAccessCredentials(
rawKey.split(this.keyDelimiter).map(hostKey => {
const pair = hostKey.split(this.hostDelimiter)
return {hostname: pair[0], key: pair[1]}
})
)
}
isEmpty(): boolean {
return this.keys.length === 0
}
raw(): string {
return this.keys
.map(k => `${k.hostname}${DevelocityAccessCredentials.hostDelimiter}${k.key}`)
.join(DevelocityAccessCredentials.keyDelimiter)
}
private static isValid(allKeys: string): boolean {
return this.accessKeyRegexp.test(allKeys)
}
}

View File

@@ -22,7 +22,10 @@ export function handleMainActionError(error: unknown): void {
} }
} }
} else if (error instanceof JobFailure) { } else if (error instanceof JobFailure) {
core.setFailed(String(error)) // No stack trace for JobFailure: these are known errors core.setFailed(String(error))
if (error.stack) {
core.info(error.stack)
}
} else { } else {
core.setFailed(String(error)) core.setFailed(String(error))
if (error instanceof Error && error.stack) { if (error instanceof Error && error.stack) {
@@ -34,6 +37,9 @@ export function handleMainActionError(error: unknown): void {
export function handlePostActionError(error: unknown): void { export function handlePostActionError(error: unknown): void {
if (error instanceof JobFailure) { if (error instanceof JobFailure) {
core.setFailed(String(error)) core.setFailed(String(error))
if (error.stack) {
core.info(error.stack)
}
} else { } else {
core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`) core.warning(`Unhandled error in Gradle post-action - job will continue: ${error}`)
if (error instanceof Error && error.stack) { if (error instanceof Error && error.stack) {

View File

@@ -1,9 +1,11 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import which from 'which'
import * as httpm from '@actions/http-client' import * as httpm from '@actions/http-client'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as cache from '@actions/cache' import * as cache from '@actions/cache'
import * as exec from '@actions/exec'
import * as toolCache from '@actions/tool-cache' import * as toolCache from '@actions/tool-cache'
import * as gradlew from './gradlew' import * as gradlew from './gradlew'
@@ -95,12 +97,18 @@ async function findGradleVersionDeclaration(version: string): Promise<GradleVers
async function installGradleVersion(versionInfo: GradleVersionInfo): Promise<string> { async function installGradleVersion(versionInfo: GradleVersionInfo): Promise<string> {
return core.group(`Provision Gradle ${versionInfo.version}`, async () => { return core.group(`Provision Gradle ${versionInfo.version}`, async () => {
const preInstalledGradle = await findGradleVersionOnPath(versionInfo)
if (preInstalledGradle !== undefined) {
core.info(`Gradle version ${versionInfo.version} is already available on PATH. Not installing.`)
return preInstalledGradle
}
return locateGradleAndDownloadIfRequired(versionInfo) return locateGradleAndDownloadIfRequired(versionInfo)
}) })
} }
async function locateGradleAndDownloadIfRequired(versionInfo: GradleVersionInfo): Promise<string> { async function locateGradleAndDownloadIfRequired(versionInfo: GradleVersionInfo): Promise<string> {
const installsDir = path.join(os.homedir(), 'gradle-installations/installs') const installsDir = path.join(getProvisionDir(), 'installs')
const installDir = path.join(installsDir, `gradle-${versionInfo.version}`) const installDir = path.join(installsDir, `gradle-${versionInfo.version}`)
if (fs.existsSync(installDir)) { if (fs.existsSync(installDir)) {
core.info(`Gradle installation already exists at ${installDir}`) core.info(`Gradle installation already exists at ${installDir}`)
@@ -119,7 +127,7 @@ async function locateGradleAndDownloadIfRequired(versionInfo: GradleVersionInfo)
} }
async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo): Promise<string> { async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo): Promise<string> {
const downloadPath = path.join(os.homedir(), `gradle-installations/downloads/gradle-${versionInfo.version}-bin.zip`) const downloadPath = path.join(getProvisionDir(), `downloads/gradle-${versionInfo.version}-bin.zip`)
// TODO: Convert this to a class and inject config // TODO: Convert this to a class and inject config
const cacheConfig = new CacheConfig() const cacheConfig = new CacheConfig()
@@ -152,6 +160,11 @@ async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo
return downloadPath return downloadPath
} }
function getProvisionDir(): string {
const tmpDir = process.env['RUNNER_TEMP'] ?? os.tmpdir()
return path.join(tmpDir, `.gradle-actions/gradle-installations`)
}
async function downloadGradleDistribution(versionInfo: GradleVersionInfo, downloadPath: string): Promise<void> { async function downloadGradleDistribution(versionInfo: GradleVersionInfo, downloadPath: string): Promise<void> {
await toolCache.downloadTool(versionInfo.downloadUrl, downloadPath) await toolCache.downloadTool(versionInfo.downloadUrl, downloadPath)
core.info(`Downloaded ${versionInfo.downloadUrl} to ${downloadPath} (size ${fs.statSync(downloadPath).size})`) core.info(`Downloaded ${versionInfo.downloadUrl} to ${downloadPath} (size ${fs.statSync(downloadPath).size})`)
@@ -179,3 +192,15 @@ interface GradleVersionInfo {
version: string version: string
downloadUrl: string downloadUrl: string
} }
async function findGradleVersionOnPath(versionInfo: GradleVersionInfo): Promise<string | undefined> {
const gradleExecutable = await which('gradle', {nothrow: true})
if (gradleExecutable) {
const output = await exec.getExecOutput(gradleExecutable, ['-v'], {silent: true})
if (output.stdout.includes(`Gradle ${versionInfo.version}`)) {
return gradleExecutable
}
}
return undefined
}

View File

@@ -2,18 +2,25 @@ import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import {BuildResult} from './build-results' import {BuildResults, BuildResult} from './build-results'
import {SummaryConfig, getActionId, getGithubToken} from './configuration' import {SummaryConfig, getActionId, getGithubToken} from './configuration'
import {Deprecation, getDeprecations} from './deprecation-collector' import {Deprecation, getDeprecations, getErrors} from './deprecation-collector'
export async function generateJobSummary( export async function generateJobSummary(
buildResults: BuildResult[], buildResults: BuildResults,
cachingReport: string, cachingReport: string,
config: SummaryConfig config: SummaryConfig
): Promise<void> { ): Promise<void> {
const summaryTable = renderSummaryTable(buildResults) const errors = renderErrors()
if (errors) {
core.summary.addRaw(errors)
await core.summary.write()
return
}
const hasFailure = buildResults.some(result => result.buildFailed) const summaryTable = renderSummaryTable(buildResults.results)
const hasFailure = buildResults.anyFailed()
if (config.shouldGenerateJobSummary(hasFailure)) { if (config.shouldGenerateJobSummary(hasFailure)) {
core.info('Generating Job Summary') core.info('Generating Job Summary')
@@ -78,10 +85,18 @@ Note that this permission is never available for a workflow triggered from a rep
return mainWarning return mainWarning
} }
function renderSummaryTable(results: BuildResult[]): string { export function renderSummaryTable(results: BuildResult[]): string {
return `${renderDeprecations()}\n${renderBuildResults(results)}` return `${renderDeprecations()}\n${renderBuildResults(results)}`
} }
function renderErrors(): string | undefined {
const errors = getErrors()
if (errors.length === 0) {
return undefined
}
return errors.map(error => `<b>:x: ${error}</b>`).join('\n')
}
function renderDeprecations(): string { function renderDeprecations(): string {
const deprecations = getDeprecations() const deprecations = getDeprecations()
if (deprecations.length === 0) { if (deprecations.length === 0) {
@@ -113,7 +128,7 @@ function renderBuildResults(results: BuildResult[]): string {
<th>Requested Tasks</th> <th>Requested Tasks</th>
<th>Gradle Version</th> <th>Gradle Version</th>
<th>Build Outcome</th> <th>Build Outcome</th>
<th>Build Scan®</th> <th>Build&nbsp;Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')} </tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table> </table>
` `
@@ -134,23 +149,46 @@ function renderOutcome(result: BuildResult): string {
return result.buildFailed ? ':x:' : ':white_check_mark:' return result.buildFailed ? ':x:' : ':white_check_mark:'
} }
function renderBuildScan(result: BuildResult): string { interface BadgeSpec {
if (result.buildScanFailed) { text: string
return renderBuildScanBadge( alt: string
'PUBLISH_FAILED', color: string
'orange', logo: boolean
'https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting' targetUrl: string
)
}
if (result.buildScanUri) {
return renderBuildScanBadge('PUBLISHED', '06A0CE', result.buildScanUri)
}
return renderBuildScanBadge('NOT_PUBLISHED', 'lightgrey', 'https://scans.gradle.com')
} }
function renderBuildScanBadge(outcomeText: string, outcomeColor: string, targetUrl: string): string { function renderBuildScan(result: BuildResult): string {
const badgeUrl = `https://img.shields.io/badge/Build%20Scan%C2%AE-${outcomeText}-${outcomeColor}?logo=Gradle` if (result.buildScanFailed) {
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />` return renderBuildScanBadge({
text: 'Publish failed',
alt: 'Build Scan publish failed',
color: 'orange',
logo: false,
targetUrl: 'https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting'
})
}
if (result.buildScanUri) {
return renderBuildScanBadge({
text: 'Build Scan®',
alt: 'Build Scan published',
color: '06A0CE',
logo: true,
targetUrl: result.buildScanUri
})
}
return renderBuildScanBadge({
text: 'Not published',
alt: 'Build Scan not published',
color: 'lightgrey',
logo: false,
targetUrl: 'https://scans.gradle.com'
})
}
function renderBuildScanBadge({text, alt, color, logo, targetUrl}: BadgeSpec): string {
const encodedText = encodeURIComponent(text)
const badgeUrl = `https://img.shields.io/badge/${encodedText}-${color}${logo ? '?logo=Gradle' : ''}`
const badgeHtml = `<img src="${badgeUrl}" alt="${alt}" />`
return `<a href="${targetUrl}" rel="nofollow" target="_blank">${badgeHtml}</a>` return `<a href="${targetUrl}" rel="nofollow" target="_blank">${badgeHtml}</a>`
} }

View File

@@ -1,23 +1,27 @@
import org.gradle.tooling.events.* import org.gradle.tooling.events.*
import org.gradle.tooling.events.task.* import org.gradle.tooling.events.task.*
import org.gradle.internal.operations.*
import org.gradle.initialization.*
import org.gradle.api.internal.tasks.execution.*
import org.gradle.execution.*
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
// Can't use settingsEvaluated since this script is applied inside a settingsEvaluated handler settingsEvaluated { settings ->
// But projectsEvaluated is good enough, since the build service won't catch configuration failures anyway
projectsEvaluated {
def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-action-buildResultsRecorder", BuildResultsRecorder, { spec -> def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-action-buildResultsRecorder", BuildResultsRecorder, { spec ->
spec.getParameters().getRootProjectName().set(gradle.rootProject.name) spec.getParameters().getRootProjectName().set(settings.rootProject.name)
spec.getParameters().getRootProjectDir().set(gradle.rootProject.rootDir.absolutePath) spec.getParameters().getRootProjectDir().set(settings.rootDir.absolutePath)
spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" ")) spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" "))
spec.getParameters().getGradleHomeDir().set(gradle.gradleHomeDir.absolutePath) spec.getParameters().getGradleHomeDir().set(gradle.gradleHomeDir.absolutePath)
spec.getParameters().getInvocationId().set(gradle.ext.invocationId) spec.getParameters().getInvocationId().set(gradle.ext.invocationId)
}) })
gradle.services.get(BuildEventsListenerRegistry).onTaskCompletion(projectTracker) gradle.services.get(BuildEventListenerRegistryInternal).onOperationCompletion(projectTracker)
} }
abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, OperationCompletionListener, AutoCloseable { abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, BuildOperationListener, AutoCloseable {
private boolean buildFailed = false private boolean buildFailed = false
private boolean configCacheHit = true
interface Params extends BuildServiceParameters { interface Params extends BuildServiceParameters {
Property<String> getRootProjectName() Property<String> getRootProjectName()
Property<String> getRootProjectDir() Property<String> getRootProjectDir()
@@ -26,11 +30,21 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
Property<String> getInvocationId() Property<String> getInvocationId()
} }
public void onFinish(FinishEvent finishEvent) { void started(BuildOperationDescriptor buildOperation, OperationStartEvent startEvent) {}
if (finishEvent instanceof TaskFinishEvent && finishEvent.result instanceof TaskFailureResult) {
void progress(OperationIdentifier operationIdentifier, OperationProgressEvent progressEvent) {}
void finished(BuildOperationDescriptor buildOperation, OperationFinishEvent finishEvent) {
if (buildOperation.details in EvaluateSettingsBuildOperationType.Details) {
// Got EVALUATE SETTINGS event: not a config-cache hit"
configCacheHit = false
}
if (buildOperation.details in RunRootBuildWorkBuildOperationType.Details) {
if (finishEvent.failure != null) {
buildFailed = true buildFailed = true
} }
} }
}
@Override @Override
public void close() { public void close() {
@@ -41,8 +55,7 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
gradleVersion: GradleVersion.current().version, gradleVersion: GradleVersion.current().version,
gradleHomeDir: getParameters().getGradleHomeDir().get(), gradleHomeDir: getParameters().getGradleHomeDir().get(),
buildFailed: buildFailed, buildFailed: buildFailed,
buildScanUri: null, configCacheHit: configCacheHit
buildScanFailed: false
] ]
def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP") def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP")
@@ -52,7 +65,7 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
} }
try { try {
def buildResultsDir = new File(runnerTempDir, ".build-results") def buildResultsDir = new File(runnerTempDir, ".gradle-actions/build-results")
buildResultsDir.mkdirs() buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, githubActionStep + getParameters().getInvocationId().get() + ".json") def buildResultsFile = new File(buildResultsDir, githubActionStep + getParameters().getInvocationId().get() + ".json")
if (!buildResultsFile.exists()) { if (!buildResultsFile.exists()) {

View File

@@ -13,6 +13,7 @@ def GE_EXTENSION = "gradleEnterprise"
// Only run against root build. Do not run against included builds. // Only run against root build. Do not run against included builds.
def isTopLevelBuild = gradle.getParent() == null def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) { if (isTopLevelBuild) {
def resultsWriter = new ResultsWriter()
def version = GradleVersion.current().baseVersion def version = GradleVersion.current().baseVersion
def atLeastGradle3 = version >= GradleVersion.version("3.0") def atLeastGradle3 = version >= GradleVersion.version("3.0")
@@ -21,59 +22,77 @@ if (isTopLevelBuild) {
def invocationId = "-${System.currentTimeMillis()}" def invocationId = "-${System.currentTimeMillis()}"
if (atLeastGradle6) { if (atLeastGradle6) {
def useBuildService = version >= GradleVersion.version("6.6")
settingsEvaluated { settings ->
// By default, use standard mechanisms to capture build results // By default, use standard mechanisms to capture build results
def useBuildService = version >= GradleVersion.version("6.6")
if (useBuildService) { if (useBuildService) {
captureUsingBuildService(settings, invocationId) captureUsingBuildService(invocationId)
} else { } else {
captureUsingBuildFinished(gradle, invocationId) captureUsingBuildFinished(gradle, invocationId, resultsWriter)
} }
// Use the Develocity plugin to also capture build scan links, when available
settingsEvaluated { settings ->
settings.pluginManager.withPlugin(GE_PLUGIN_ID) { settings.pluginManager.withPlugin(GE_PLUGIN_ID) {
// Only execute if develocity plugin isn't applied. // Only execute if develocity plugin isn't applied.
if (!settings.extensions.findByName(DEVELOCITY_EXTENSION)) { if (!settings.extensions.findByName(DEVELOCITY_EXTENSION)) {
captureUsingBuildScanPublished(settings.extensions[GE_EXTENSION].buildScan, settings.rootProject, invocationId) captureUsingBuildScanPublished(settings.extensions[GE_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
settings.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { settings.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) {
captureUsingBuildScanPublished(settings.extensions[DEVELOCITY_EXTENSION].buildScan, settings.rootProject, invocationId) captureUsingBuildScanPublished(settings.extensions[DEVELOCITY_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
} else if (atLeastGradle3) { } else if (atLeastGradle3) {
projectsEvaluated { gradle -> projectsEvaluated { gradle ->
// By default, use 'buildFinished' to capture build results // By default, use 'buildFinished' to capture build results
captureUsingBuildFinished(gradle, invocationId) captureUsingBuildFinished(gradle, invocationId, resultsWriter)
gradle.rootProject.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) { gradle.rootProject.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) {
// Only execute if develocity plugin isn't applied. // Only execute if develocity plugin isn't applied.
if (!gradle.rootProject.extensions.findByName(DEVELOCITY_EXTENSION)) { if (!gradle.rootProject.extensions.findByName(DEVELOCITY_EXTENSION)) {
captureUsingBuildScanPublished(gradle.rootProject.extensions[BUILD_SCAN_EXTENSION], gradle.rootProject, invocationId) captureUsingBuildScanPublished(gradle.rootProject.extensions[BUILD_SCAN_EXTENSION], invocationId, resultsWriter)
} }
} }
gradle.rootProject.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { gradle.rootProject.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) {
captureUsingBuildScanPublished(gradle.rootProject.extensions[DEVELOCITY_EXTENSION].buildScan, gradle.rootProject, invocationId) captureUsingBuildScanPublished(gradle.rootProject.extensions[DEVELOCITY_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
} }
} }
def captureUsingBuildService(invocationId) {
gradle.ext.invocationId = invocationId
apply from: 'gradle-actions.build-result-capture-service.plugin.groovy'
}
void captureUsingBuildFinished(gradle, String invocationId, ResultsWriter resultsWriter) {
gradle.buildFinished { result ->
println "Got buildFinished: ${result}"
def buildResults = [
rootProjectName: rootProject.name,
rootProjectDir: rootProject.projectDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: result.failure != null,
configCacheHit: false
]
resultsWriter.writeToResultsFile("build-results", invocationId, buildResults)
}
}
// The `buildScanPublished` hook allows the capture of the Build Scan URI. // The `buildScanPublished` hook allows the capture of the Build Scan URI.
// Results captured this way will overwrite any results from 'buildFinished'. // Results captured this way will overwrite any results from 'buildFinished'.
def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId) { void captureUsingBuildScanPublished(buildScanExtension, String invocationId, ResultsWriter resultsWriter) {
buildScanExtension.with { buildScanExtension.with {
def buildResults = new BuildResults(invocationId, gradle, rootProject)
buildFinished { result ->
buildResults.setBuildResult(result)
}
buildScanPublished { buildScan -> buildScanPublished { buildScan ->
buildResults.setBuildScanUri(buildScan.buildScanUri.toASCIIString()) def scanResults = [
buildResults.writeToResultsFile(true) buildScanUri: buildScan.buildScanUri.toASCIIString(),
buildScanFailed: false
]
resultsWriter.writeToResultsFile("build-scans", invocationId, scanResults)
def githubOutput = System.getenv("GITHUB_OUTPUT") def githubOutput = System.getenv("GITHUB_OUTPUT")
if (githubOutput) { if (githubOutput) {
@@ -85,63 +104,17 @@ def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId
} }
onError { error -> onError { error ->
buildResults.setBuildScanFailed() def scanResults = [
buildResults.writeToResultsFile(true)
}
}
}
def captureUsingBuildFinished(gradle, invocationId) {
gradle.buildFinished { result ->
println "Got buildFinished: ${result}"
def buildResults = new BuildResults(invocationId, gradle, gradle.rootProject)
buildResults.setBuildResult(result)
buildResults.writeToResultsFile(false)
}
}
def captureUsingBuildService(settings, invocationId) {
gradle.ext.invocationId = invocationId
apply from: 'gradle-actions.build-result-capture-service.plugin.groovy'
}
class BuildResults {
def invocationId
def buildResults
BuildResults(String invocationId, def gradle, def rootProject) {
this.invocationId = invocationId
buildResults = [
rootProjectName: rootProject.name,
rootProjectDir: rootProject.projectDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: false,
buildScanUri: null, buildScanUri: null,
buildScanFailed: false buildScanFailed: true
] ]
resultsWriter.writeToResultsFile("build-scans", invocationId, scanResults)
} }
}
}
def setBuildResult(def result) { class ResultsWriter {
try { void writeToResultsFile(String subDir, String invocationId, def content) {
// Gradle and old Build Scan/Gradle Enterprise plugins report a single optional failure in the build result
buildResults['buildFailed'] = result.failure != null
} catch (Exception e) {
// Develocity plugin unwraps all build failures and reports them as a mandatory array
buildResults['buildFailed'] = !result.failures.empty
}
}
def setBuildScanUri(def buildScanUrl) {
buildResults['buildScanUri'] = buildScanUrl
}
def setBuildScanFailed() {
buildResults['buildScanFailed'] = true
}
def writeToResultsFile(boolean overwrite) {
def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP") def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP")
def githubActionStep = System.getProperty("GITHUB_ACTION") ?: System.getenv("GITHUB_ACTION") def githubActionStep = System.getProperty("GITHUB_ACTION") ?: System.getenv("GITHUB_ACTION")
if (!runnerTempDir || !githubActionStep) { if (!runnerTempDir || !githubActionStep) {
@@ -149,19 +122,12 @@ class BuildResults {
} }
try { try {
def buildResultsDir = new File(runnerTempDir, ".build-results") def buildResultsDir = new File(runnerTempDir, ".gradle-actions/${subDir}")
buildResultsDir.mkdirs() buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, githubActionStep + invocationId + ".json") def buildResultsFile = new File(buildResultsDir, githubActionStep + invocationId + ".json")
if (!buildResultsFile.exists()) {
// Overwrite any contents written by buildFinished or build service, since this result is a superset. buildResultsFile << groovy.json.JsonOutput.toJson(content)
if (buildResultsFile.exists()) {
if (overwrite) {
buildResultsFile.text = groovy.json.JsonOutput.toJson(buildResults)
} }
} else {
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
} catch (Exception e) { } catch (Exception e) {
println "\ngradle action failed to write build-results file. Will continue.\n> ${e.getLocalizedMessage()}" println "\ngradle action failed to write build-results file. Will continue.\n> ${e.getLocalizedMessage()}"
} }

View File

@@ -6,7 +6,7 @@ buildscript {
def pluginRepositoryUrl = getInputParam('gradle.plugin-repository.url') ?: 'https://plugins.gradle.org/m2' def pluginRepositoryUrl = getInputParam('gradle.plugin-repository.url') ?: 'https://plugins.gradle.org/m2'
def pluginRepositoryUsername = getInputParam('gradle.plugin-repository.username') def pluginRepositoryUsername = getInputParam('gradle.plugin-repository.username')
def pluginRepositoryPassword = getInputParam('gradle.plugin-repository.password') def pluginRepositoryPassword = getInputParam('gradle.plugin-repository.password')
def dependencyGraphPluginVersion = getInputParam('dependency-graph-plugin.version') ?: '1.3.0' def dependencyGraphPluginVersion = getInputParam('dependency-graph-plugin.version') ?: '1.3.1'
logger.lifecycle("Resolving dependency graph plugin ${dependencyGraphPluginVersion} from plugin repository: ${pluginRepositoryUrl}") logger.lifecycle("Resolving dependency graph plugin ${dependencyGraphPluginVersion} from plugin repository: ${pluginRepositoryUrl}")
repositories { repositories {

View File

@@ -12,7 +12,7 @@ if (gradleVersion < GradleVersion.version("5.2") ||
if (getVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE') != "true") { if (getVariable('GITHUB_DEPENDENCY_GRAPH_CONTINUE_ON_FAILURE') != "true") {
throw new GradleException("Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated.") throw new GradleException("Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated.")
} }
println "::warning::Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated." logger.warn("::warning::Dependency Graph is not supported for ${gradleVersion}. No dependency snapshot will be generated.")
return return
} }
@@ -23,11 +23,11 @@ if (isTopLevelBuild) {
def reportFile = getUniqueReportFile(getVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR')) def reportFile = getUniqueReportFile(getVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR'))
if (reportFile == null) { if (reportFile == null) {
println "::warning::No dependency snapshot generated for step. Could not determine unique job correlator - specify GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR var for this step." logger.warn("::warning::No dependency snapshot generated for step. Could not determine unique job correlator - specify GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR var for this step.")
return return
} }
println "Generating dependency graph into '${reportFile}'" logger.lifecycle("Generating dependency graph into '${reportFile}'")
} }
apply from: 'gradle-actions.github-dependency-graph-gradle-plugin-apply.groovy' apply from: 'gradle-actions.github-dependency-graph-gradle-plugin-apply.groovy'

View File

@@ -1,16 +1,20 @@
/*
* Initscript for injection of Develocity into Gradle builds.
* Version: v1.0
*/
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
// note that there is no mechanism to share code between the initscript{} block and the main script, so some logic is duplicated
// conditionally apply the Develocity plugin to the classpath so it can be applied to the build further down in this script
initscript { initscript {
// NOTE: there is no mechanism to share code between the initscript{} block and the main script, so some logic is duplicated
def isTopLevelBuild = !gradle.parent def isTopLevelBuild = !gradle.parent
if (!isTopLevelBuild) { if (!isTopLevelBuild) {
return return
} }
def getInputParam = { String name -> def getInputParam = { String name ->
def envVarName = name.toUpperCase().replace('.', '_').replace('-', '_') def ENV_VAR_PREFIX = ''
def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_')
return System.getProperty(name) ?: System.getenv(envVarName) return System.getProperty(name) ?: System.getenv(envVarName)
} }
@@ -20,9 +24,9 @@ initscript {
return return
} }
// finish early if injection is disabled // Plugin loading is only required for Develocity injection. Abort early if not enabled.
def gradleInjectionEnabled = getInputParam("develocity.injection-enabled") def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam("develocity.injection-enabled"))
if (gradleInjectionEnabled != "true") { if (!develocityInjectionEnabled) {
return return
} }
@@ -75,29 +79,17 @@ initscript {
} }
} }
def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan' static getInputParam(String name) {
def BUILD_SCAN_PLUGIN_CLASS = 'com.gradle.scan.plugin.BuildScanPlugin' def ENV_VAR_PREFIX = ''
def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_')
def GRADLE_ENTERPRISE_PLUGIN_ID = 'com.gradle.enterprise' return System.getProperty(name) ?: System.getenv(envVarName)
def GRADLE_ENTERPRISE_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin' }
def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity'
def DEVELOCITY_PLUGIN_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityPlugin'
def CI_AUTO_INJECTION_CUSTOM_VALUE_NAME = 'CI auto injection'
def CCUD_PLUGIN_ID = 'com.gradle.common-custom-user-data-gradle-plugin'
def CCUD_PLUGIN_CLASS = 'com.gradle.CommonCustomUserDataGradlePlugin'
def isTopLevelBuild = !gradle.parent def isTopLevelBuild = !gradle.parent
if (!isTopLevelBuild) { if (!isTopLevelBuild) {
return return
} }
def getInputParam = { String name ->
def envVarName = name.toUpperCase().replace('.', '_').replace('-', '_')
return System.getProperty(name) ?: System.getenv(envVarName)
}
def requestedInitScriptName = getInputParam('develocity.injection.init-script-name') def requestedInitScriptName = getInputParam('develocity.injection.init-script-name')
def initScriptName = buildscript.sourceFile.name def initScriptName = buildscript.sourceFile.name
if (requestedInitScriptName != initScriptName) { if (requestedInitScriptName != initScriptName) {
@@ -105,42 +97,62 @@ if (requestedInitScriptName != initScriptName) {
return return
} }
// finish early if injection is disabled def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam("develocity.injection-enabled"))
def gradleInjectionEnabled = getInputParam("develocity.injection-enabled") if (develocityInjectionEnabled) {
if (gradleInjectionEnabled != "true") { enableDevelocityInjection()
return
} }
def develocityUrl = getInputParam('develocity.url') // To enable build-scan capture, a `captureBuildScanLink(String)` method must be added to `BuildScanCollector`.
def develocityAllowUntrustedServer = Boolean.parseBoolean(getInputParam('develocity.allow-untrusted-server')) def buildScanCollector = new BuildScanCollector()
def develocityEnforceUrl = Boolean.parseBoolean(getInputParam('develocity.enforce-url')) def buildScanCaptureEnabled = buildScanCollector.metaClass.respondsTo(buildScanCollector, 'captureBuildScanLink', String)
def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam('develocity.build-scan.upload-in-background')) if (buildScanCaptureEnabled) {
def develocityCaptureFileFingerprints = getInputParam('develocity.capture-file-fingerprints') ? Boolean.parseBoolean(getInputParam('develocity.capture-file-fingerprints')) : true enableBuildScanLinkCapture(buildScanCollector)
def develocityPluginVersion = getInputParam('develocity.plugin.version') }
def ccudPluginVersion = getInputParam('develocity.ccud-plugin.version')
def buildScanTermsOfUseUrl = getInputParam('develocity.terms-of-use.url')
def buildScanTermsOfUseAgree = getInputParam('develocity.terms-of-use.agree')
def ciAutoInjectionCustomValueValue = getInputParam('develocity.auto-injection.custom-value')
def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0') void enableDevelocityInjection() {
def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0') def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan'
def shouldApplyDevelocityPlugin = atLeastGradle5 && develocityPluginVersion && isAtLeast(develocityPluginVersion, '3.17') def BUILD_SCAN_PLUGIN_CLASS = 'com.gradle.scan.plugin.BuildScanPlugin'
def dvOrGe = { def dvValue, def geValue -> def GRADLE_ENTERPRISE_PLUGIN_ID = 'com.gradle.enterprise'
def GRADLE_ENTERPRISE_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin'
def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity'
def DEVELOCITY_PLUGIN_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityPlugin'
def CI_AUTO_INJECTION_CUSTOM_VALUE_NAME = 'CI auto injection'
def CCUD_PLUGIN_ID = 'com.gradle.common-custom-user-data-gradle-plugin'
def CCUD_PLUGIN_CLASS = 'com.gradle.CommonCustomUserDataGradlePlugin'
def develocityUrl = getInputParam('develocity.url')
def develocityAllowUntrustedServer = Boolean.parseBoolean(getInputParam('develocity.allow-untrusted-server'))
def develocityEnforceUrl = Boolean.parseBoolean(getInputParam('develocity.enforce-url'))
def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam('develocity.build-scan.upload-in-background'))
def develocityCaptureFileFingerprints = getInputParam('develocity.capture-file-fingerprints') ? Boolean.parseBoolean(getInputParam('develocity.capture-file-fingerprints')) : true
def develocityPluginVersion = getInputParam('develocity.plugin.version')
def ccudPluginVersion = getInputParam('develocity.ccud-plugin.version')
def buildScanTermsOfUseUrl = getInputParam('develocity.terms-of-use.url')
def buildScanTermsOfUseAgree = getInputParam('develocity.terms-of-use.agree')
def ciAutoInjectionCustomValueValue = getInputParam('develocity.auto-injection.custom-value')
def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0')
def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0')
def shouldApplyDevelocityPlugin = atLeastGradle5 && develocityPluginVersion && isAtLeast(develocityPluginVersion, '3.17')
def dvOrGe = { def dvValue, def geValue ->
if (shouldApplyDevelocityPlugin) { if (shouldApplyDevelocityPlugin) {
return dvValue instanceof Closure<?> ? dvValue() : dvValue return dvValue instanceof Closure<?> ? dvValue() : dvValue
} }
return geValue instanceof Closure<?> ? geValue() : geValue return geValue instanceof Closure<?> ? geValue() : geValue
} }
// finish early if configuration parameters passed in via system properties are not valid/supported // finish early if configuration parameters passed in via system properties are not valid/supported
if (ccudPluginVersion && isNotAtLeast(ccudPluginVersion, '1.7')) { if (ccudPluginVersion && isNotAtLeast(ccudPluginVersion, '1.7')) {
logger.warn("Common Custom User Data Gradle plugin must be at least 1.7. Configured version is $ccudPluginVersion.") logger.warn("Common Custom User Data Gradle plugin must be at least 1.7. Configured version is $ccudPluginVersion.")
return return
} }
// register buildScanPublished listener and optionally apply the Develocity plugin // Conditionally apply and configure the Develocity plugin
if (GradleVersion.current() < GradleVersion.version('6.0')) { if (GradleVersion.current() < GradleVersion.version('6.0')) {
rootProject { rootProject {
buildscript.configurations.getByName("classpath").incoming.afterResolve { ResolvableDependencies incoming -> buildscript.configurations.getByName("classpath").incoming.afterResolve { ResolvableDependencies incoming ->
def resolutionResult = incoming.resolutionResult def resolutionResult = incoming.resolutionResult
@@ -231,7 +243,7 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
} }
} }
} }
} else { } else {
gradle.settingsEvaluated { settings -> gradle.settingsEvaluated { settings ->
if (develocityPluginVersion) { if (develocityPluginVersion) {
if (!settings.pluginManager.hasPlugin(GRADLE_ENTERPRISE_PLUGIN_ID) && !settings.pluginManager.hasPlugin(DEVELOCITY_PLUGIN_ID)) { if (!settings.pluginManager.hasPlugin(GRADLE_ENTERPRISE_PLUGIN_ID) && !settings.pluginManager.hasPlugin(DEVELOCITY_PLUGIN_ID)) {
@@ -307,6 +319,7 @@ if (GradleVersion.current() < GradleVersion.version('6.0')) {
} }
} }
} }
}
} }
void applyPluginExternally(def pluginManager, String pluginClassName) { void applyPluginExternally(def pluginManager, String pluginClassName) {
@@ -361,3 +374,41 @@ static boolean isAtLeast(String versionUnderTest, String referenceVersion) {
static boolean isNotAtLeast(String versionUnderTest, String referenceVersion) { static boolean isNotAtLeast(String versionUnderTest, String referenceVersion) {
!isAtLeast(versionUnderTest, referenceVersion) !isAtLeast(versionUnderTest, referenceVersion)
} }
void enableBuildScanLinkCapture(BuildScanCollector collector) {
def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan'
def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity'
// Conditionally apply and configure the Develocity plugin
if (GradleVersion.current() < GradleVersion.version('6.0')) {
rootProject {
pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) {
// Only execute if develocity plugin isn't applied.
if (gradle.rootProject.extensions.findByName("develocity")) return
buildScanPublishedAction(buildScan, collector)
}
pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) {
buildScanPublishedAction(develocity.buildScan, collector)
}
}
} else {
gradle.settingsEvaluated { settings ->
eachDevelocitySettingsExtension(settings) { ext ->
buildScanPublishedAction(ext.buildScan, collector)
}
}
}
}
// Action will only be called if a `BuildScanCollector.captureBuildScanLink` method is present.
// Add `void captureBuildScanLink(String) {}` to the `BuildScanCollector` class to respond to buildScanPublished events
static buildScanPublishedAction(def buildScanExtension, BuildScanCollector collector) {
if (buildScanExtension.metaClass.respondsTo(buildScanExtension, 'buildScanPublished', Action)) {
buildScanExtension.buildScanPublished { scan ->
collector.captureBuildScanLink(scan.buildScanUri.toString())
}
}
}
class BuildScanCollector {}

View File

@@ -1,24 +1,34 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import * as os from 'os' import * as os from 'os'
import * as caches from './caching/caches' import * as caches from './caching/caches'
import * as jobSummary from './job-summary' import * as jobSummary from './job-summary'
import * as buildScan from './build-scan' import * as buildScan from './develocity/build-scan'
import {loadBuildResults, markBuildResultsProcessed} from './build-results' import {loadBuildResults, markBuildResultsProcessed} from './build-results'
import {CacheListener, generateCachingReport} from './caching/cache-reporting' import {CacheListener, generateCachingReport} from './caching/cache-reporting'
import {DaemonController} from './daemon-controller' import {DaemonController} from './daemon-controller'
import {BuildScanConfig, CacheConfig, SummaryConfig, getWorkspaceDirectory} from './configuration' import {
import {findInvalidWrapperJars} from './wrapper-validation/validate' BuildScanConfig,
import {JobFailure} from './errors' CacheConfig,
SummaryConfig,
WrapperValidationConfig,
getWorkspaceDirectory
} from './configuration'
import * as wrapperValidator from './wrapper-validation/wrapper-validator'
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED' const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
const USER_HOME = 'USER_HOME' const USER_HOME = 'USER_HOME'
const GRADLE_USER_HOME = 'GRADLE_USER_HOME' const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
const CACHE_LISTENER = 'CACHE_LISTENER' const CACHE_LISTENER = 'CACHE_LISTENER'
export async function setup(cacheConfig: CacheConfig, buildScanConfig: BuildScanConfig): Promise<boolean> { export async function setup(
cacheConfig: CacheConfig,
buildScanConfig: BuildScanConfig,
wrapperValidationConfig: WrapperValidationConfig
): Promise<boolean> {
const userHome = await determineUserHome() const userHome = await determineUserHome()
const gradleUserHome = await determineGradleUserHome() const gradleUserHome = await determineGradleUserHome()
@@ -41,7 +51,9 @@ export async function setup(cacheConfig: CacheConfig, buildScanConfig: BuildScan
core.saveState(CACHE_LISTENER, cacheListener.stringify()) core.saveState(CACHE_LISTENER, cacheListener.stringify())
buildScan.setup(buildScanConfig) await wrapperValidator.validateWrappers(wrapperValidationConfig, getWorkspaceDirectory(), gradleUserHome)
await buildScan.setup(buildScanConfig)
return true return true
} }
@@ -60,7 +72,7 @@ export async function complete(cacheConfig: CacheConfig, summaryConfig: SummaryC
const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER)) const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
const daemonController = new DaemonController(buildResults) const daemonController = new DaemonController(buildResults)
await caches.save(userHome, gradleUserHome, cacheListener, daemonController, cacheConfig) await caches.save(userHome, gradleUserHome, cacheListener, daemonController, buildResults, cacheConfig)
const cachingReport = generateCachingReport(cacheListener) const cachingReport = generateCachingReport(cacheListener)
await jobSummary.generateJobSummary(buildResults, cachingReport, summaryConfig) await jobSummary.generateJobSummary(buildResults, cachingReport, summaryConfig)
@@ -79,7 +91,24 @@ async function determineGradleUserHome(): Promise<string> {
return path.resolve(rootDir, customGradleUserHome) return path.resolve(rootDir, customGradleUserHome)
} }
return path.resolve(await determineUserHome(), '.gradle') const defaultGradleUserHome = path.resolve(await determineUserHome(), '.gradle')
// Use the default Gradle User Home if it already exists
if (fs.existsSync(defaultGradleUserHome)) {
core.info(`Gradle User Home already exists at ${defaultGradleUserHome}`)
core.exportVariable('GRADLE_USER_HOME', defaultGradleUserHome)
return defaultGradleUserHome
}
// Switch Gradle User Home to faster 'D:' drive if possible
if (os.platform() === 'win32' && defaultGradleUserHome.startsWith('C:\\') && fs.existsSync('D:\\a\\')) {
const fasterGradleUserHome = 'D:\\a\\.gradle'
core.info(`Setting GRADLE_USER_HOME to ${fasterGradleUserHome} to leverage (potentially) faster drive.`)
core.exportVariable('GRADLE_USER_HOME', fasterGradleUserHome)
return fasterGradleUserHome
}
core.exportVariable('GRADLE_USER_HOME', defaultGradleUserHome)
return defaultGradleUserHome
} }
/** /**
@@ -98,16 +127,3 @@ async function determineUserHome(): Promise<string> {
core.debug(`Determined user.home from java -version output: '${userHome}'`) core.debug(`Determined user.home from java -version output: '${userHome}'`)
return userHome return userHome
} }
export async function checkNoInvalidWrapperJars(rootDir = getWorkspaceDirectory()): Promise<void> {
const allowedChecksums = process.env['ALLOWED_GRADLE_WRAPPER_CHECKSUMS']?.split(',') || []
const result = await findInvalidWrapperJars(rootDir, 1, false, allowedChecksums)
if (result.isValid()) {
core.info(result.toDisplayString())
} else {
core.info(result.toDisplayString())
throw new JobFailure(
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/actions/blob/main/docs/wrapper-validation.md#reporting-failures\n${result.toDisplayString()}`
)
}
}

View File

@@ -0,0 +1,26 @@
import fs from 'fs'
import path from 'path'
import {ACTION_METADATA_DIR} from '../configuration'
export class ChecksumCache {
private readonly cacheFile: string
constructor(gradleUserHome: string) {
this.cacheFile = path.resolve(gradleUserHome, ACTION_METADATA_DIR, 'valid-wrappers.json')
}
load(): string[] {
// Load previously validated checksums saved in Gradle User Home
if (fs.existsSync(this.cacheFile)) {
return JSON.parse(fs.readFileSync(this.cacheFile, 'utf-8'))
}
return []
}
save(checksums: string[]): void {
const uniqueChecksums = [...new Set(checksums)]
// Save validated checksums to Gradle User Home
fs.mkdirSync(path.dirname(this.cacheFile), {recursive: true})
fs.writeFileSync(this.cacheFile, JSON.stringify(uniqueChecksums))
}
}

View File

@@ -1,24 +1,31 @@
import * as httpm from 'typed-rest-client/HttpClient' import * as httpm from 'typed-rest-client/HttpClient'
import * as cheerio from 'cheerio'
import fileWrapperChecksums from './wrapper-checksums.json' import fileWrapperChecksums from './wrapper-checksums.json'
const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3}) const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3})
function getKnownValidChecksums(): Map<string, Set<string>> { export class WrapperChecksums {
const versionsMap = new Map<string, Set<string>>() checksums = new Map<string, Set<string>>()
versions = new Set<string>()
add(version: string, checksum: string): void {
if (this.checksums.has(checksum)) {
this.checksums.get(checksum)!.add(version)
} else {
this.checksums.set(checksum, new Set([version]))
}
this.versions.add(version)
}
}
function loadKnownChecksums(): WrapperChecksums {
const checksums = new WrapperChecksums()
for (const entry of fileWrapperChecksums) { for (const entry of fileWrapperChecksums) {
const checksum = entry.checksum checksums.add(entry.version, entry.checksum)
let versionNames = versionsMap.get(checksum)
if (versionNames === undefined) {
versionNames = new Set()
versionsMap.set(checksum, versionNames)
} }
return checksums
versionNames.add(entry.version)
}
return versionsMap
} }
/** /**
@@ -26,9 +33,12 @@ function getKnownValidChecksums(): Map<string, Set<string>> {
* *
* Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum. * Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum.
*/ */
export const KNOWN_VALID_CHECKSUMS = getKnownValidChecksums() export const KNOWN_CHECKSUMS = loadKnownChecksums()
export async function fetchValidChecksums(allowSnapshots: boolean): Promise<Set<string>> { export async function fetchUnknownChecksums(
allowSnapshots: boolean,
knownChecksums: WrapperChecksums
): Promise<Set<string>> {
const all = await httpGetJsonArray('https://services.gradle.org/versions/all') const all = await httpGetJsonArray('https://services.gradle.org/versions/all')
const withChecksum = all.filter( const withChecksum = all.filter(
entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl') entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl')
@@ -37,11 +47,23 @@ export async function fetchValidChecksums(allowSnapshots: boolean): Promise<Set<
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => allowSnapshots || !entry.snapshot (entry: any) => allowSnapshots || !entry.snapshot
) )
const checksumUrls = allowed.map( const notKnown = allowed.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => !knownChecksums.versions.has(entry.version)
)
const checksumUrls = notKnown.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => entry.wrapperChecksumUrl as string (entry: any) => entry.wrapperChecksumUrl as string
) )
const checksums = await Promise.all(checksumUrls.map(async (url: string) => httpGetText(url))) if (allowSnapshots) {
await addDistributionSnapshotChecksums(checksumUrls)
}
const checksums = await Promise.all(
checksumUrls.map(async (url: string) => {
// console.log(`Fetching checksum from ${url}`)
return httpGetText(url)
})
)
return new Set(checksums) return new Set(checksums)
} }
@@ -53,3 +75,22 @@ async function httpGetText(url: string): Promise<string> {
const response = await httpc.get(url) const response = await httpc.get(url)
return await response.readBody() return await response.readBody()
} }
// Public for testing
export async function addDistributionSnapshotChecksums(checksumUrls: string[]): Promise<void> {
// Load the index page of the distribution snapshot repository
const indexPage = await httpGetText('https://services.gradle.org/distributions-snapshots/')
// // Extract all wrapper checksum from the index page. These end in -wrapper.jar.sha256
// // Load the HTML into cheerio
const $ = cheerio.load(indexPage)
// // Find all links ending with '-wrapper.jar.sha256'
const wrapperChecksumLinks = $('a[href$="-wrapper.jar.sha256"]')
// build the absolute URL for each wrapper checksum
wrapperChecksumLinks.each((index, element) => {
const url = $(element).attr('href')
checksumUrls.push(`https://services.gradle.org${url}`)
})
}

View File

@@ -8,7 +8,8 @@ export async function findInvalidWrapperJars(
minWrapperCount: number, minWrapperCount: number,
allowSnapshots: boolean, allowSnapshots: boolean,
allowedChecksums: string[], allowedChecksums: string[],
knownValidChecksums: Map<string, Set<string>> = checksums.KNOWN_VALID_CHECKSUMS previouslyValidatedChecksums: string[] = [],
knownValidChecksums: checksums.WrapperChecksums = checksums.KNOWN_CHECKSUMS
): Promise<ValidationResult> { ): Promise<ValidationResult> {
const wrapperJars = await find.findWrapperJars(gitRepoRoot) const wrapperJars = await find.findWrapperJars(gitRepoRoot)
const result = new ValidationResult([], []) const result = new ValidationResult([], [])
@@ -21,7 +22,11 @@ export async function findInvalidWrapperJars(
const notYetValidatedWrappers = [] const notYetValidatedWrappers = []
for (const wrapperJar of wrapperJars) { for (const wrapperJar of wrapperJars) {
const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar)) const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar))
if (allowedChecksums.includes(sha) || knownValidChecksums.has(sha)) { if (
allowedChecksums.includes(sha) ||
previouslyValidatedChecksums.includes(sha) ||
knownValidChecksums.checksums.has(sha)
) {
result.valid.push(new WrapperJar(wrapperJar, sha)) result.valid.push(new WrapperJar(wrapperJar, sha))
} else { } else {
notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha)) notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha))
@@ -31,7 +36,7 @@ export async function findInvalidWrapperJars(
// Otherwise fall back to fetching checksums from Gradle API and compare against them // Otherwise fall back to fetching checksums from Gradle API and compare against them
if (notYetValidatedWrappers.length > 0) { if (notYetValidatedWrappers.length > 0) {
result.fetchedChecksums = true result.fetchedChecksums = true
const fetchedValidChecksums = await checksums.fetchValidChecksums(allowSnapshots) const fetchedValidChecksums = await checksums.fetchUnknownChecksums(allowSnapshots, knownValidChecksums)
for (const wrapperJar of notYetValidatedWrappers) { for (const wrapperJar of notYetValidatedWrappers) {
if (!fetchedValidChecksums.has(wrapperJar.checksum)) { if (!fetchedValidChecksums.has(wrapperJar.checksum)) {

View File

@@ -1,4 +1,28 @@
[ [
{
"version": "8.9",
"checksum": "498495120a03b9a6ab5d155f5de3c8f0d986a449153702fb80fc80e134484f17"
},
{
"version": "8.9-rc-2",
"checksum": "498495120a03b9a6ab5d155f5de3c8f0d986a449153702fb80fc80e134484f17"
},
{
"version": "8.9-rc-1",
"checksum": "498495120a03b9a6ab5d155f5de3c8f0d986a449153702fb80fc80e134484f17"
},
{
"version": "8.8",
"checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8"
},
{
"version": "8.8-rc-2",
"checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8"
},
{
"version": "8.8-rc-1",
"checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8"
},
{ {
"version": "8.7", "version": "8.7",
"checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8"

View File

@@ -0,0 +1,41 @@
import * as core from '@actions/core'
import {WrapperValidationConfig} from '../configuration'
import {ChecksumCache} from './cache'
import {findInvalidWrapperJars} from './validate'
import {JobFailure} from '../errors'
export async function validateWrappers(
config: WrapperValidationConfig,
workspaceRoot: string,
gradleUserHome: string
): Promise<void> {
if (!config.doValidateWrappers()) {
return // Wrapper validation is disabled
}
const checksumCache = new ChecksumCache(gradleUserHome)
const allowedChecksums = process.env['ALLOWED_GRADLE_WRAPPER_CHECKSUMS']?.split(',') || []
const previouslyValidatedChecksums = checksumCache.load()
const result = await findInvalidWrapperJars(
workspaceRoot,
0,
config.allowSnapshotWrappers(),
allowedChecksums,
previouslyValidatedChecksums
)
if (result.isValid()) {
await core.group('All Gradle Wrapper jars are valid', async () => {
core.info(`Loaded previously validated checksums from cache: ${previouslyValidatedChecksums.join(', ')}`)
core.info(result.toDisplayString())
})
} else {
core.info(result.toDisplayString())
throw new JobFailure(
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/actions/blob/main/docs/wrapper-validation.md#validation-failures\n${result.toDisplayString()}`
)
}
checksumCache.save(result.valid.map(wrapper => wrapper.checksum))
}

View File

@@ -20,7 +20,7 @@ dependencies {
testImplementation ('io.ratpack:ratpack-groovy-test:1.9.0') { testImplementation ('io.ratpack:ratpack-groovy-test:1.9.0') {
exclude group: 'org.codehaus.groovy', module: 'groovy-all' exclude group: 'org.codehaus.groovy', module: 'groovy-all'
} }
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.17.0' testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.17.2'
} }
test { test {

Some files were not shown because too many files have changed in this diff Show More