Compare commits

...

118 Commits
v1.0.2 ... v1

Author SHA1 Message Date
Daz DeBoer
b3afdc78a7 Truncate Gradle args when constructing cache key (#71)
Cache keys have a hard limit of 512 characters, so we need to ensure that we don't generate a key longer than this.

- Remove excess whitespace
- Truncate to 400 characters

Fixes #70
2021-08-24 12:46:48 -06:00
Daz DeBoer
e0c2736e35 Include Gradle invocation arguments in cache keys (#69)
This permits a new cache entry to be persisted when a subsequent Gradle invocation does more work that an earlier invocation.

Fixes #68
2021-08-22 17:11:05 -06:00
Daz DeBoer
a63892c289 Log cache entry on save 2021-08-22 14:58:36 -06:00
Daz DeBoer
d432f2086c Provide a better description for the action 2021-08-22 14:48:38 -06:00
Daz DeBoer
eaad2cd2bb Merge pull request #67 from gradle/dd/v15
Allow caching to be enabled read-only
2021-08-22 14:45:57 -06:00
Daz DeBoer
a148b21183 Improve prod workflow
- Use a separate job to test read-only cache
- Use dependency jobs to avoid cache race conditions
2021-08-22 14:42:01 -06:00
Daz DeBoer
e7422f245c Fix typo in README 2021-08-22 14:42:01 -06:00
Daz DeBoer
c86093d76a Build distribution 2021-08-22 14:28:35 -06:00
Daz DeBoer
a693ccda4b Allow use of caches 'read-only'
To avoid evicting useful entries, some pipeline may benefit from using existing
cache entries without writing any changes back to the cache.

Fixes #62
2021-08-22 14:28:34 -06:00
Daz DeBoer
543cacb256 Allow manual trigger of prod workflow 2021-08-20 14:00:30 -06:00
Daz DeBoer
3f3947669a Rename action from "Gradle Command" to "Gradle Build Action" 2021-08-17 09:36:14 -06:00
dependabot[bot]
579711fd3c Bump path-parse from 1.0.6 to 1.0.7 (#65)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-16 15:52:54 -06:00
Daz DeBoer
7336529ec5 Build distribution 2021-08-07 16:13:35 -07:00
Daz DeBoer
88af98fab4 Fail if configuration-cache is enabled without dependencies cache
Fixes #61
2021-08-07 16:13:35 -07:00
Daz DeBoer
01bfa29846 Update metadata and docs for repository move/rename
Fixes #60
2021-08-07 16:13:35 -07:00
Paul Merlin
0206df026c Merge pull request #59 from eskatos/dd/v1-improved
Minor cleanups, simplifications and improvements
2021-07-27 08:45:59 +02:00
Paul Merlin
41aebc770d Fix typo in test workflow
Signed-off-by: Paul Merlin <paul@gradle.com>
2021-07-27 08:22:05 +02:00
Daz DeBoer
bc4d6bddd4 Rebuild index files 2021-07-26 15:45:00 -06:00
Daz DeBoer
1f57b4dd2d Re-add workflow jobs for dependencies and configuration caches 2021-07-26 15:45:00 -06:00
Daz DeBoer
02d4f46354 Use core functionality to access action inputs
- Specify default values in action.yaml definition where appropriate
- Replace custom methods with core functions:
  -  getInputBoolean() with core.getBooleanInput()
  - inputOrNull() with core.getInput()
  - inputArrayOrNull() with core.getMultilineInput()
- Remove github-utils.js
2021-07-26 14:54:59 -06:00
Daz DeBoer
b9684c0cf5 Prefer ‘release-candidate’ instead of ‘rc’
This makes the version alias match other places where we reference a release candidate version.
The 'rc' alias is still supported, but emits a deprecation warning.
2021-07-26 14:54:59 -06:00
Daz DeBoer
5423935c9b Deprecate the 'wrapper-directory' input and recommend 'gradle-executable' instead
Use of a wrapper in a non-standard directory is uncommon, and is effectively handled
by referencing the `gradlew` script as the `gradle-executable`.
2021-07-26 14:54:59 -06:00
Daz DeBoer
41ca2299a5 Ignore .tool-versions directory
This directory is created by the ASDF version manager (https://asdf-vm.com),
and can be used to control the version of nodejs/npm used to run.
2021-07-20 11:20:21 -06:00
Daz DeBoer
33e91b639d Cache Gradle distributions downloaded for a particular version (#58)
- Cache is separate from (but similar to) the wrapper distribution cache
- New 'distributions-cache-enabled' flag controls caching of all downloaded distributions
  (including wrapper distributions)
- Deprecated the 'wrapper-cache-enabled' flag for removal in v2
2021-07-08 12:22:48 -06:00
Daz DeBoer
e4ec586f46 Only cache the wrapper ZIP, not the exploded dir
Prior to this change, the wrapper cache contained both the downloaded zip
file as well as the exploded wrapper dir. Only the zip file is required,
as Gradle will automatically detect and unpack.
2021-07-08 08:01:44 -06:00
Daz DeBoer
15a8123fbc Validate presense of gradle wrapper to provide better feedback
- Provide a more useful error message when no Gradle wrapper can be located,
  and 'gradle-version' or 'gradle-executable' is not used.
- Add test for case where wrapper is missing.
  This isn't really a "test" per-se, but this failing build invocation makes it
  easy to verify the GitHub action behaviour when the build is misconfigured.
2021-07-08 08:01:44 -06:00
Daz DeBoer
f0c6ac01d3 Add test for gradle-executable parameter
Use matrix to allow different script suffix on windows
2021-07-06 13:29:55 -06:00
Daz DeBoer
63fea55da4 Add a very-limited ‘no-wrapper’ test Gradle build
Will use this for testing Gradle execution with different versions and mechanisms.
2021-07-06 13:29:55 -06:00
Daz DeBoer
26b92e3f5c Separate unit-test data from integ-test samples
- Upgraded `samples/basic` to use latest Gradle version.
2021-07-06 13:29:55 -06:00
Daz DeBoer
643092d2fc Ensure that test invocations are targeted at a specific use-case
- Remove the 'gradle --stop' step from the prod workflow.
  We either need to stop all instances started, or rely on GitHub to clean up processes on completion.
- Remove configuration-cache and dependencies-cache from basic tests. We will later need to add
  tests invocations specific for these features.
2021-07-06 13:29:55 -06:00
Daz DeBoer
18c8a679dc Look for gradle wrapper in build-root-directory by default
This removes the need to specify `wrapper-directory` when using a Gradle
project that is not located in the root of the workspace.

Fixes #44.
2021-07-06 09:56:27 -06:00
Paul Merlin
13d33a88ca Merge pull request #52 from eskatos/dd/update-versions
Update dependency versions
2021-07-06 11:47:55 +02:00
Daz DeBoer
738bda9866 Use built-in library functions in preference to custom implementations
- Use built-in `hashFiles` function included in '@actions/globv0.2.0'
- Use `downloadTool` and `extractZip` functions from '@actions/tool-cache'
2021-07-05 13:57:22 -06:00
Daz DeBoer
47c9af9d7d Rebuild outputs 2021-07-05 12:40:53 -06:00
Daz DeBoer
3fba6132b4 Update devDependencies versions 2021-07-05 12:40:37 -06:00
Daz DeBoer
127b9b6624 Update to latest lib dependencies 2021-07-05 12:40:16 -06:00
Daz DeBoer
8189d29e96 Run 'npm update'
Get latest versions of transitive deps.
2021-07-05 12:20:36 -06:00
Daz DeBoer
eb7aa853fa Run 'npm audit fix'
Updates transitive library versions to address known vulnerabilities
2021-07-05 12:05:56 -06:00
Tomaz Muraus
15bf8034d6 Fix typo, the directory which contains dependency locks is named
gradle/dependency-locks.
2020-12-22 12:13:55 +01:00
paul
6bf2690b23 Upgrade dependencies 2020-12-22 12:11:50 +01:00
Benedikt Ritter
ced6c34563 Add worknig example for build scan comment
Replaces the dummy example for how to automatically add a comment containing the build scan URL
with a working example using the [GitHub Script action](https://github.com/marketplace/actions/github-script#comment-on-an-issue).

See https://github.com/britter/maven-plugin-development/pull/56#issuecomment-708406163 for how it looks in action.
2020-10-14 15:57:13 +02:00
Paul Merlin
2efcc22ff5 Replace typed-rest-client with @actions/http-client 2020-09-13 14:17:37 +02:00
Paul Merlin
cb2742a00b Protect RC provisioning from network problems and add logging 2020-09-13 13:30:54 +02:00
Paul Merlin
c8c53f54bb Let CI exercises using latest Gradle RC 2020-09-13 13:25:31 +02:00
Paul Merlin
863daedf86 Bump test build wrapper 2020-09-13 13:23:48 +02:00
Paul Merlin
55871cbb47 Simplify build scan url extraction 2020-09-13 13:09:14 +02:00
Paul Merlin
e3d60b3873 Protect wrapper cache restore against tar errors 2020-09-13 13:06:00 +02:00
Paul Merlin
2dd6cc1801 Upgrade dependencies 2020-09-13 12:37:40 +02:00
Paul Merlin
f11e7d60d7 Short-circuit dependencies and configuration caching if cache dir already exists
This is relevant if you run this action several times in a single job.
This prevent doing unnecessary work starting with the second job using the action.
This prevent droping dependencies downloaded by the first job using the action.
This prevent Windows agents to fail unlinking already existing files.
2020-06-22 16:40:58 +02:00
Paul Merlin
7137b09ae2 Upgrade dependencies 2020-06-22 16:36:25 +02:00
Paul Merlin
053f389907 CI prod check workflow stops Gradle daemon at the end
to allow for dependency caching to work on windows
2020-06-15 20:08:27 +02:00
Paul Merlin
4f9b5202aa Faster CI 2020-06-15 20:03:30 +02:00
Paul Merlin
466a737d16 Only restore wrapper cache if dir doesn't exist 2020-06-15 19:33:04 +02:00
Paul Merlin
eb7eb78d95 Refine README 2020-06-15 16:50:15 +02:00
Paul Merlin
682d7347f7 Optional dependencies cache
and bonus configuration cache

Merge branch 'eskatos/caching'
2020-06-15 16:36:19 +02:00
Paul Merlin
3c43b6525f Add some documentation around caching 2020-06-15 16:23:01 +02:00
Paul Merlin
aefd8348d2 Enable configuration cache on CI 2020-06-15 16:03:04 +02:00
Paul Merlin
f2de61db4e Enable configuration cache on CI 2020-06-15 15:58:50 +02:00
Paul Merlin
4c7d97cca4 Add caching of Gradle build configuration cache 2020-06-15 15:58:20 +02:00
Paul Merlin
806543fb3a Enable dependencies cache on CI 2020-06-15 15:57:47 +02:00
Paul Merlin
2afa86ca9f Disable dependencies cache by default 2020-06-15 15:57:09 +02:00
Paul Merlin
ae50675399 Better warning message 2020-06-15 15:43:21 +02:00
Paul Merlin
d7a54a26c7 Don't drop gc.properties in dependencies cache
because it would disable Gradle cache cleanup
2020-06-15 15:41:53 +02:00
Paul Merlin
02a8a21e55 Add input to disable wrapper caching 2020-06-15 15:41:09 +02:00
Paul Merlin
3abad5567a Add input to disable dependencies caching altogether 2020-06-15 14:32:54 +02:00
Paul Merlin
7c8cc1a9ef Fix typo 2020-06-15 14:28:57 +02:00
Paul Merlin
355e9c1f86 Try delete dependencies lock files and disable caching if not possible 2020-06-15 14:22:41 +02:00
Paul Merlin
26dd4cb9bb Make file hashing more robust 2020-06-15 14:09:44 +02:00
Paul Merlin
317ca35dca Refine dependencies caching 2020-06-15 13:16:51 +02:00
Paul Merlin
053762c1c1 Add input to make the dependencies caching strict 2020-06-15 13:05:36 +02:00
Paul Merlin
95e20daa83 Automatic caching of dependencies
in a best effort manner by default
allowing to specify files to hash for computing the cache key
2020-06-15 13:04:42 +02:00
Paul Merlin
fcc1683d01 Extract github actions utils 2020-06-14 19:05:03 +02:00
Paul Merlin
059f2e7791 Rename cache.ts to cache-wrapper.ts 2020-06-14 19:04:18 +02:00
Paul Merlin
692fda9de7 Cache wrapper install when gradle-executable points to a wrapper 2020-06-14 18:01:54 +02:00
Paul Merlin
e8885a31b8 Refine README 2020-06-14 12:57:10 +02:00
Paul Merlin
12e24e843d Fix for Windows
Merge branch 'eskatos/fix-windows'
2020-06-14 12:39:47 +02:00
Paul Merlin
b35e929b1d Simplify CI 2020-06-14 12:36:06 +02:00
Paul Merlin
c839ac993c Locate user home in an OS agnostic way 2020-06-14 12:28:17 +02:00
Paul Merlin
83e6d042d7 Run CI on linux, macos and windows 2020-06-14 12:22:21 +02:00
Paul Merlin
b995a7b937 Merge pull request #19 from eskatos/eskatos/june
Automatically cache wrapper installation
2020-06-13 16:31:47 +02:00
Paul Merlin
1c1db193aa Cache wrapper installation 2020-06-13 16:15:53 +02:00
Paul Merlin
9675f09de6 Compute wrapper cache key 2020-06-13 15:51:58 +02:00
Paul Merlin
392bcac1c1 Fix state 2020-06-13 15:03:30 +02:00
Paul Merlin
a2ba1beedb Debugging 2020-06-13 15:01:08 +02:00
Paul Merlin
42e2fed267 Cache wrapper dist, step 1 2020-06-13 14:48:54 +02:00
Paul Merlin
8e2fd532f1 Fix dist download 2020-06-13 14:37:12 +02:00
Paul Merlin
2bf5eec3b6 Test dist download on CI 2020-06-13 14:33:32 +02:00
Paul Merlin
43efc20423 Run lint on CI 2020-06-13 14:33:04 +02:00
Paul Merlin
4a9a0a05a3 Lint 3 2020-06-13 14:30:57 +02:00
Paul Merlin
1647b85e82 Lint 2 2020-06-13 14:21:54 +02:00
Paul Merlin
6cee865aea Lint 2020-06-13 13:58:25 +02:00
Paul Merlin
5c61ab77ec Add back CI check for uncommited changes 2020-06-13 13:46:53 +02:00
Paul Merlin
a31de8476d Format 2020-06-13 13:44:30 +02:00
Paul Merlin
b2c379621c Add @action/cache 2020-06-13 13:42:27 +02:00
Paul Merlin
04a5ee4df2 Cleanup 2020-06-13 13:40:38 +02:00
Paul Merlin
a188e7cd61 Debug logging 2020-06-13 13:36:11 +02:00
Paul Merlin
e0644c97f9 Split action, step 2 2020-06-13 13:34:07 +02:00
Paul Merlin
9cc76cdea7 Split action, step 1 2020-06-13 13:30:20 +02:00
Paul Merlin
b55d63f1f8 Refine CI workflows 2020-06-13 13:17:04 +02:00
Paul Merlin
8f6be44bf5 Split dev/prod CI workflows 2020-06-13 13:14:52 +02:00
Paul Merlin
469bf6123e Fix CI 2020-06-13 13:10:37 +02:00
Paul Merlin
9cbb22e130 Test something on CI 2020-06-13 13:09:06 +02:00
Paul Merlin
4c65920ab9 Fix build 2020-06-13 13:03:18 +02:00
Paul Merlin
bad0f7d376 Fix build 2020-06-13 13:00:27 +02:00
Paul Merlin
ab09ae8fad Refine CI 2020-06-13 12:48:35 +02:00
Paul Merlin
2f66fb5679 Add basic test Gradle build 2020-06-13 12:47:27 +02:00
Paul Merlin
1a6a8efda2 Refine CI 2020-06-13 12:47:09 +02:00
Paul Merlin
e5b01ed062 Add more npm scripts 2020-06-13 12:47:01 +02:00
Paul Merlin
3d5e31b7a4 Upgrade dependencies 2020-06-13 12:46:47 +02:00
Paul Merlin
4336c6b886 Add various js build configs 2020-06-13 12:46:29 +02:00
Paul Merlin
f25026ba74 Refine README 2020-06-13 12:45:21 +02:00
Paul Merlin
9f5ce3593a Fix typo 2020-04-18 17:10:08 +02:00
Ivan Shcherbak
a5009d1aec Fix typo
Get an error from Github: `pull-request is not a valid event name`

https://help.github.com/en/actions/reference/events-that-trigger-workflows#example-using-a-list-of-events
2020-04-18 17:09:21 +02:00
Frieder Bluemle
ae5079ecac Fix GitHub spelling 2020-03-17 22:36:04 +01:00
Paul Merlin
38fdc3f684 Upgrade dependencies 2020-01-12 11:39:57 +01:00
Paul Merlin
0821518fd9 Merge pull request #6 from musketyr/patch-1
ignore version called "wrapper"
2019-12-09 15:18:28 +01:00
Vladimir Orany
c8b76ea3f7 Update README.md 2019-12-09 10:46:30 +01:00
Vladimir Orany
bf26498bc4 ignore version called "wrapper"
fixes #5
2019-12-09 10:44:21 +01:00
50 changed files with 6471 additions and 3080 deletions

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
dist/
lib/
node_modules/

52
.eslintrc.json Normal file
View File

@@ -0,0 +1,52 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": ["error", {"ignoreStringArrays": true}],
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}

View File

@@ -1,25 +1,24 @@
name: "PR Checks"
on: [pull_request, push]
# make sure the build works and doesn't produce spurious changes
name: dev
on:
pull_request:
push:
jobs:
check_pr:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: "npm ci"
run: npm ci
- name: "npm run build"
run: npm run build
- name: "npm run test"
run: npm run test
- name: "check for uncommitted changes"
- name: Checkout sources
uses: actions/checkout@v2
- name: Build
run: |
npm install
npm run all
- name: Check for uncommitted changes
# Ensure no changes, but ignore node_modules dir since dev/fresh ci deps installed.
run: |
git diff --exit-code --stat -- . ':!node_modules' \
|| (echo "##[error] found changed files after build. please 'npm run build && npm run format'" \
|| (echo "##[error] found changed files after build. please 'npm run all'" \
"and check in all changes" \
&& exit 1)

124
.github/workflows/prod.yml vendored Normal file
View File

@@ -0,0 +1,124 @@
# make sure the action works on a clean machine without building
name: prod
on:
pull_request:
push:
workflow_dispatch:
jobs:
basic-build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test
gradle-execution:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use defined Gradle version
uses: ./
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help
- name: Test use Gradle version alias
uses: ./
with:
gradle-version: release-candidate
build-root-directory: __tests__/samples/no-wrapper
arguments: help
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: __tests__/samples/basic/gradlew${{ matrix.script-suffix }}
build-root-directory: __tests__/samples/no-wrapper
arguments: help
- name: Test custom wrapper location (deprecated)
uses: ./
with:
build-root-directory: __tests__/samples/no-wrapper
wrapper-directory: __tests__/samples/basic
arguments: help
dependencies-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test dependencies-cache-enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --no-daemon
dependencies-cache-enabled: true
configuration-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test configuration-cache-enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --configuration-cache --no-daemon
configuration-cache-enabled: true
dependencies-cache-enabled: true
# Configuration cache requires dependencies cache, since it assumes dependencies are already downloaded.
cache-read-only:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test cache-read-only
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --no-daemon
dependencies-cache-enabled: true
configuration-cache-enabled: true
cache-read-only: true
failures: # These build invocations are informational only, and are expected to fail
needs: basic-build
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test wrapper missing
uses: ./
continue-on-error: true
with:
build-root-directory: __tests__/samples/no-wrapper
arguments: help

21
.gitignore vendored
View File

@@ -1,10 +1,6 @@
lib/
# Dependency directory
node_modules
.idea
*.iml
__tests__/runner/*
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
@@ -93,3 +89,18 @@ typings/
# DynamoDB Local files
.dynamodb/
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
__tests__/runner/*
# lib/**/*
# IntelliJ IDEA config files
.idea/
*.iml
# ASDF tool configuration
.tool-versions

3
.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
dist/
lib/
node_modules/

11
.prettierrc.json Normal file
View File

@@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

166
README.md
View File

@@ -1,19 +1,15 @@
# Execute Gradle commands in Github Actions workflows
# Execute Gradle builds in GitHub Actions workflows
This Github Action can be used to run arbitrary Gradle commands on any platform supported by Github Actions.
You might also be interested by the related [Gradle Plugin](https://github.com/eskatos/gradle-github-actions-plugin) that allows your build to easily get Github Actions environment and tag Gradle Build Scans accordingly.
This GitHub Action can be used to execute a Gradle build on any platform supported by GitHub Actions.
## Usage
The following workflow will run `gradle build` using the wrapper from the repository on ubuntu, macos and windows. The only prerequisite is to have Java installed, you can define the version you need to run the build using the `actions/setup-java` action.
The following workflow will run `./gradlew build` using the wrapper from the repository on ubuntu, macos and windows. The only prerequisite is to have Java installed: you define the version of Java you need to run the build using the `actions/setup-java` action.
```yaml
# .github/workflows/gradle-build-pr.yml
name: Run Gradle on PRs
on: pull-request
on: pull_request
jobs:
gradle:
strategy:
@@ -21,11 +17,11 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
arguments: build
```
@@ -46,10 +42,10 @@ arguments: build -DsystemProperty=bar
See `gradle --help` for more information.
If you need to pass environment variables, simply use the Github Actions workflow syntax:
If you need to pass environment variables, simply use the GitHub Actions workflow syntax:
```yaml
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
env:
CI: true
```
@@ -57,33 +53,35 @@ If you need to pass environment variables, simply use the Github Actions workflo
## Run a build from a different directory
```yaml
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
build-root-directory: some/subdirectory
```
## Use a specific `gradle` executable
```yaml
- uses: gradle/gradle-build-action@v1
with:
gradle-executable: path/to/gradle
```
## Use a Gradle wrapper from a different directory
```yaml
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
wrapper-directory: path/to/wrapper-directory
gradle-executable: path/to/gradlew
```
## Use a specific `gradle` executable
```yaml
- uses: eskatos/gradle-command-action@v1
with:
gradle-executable: path/to/gradle
```
NOTE: The `wrapper-directory` input has been deprecated. Use `gradle-executable` instead.
## Setup and use a declared Gradle version
```yaml
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
gradle-version: 5.6.2
gradle-version: 6.5
```
`gradle-version` can be set to any valid Gradle version.
@@ -92,10 +90,11 @@ Moreover, you can use the following aliases:
| Alias | Selects |
| --- |---|
| `current` | The current [stable release](https://gradle.org/install/) |
| `rc` | The current [release candidate](https://gradle.org/release-candidate/) if any, otherwise fallback to `current` |
| `nightly` | The latest [nightly](https://gradle.org/nightly/), fails if none. |
| `release-nightly` | The latest [release nightly](https://gradle.org/release-nightly/), fails if none. |
| `wrapper` | The Gradle wrapper's version (default, useful for matrix builds) |
| `current` | The current [stable release](https://gradle.org/install/) |
| `release-candidate` | The current [release candidate](https://gradle.org/release-candidate/) if any, otherwise fallback to `current` |
| `nightly` | The latest [nightly](https://gradle.org/nightly/), fails if none. |
| `release-nightly` | The latest [release nightly](https://gradle.org/release-nightly/), fails if none. |
This can be handy to, for example, automatically test your build with the next Gradle version once a release candidate is out:
@@ -109,19 +108,100 @@ jobs:
gradle-rc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
gradle-version: rc
gradle-version: release-candidate
arguments: build --dry-run # just test build configuration
```
# Build scans
## Caching
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-command-action` action will emit the link to the published build scan as an output named `build-scan-url`.
This action provides 3 levels of caching to help speed up your GitHub Actions:
- `distributions` caches any downloaded Gradle zips, including any downloaded [wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) versions, saving time downloading Gradle distributions ;
- `dependencies` caches the [dependencies](https://docs.gradle.org/current/userguide/dependency_resolution.html#sub:cache_copy), saving time downloading dependencies ;
- `configuration` caches the [build configuration](https://docs.gradle.org/nightly/userguide/configuration_cache.html), saving time configuring the build.
Only the first one, caching downloaded distributions, is enabled by default.
Future versions of this action will enable all caching by default.
You can control which level is enabled as follows:
```yaml
distributions-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
```
NOTE: The `wrapper-cache-enabled` flag has been deprecated, replaced by `distributions-cache-enabled` which enables caching for all downloaded distributions, including Gradle wrapper downloads.
The distributions cache is simple and can't be configured further.
The dependencies and configuration cache will compute a cache key in a best effort manner.
Keep reading to learn how to better control how they work.
Note that enabling configuration cache without the dependencies cache is not permitted, since a hit in the configuration cache assumes that dependencies are already present in the local dependencies cache.
### Configuring the dependencies and configuration caches
Both the dependencies and configuration caches use the same default configuration:
They use the following inputs to calculate the cache key:
```text
**/*.gradle
**/*.gradle.kts
**/gradle.properties
gradle/**
```
This is a good enough approximation.
They restore cached state even if there isn't an exact match.
If the defaults don't suit your needs you can override them with the following inputs:
```yaml
dependencies-cache-key: |
**/gradle.properties
gradle/dependency-locks/**
dependencies-cache-exact: true
configuration-cache-key: |
**/gradle.properties
gradle/dependency-locks/**
configuration-cache-exact: true
```
Coming up with a good cache key isn't trivial and depends on your build.
The above example isn't realistic.
Stick to the defaults unless you know what you are doing.
If you happen to use Gradle [dependency locking](https://docs.gradle.org/current/userguide/dependency_locking.html) you can make the dependencies cache more precise with the following configuration:
```yaml
dependencies-cache-enabled: true
dependencies-cache-key: gradle/dependency-locks/**
dependencies-cache-exact: true
```
### Using the caches read-only
Cache storage space is limited for GitHub actions, and writing new cache entries can trigger the deletion of exising entries.
In some circumstances, it makes sense for a Gradle invocation to use any existing cache entries but not to write and changes back.
For example, you may want to write cache entries for builds on your `main` branch, but not for any PR build invocations.
Use the following configuration to avoid writing cache entries for the action invocation:
```yaml
cache-read-only: true
```
## Build scans
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-build-action` action will emit the link to the published build scan as an output named `build-scan-url`.
You can then use that link in subsequent actions of your workflow.
@@ -130,7 +210,7 @@ For example:
```yaml
# .github/workflows/gradle-build-pr.yml
name: Run Gradle on PRs
on: pull-request
on: pull_request
jobs:
gradle:
strategy:
@@ -138,16 +218,24 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: eskatos/gradle-command-action@v1
- uses: gradle/gradle-build-action@v1
with:
arguments: build
id: gradle
- uses: example/action-that-comments-on-the-pr@v0
if: failure()
- name: "Comment build scan url"
uses: actions/github-script@v3
if: github.event_name == 'pull_request' && failure()
with:
comment: Build failed ${{ steps.gradle.outputs.build-scan-url }}
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ ${{ github.workflow }} failed: ${{ steps.gradle.outputs.build-scan-url }}'
})
```

View File

@@ -0,0 +1,65 @@
import * as cacheUtils from '../src/cache-utils'
import * as path from 'path'
describe('cacheUtils-utils', () => {
describe('can hash', () => {
it('a directory', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/gradle')
)
expect(hash).toBe(
process.platform === 'win32'
? '3364336e94e746ce65a31748a6371b7efd7d499e18ad605c74c91cde0edc0a44'
: '63b9f14f65d014e585099c9c274b9dcbddf5cfd1a8978e5a24efb89ff9304348'
)
})
it('a directory with a glob', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['gradle/**']
)
expect(hash).toBe(
process.platform === 'win32'
? '3364336e94e746ce65a31748a6371b7efd7d499e18ad605c74c91cde0edc0a44'
: '63b9f14f65d014e585099c9c274b9dcbddf5cfd1a8978e5a24efb89ff9304348'
)
})
it('a directory with globs', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['**/*.gradle', 'gradle/**']
)
expect(hash).toBe(
process.platform === 'win32'
? 'd9b66fded38f79f601ce745d64ed726a8df8c0b242b02bcd2c1d331f54742ad6'
: 'f42cd10636f09799f4e01cc84e7ae906cc1d9140f1446f8dcd054d19cbc44c2b'
)
})
})
describe('can truncate args', () => {
test('handles zero-length string', () => {
expect(cacheUtils.truncateArgs('')).toBe('')
})
test('leaves short string untouched', () => {
expect(
cacheUtils.truncateArgs('short string that-should-be-untouched')
).toBe('short string that-should-be-untouched')
})
test('truncates long string', () => {
const longString = 'a'.repeat(500)
expect(cacheUtils.truncateArgs(longString)).toBe('a'.repeat(400))
})
test('trims leading and trailing whitespace', () => {
expect(cacheUtils.truncateArgs(' this is an arg ')).toBe(
'this is an arg'
)
})
test('removes repeated whitespace', () => {
expect(
cacheUtils.truncateArgs(
' this one has long \t\n\t\r spaces '
)
).toBe('this one has long spaces')
})
})
})

View File

@@ -0,0 +1,33 @@
import * as cacheWrapper from '../src/cache-wrapper'
import * as path from 'path'
describe('cache', () => {
describe('can extract gradle wrapper slug', () => {
it('from wrapper properties file', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFrom(
path.resolve(
'__tests__/data/cache-wrapper-test/gradle-wrapper.properties'
)
)
expect(version).toBe('6.6.1-bin')
})
it('for -bin dist', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip'
)
expect(version).toBe('6.6.1-bin')
})
it('for -all dist', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-all.zip'
)
expect(version).toBe('6.6.1-all')
})
it('for milestone', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6-milestone-1-all.zip'
)
expect(version).toBe('6.6-milestone-1-all')
})
})
})

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View File

@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation('junit:junit:4.12')
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
__tests__/data/crypto-utils-test/gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
*/
rootProject.name = 'basic'

View File

@@ -0,0 +1,10 @@
package basic;
import org.junit.Test;
public class BasicTest {
@Test
public void test() {
assert true;
}
}

View File

@@ -1,3 +0,0 @@
describe('TODO - Add a test suite', () => {
it('TODO - Add a test', async () => {});
});

View File

@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

5
__tests__/samples/basic/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation('junit:junit:4.12')
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
__tests__/samples/basic/gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

104
__tests__/samples/basic/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
rootProject.name = 'basic'

View File

@@ -0,0 +1,10 @@
package basic;
import org.junit.Test;
public class BasicTest {
@Test
public void test() {
assert true;
}
}

View File

@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View File

@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

View File

@@ -0,0 +1,3 @@
rootProject.name = 'no-wrapper'
println "Using Gradle version: ${gradle.gradleVersion}"

View File

@@ -1,6 +1,5 @@
name: "Gradle Command"
description: 'Execute Gradle Command Line'
author: 'Paul Merlin <paul@nospere.org>'
name: "Gradle Build Action"
description: 'Executes a Gradle build, caching useful state in the GitHub actions cache'
# https://help.github.com/en/articles/metadata-syntax-for-github-actions
@@ -8,6 +7,7 @@ inputs:
wrapper-directory:
description: Path to the Gradle Wrapper directory
required: false
deprecationMessage: Use 'gradle-executable' to point to a gradlew[.bat] file in a non-default location
gradle-executable:
description: Path to the Gradle executable
required: false
@@ -20,6 +20,41 @@ inputs:
arguments:
description: Gradle command line arguments, see gradle --help
required: false
distributions-cache-enabled:
description: Whether caching downloaded Gradle distributions is enabled or not, default to 'true'
required: false
default: true
wrapper-cache-enabled:
description: Whether caching wrapper installation is enabled or not, default to 'true'
required: false
default: true
deprecationMessage: Replaced by 'distributions-cache-enabled' which enables caching for all downloaded Gradle distributions
dependencies-cache-enabled:
description: Whether caching dependencies is enabled or not, default to 'false'
required: false
default: false
dependencies-cache-key:
description: Globs of files to hash in the build root directory, separated by new lines, use best-effort if unset
required: false
dependencies-cache-exact:
description: Whether to restore only if exact match, default to 'false'
required: false
default: false
configuration-cache-enabled:
description: Whether caching build configuration is enabled or not, default to 'false'
required: false
default: false
configuration-cache-key:
description: Globs of files to hash in the build root directory, separated by new lines, use best-effort if unset
required: false
configuration-cache-exact:
description: Whether to restore only if exact match, default to 'false'
required: false
default: false
cache-read-only:
description: When 'true', existing entries will be read from the cache but no entries will be written
required: false
default: false
outputs:
build-scan-url:
@@ -27,7 +62,9 @@ outputs:
runs:
using: 'node12'
main: 'lib/main.js'
main: 'dist/main/index.js'
post: 'dist/post/index.js'
post-if: success()
branding:
icon: 'box'

1
dist/main/index.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/post/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,12 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
moduleFileExtensions: ['js', 'ts', 'json'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
verbose: true,
setupFilesAfterEnv: ['./jest.setup.js']
}

1
jest.setup.js Normal file
View File

@@ -0,0 +1 @@
jest.setTimeout(10000) // in milliseconds

7414
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,19 @@
{
"name": "gradle-command-action",
"name": "gradle-build-action",
"version": "1.0.0",
"description": "Execute Gradle Command Line",
"main": "lib/main.js",
"private": true,
"description": "Execute Gradle Build",
"scripts": {
"build": "tsc",
"test": "jest"
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"build": "ncc build src/main.ts --out dist/main --minify && ncc build src/post.ts --out dist/post --minify",
"test": "jest",
"all": "npm run format && npm run lint && npm run build && npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/eskatos/gradle-command-action.git"
"url": "git+https://github.com/gradle/gradle-build-action.git"
},
"keywords": [
"github",
@@ -20,21 +24,28 @@
"author": "Paul Merlin <paul@nosphere.org>",
"license": "MIT",
"dependencies": {
"@actions/core": "1.1.1",
"@actions/exec": "1.0.1",
"@actions/io": "1.0.1",
"@actions/tool-cache": "1.1.1",
"string-argv": "0.3.1",
"typed-rest-client": "1.5.0",
"unzipper": "0.10.5"
"@actions/cache": "1.0.7",
"@actions/core": "1.4.0",
"@actions/exec": "1.1.0",
"@actions/glob": "0.2.0",
"@actions/http-client": "1.0.11",
"@actions/tool-cache": "1.7.1",
"string-argv": "0.3.1"
},
"devDependencies": {
"@types/jest": "24.0.18",
"@types/node": "12.7.5",
"@types/unzipper": "0.10.0",
"jest": "24.9.0",
"jest-circus": "24.9.0",
"ts-jest": "24.1.0",
"typescript": "3.6.3"
"@types/jest": "26.0.23",
"@types/node": "14.17.3",
"@types/unzipper": "0.10.4",
"@typescript-eslint/parser": "4.28.2",
"@zeit/ncc": "0.22.3",
"eslint": "7.30.0",
"eslint-plugin-github": "4.1.3",
"eslint-plugin-jest": "24.3.6",
"jest": "26.6.3",
"jest-circus": "26.6.3",
"js-yaml": "3.14.1",
"prettier": "2.3.2",
"ts-jest": "26.5.6",
"typescript": "4.3.5"
}
}

113
src/cache-configuration.ts Normal file
View File

@@ -0,0 +1,113 @@
import * as path from 'path'
import * as fs from 'fs'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as cacheUtils from './cache-utils'
import {
inputCacheKeyGlobs,
tryDeleteFiles,
isDependenciesCacheDisabled
} from './cache-dependencies'
const CONFIGURATION_CACHE_PATH = 'CONFIGURATION_CACHE_PATH'
const CONFIGURATION_CACHE_KEY = 'CONFIGURATION_CACHE_KEY'
const CONFIGURATION_CACHE_RESULT = 'CONFIGURATION_CACHE_RESULT'
export async function restoreCachedConfiguration(
rootDir: string
): Promise<void> {
if (isConfigurationCacheDisabled()) return
if (isDependenciesCacheDisabled()) {
throw new Error(
`Must enable dependencies-cache when configuration-cache is enabled`
)
}
const cachePath = path.resolve(rootDir, '.gradle/configuration-cache')
if (fs.existsSync(cachePath)) return
core.saveState(CONFIGURATION_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('configuration-cache-exact')
const cacheKeyPrefix = 'configuration|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('configuration-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
core.saveState(CONFIGURATION_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
core.info(
'Configuration cache not found, expect task graph calculation.'
)
return
}
core.saveState(CONFIGURATION_CACHE_RESULT, cacheResult)
core.info(`Configuration restored from cache key: ${cacheResult}`)
return
}
export async function cacheConfiguration(): Promise<void> {
if (isConfigurationCacheDisabled()) return
const cachePath = core.getState(CONFIGURATION_CACHE_PATH)
const cacheKey = core.getState(CONFIGURATION_CACHE_KEY)
const cacheResult = core.getState(CONFIGURATION_CACHE_RESULT)
if (!cachePath || !fs.existsSync(cachePath)) {
core.debug('No configuration to cache.')
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Configuration cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
const locksDeleted = tryDeleteFiles([
path.resolve(cachePath, 'configuration-cache.lock')
])
if (!locksDeleted) {
core.warning(
'Unable to delete configuration lock files, try using --no-daemon or stopping the daemon last if you have several Gradle steps, not saving cache.'
)
return
}
core.info(`Will cache configuration with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
function isConfigurationCacheDisabled(): boolean {
return !core.getBooleanInput('configuration-cache-enabled')
}

126
src/cache-dependencies.ts Normal file
View File

@@ -0,0 +1,126 @@
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as cacheUtils from './cache-utils'
const DEPENDENCIES_CACHE_PATH = 'DEPENDENCIES_CACHE_PATH'
const DEPENDENCIES_CACHE_KEY = 'DEPENDENCIES_CACHE_KEY'
const DEPENDENCIES_CACHE_RESULT = 'DEPENDENCIES_CACHE_RESULT'
export async function restoreCachedDependencies(
rootDir: string
): Promise<void> {
if (isDependenciesCacheDisabled()) return
const cachePath = path.resolve(os.homedir(), '.gradle/caches/modules-2')
if (fs.existsSync(cachePath)) return
core.saveState(DEPENDENCIES_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('dependencies-cache-exact')
const cacheKeyPrefix = 'dependencies|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('dependencies-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
core.saveState(DEPENDENCIES_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
core.info('Dependencies cache not found, expect dependencies download.')
return
}
core.saveState(DEPENDENCIES_CACHE_RESULT, cacheResult)
core.info(`Dependencies restored from cache key: ${cacheResult}`)
return
}
export async function cacheDependencies(): Promise<void> {
if (isDependenciesCacheDisabled()) return
const cachePath = core.getState(DEPENDENCIES_CACHE_PATH)
const cacheKey = core.getState(DEPENDENCIES_CACHE_KEY)
const cacheResult = core.getState(DEPENDENCIES_CACHE_RESULT)
if (!cachePath || !fs.existsSync(cachePath)) {
core.debug('No dependencies to cache.')
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Dependencies cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
const locksDeleted = tryDeleteFiles([
path.resolve(cachePath, 'modules-2.lock')
])
if (!locksDeleted) {
core.warning(
'Unable to delete dependencies lock files, try using --no-daemon or stopping the daemon last if you have several Gradle steps, not saving cache.'
)
return
}
core.info(`Will cache dependencies with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
export function tryDeleteFiles(filePaths: string[]): boolean {
let failure = false
for (const filePath of filePaths) {
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath)
} catch (error) {
failure = true
}
}
}
return !failure
}
export function isDependenciesCacheDisabled(): boolean {
return !core.getBooleanInput('dependencies-cache-enabled')
}
export function inputCacheKeyGlobs(input: string): string[] {
const inputValue = core.getMultilineInput(input)
return inputValue.length > 0
? inputValue
: [
'**/*.gradle',
'**/*.gradle.kts',
'**/gradle.properties',
'gradle/**'
]
}

17
src/cache-utils.ts Normal file
View File

@@ -0,0 +1,17 @@
import * as path from 'path'
import * as glob from '@actions/glob'
export async function hashFiles(
baseDir: string,
patterns: string[] = ['**'],
followSymbolicLinks = false
): Promise<string | null> {
const combinedPatterns = patterns
.map(pattern => `${baseDir}${path.sep}${pattern}`)
.join('\n')
return glob.hashFiles(combinedPatterns, {followSymbolicLinks})
}
export function truncateArgs(args: string): string {
return args.trim().replace(/\s+/g, ' ').substr(0, 400)
}

132
src/cache-wrapper.ts Normal file
View File

@@ -0,0 +1,132 @@
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
const WRAPPER_SLUG = 'WRAPPER_SLUG'
export async function restoreCachedWrapperDist(
gradlewDirectory: string | null
): Promise<void> {
if (isWrapperCacheDisabled()) return
if (gradlewDirectory == null) return
const wrapperProperties = path.join(
path.resolve(gradlewDirectory),
'gradle/wrapper/gradle-wrapper.properties'
)
const wrapperSlug = extractGradleWrapperSlugFrom(wrapperProperties)
if (!wrapperSlug) {
core.warning(
`Could not calculate wrapper version from ${wrapperProperties}`
)
return
}
const wrapperDir = getWrapperDir(wrapperSlug)
const cacheKey = getCacheKey(wrapperSlug)
const cachePath = getCachePath(wrapperSlug)
// Check if the wrapper has already been downloaded to Gradle User Home
if (fs.existsSync(wrapperDir)) return
try {
const restoredKey = await cache.restoreCache([cachePath], cacheKey)
if (restoredKey) {
core.info(
`Wrapper installation restored from cache key: ${restoredKey}`
)
} else {
core.info(
`Wrapper installation cache not found. Will download and cache with key: ${cacheKey}.`
)
// Save the slug to trigger caching of the downloaded wrapper
core.saveState(WRAPPER_SLUG, wrapperSlug)
}
} catch (error) {
core.info(
`Wrapper installation cache restore failed for key: ${cacheKey}.\n ${error}`
)
}
}
export async function cacheWrapperDist(): Promise<void> {
if (isWrapperCacheDisabled()) return
const wrapperSlug = core.getState(WRAPPER_SLUG)
if (!wrapperSlug) return
const wrapperDir = getWrapperDir(wrapperSlug)
const cacheKey = getCacheKey(wrapperSlug)
const cachePath = getCachePath(wrapperSlug)
if (!fs.existsSync(wrapperDir)) {
core.warning(`No wrapper installation to cache at ${wrapperDir}`)
return
}
core.info(`Will cache wrapper zip ${cachePath} with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
export function extractGradleWrapperSlugFrom(
wrapperProperties: string
): string | null {
const props = fs.readFileSync(wrapperProperties, {encoding: 'utf8'})
const distUrlLine = props
.split('\n')
.find(line => line.startsWith('distributionUrl'))
if (!distUrlLine) return null
return extractGradleWrapperSlugFromDistUri(distUrlLine.substr(16).trim())
}
export function extractGradleWrapperSlugFromDistUri(
distUri: string
): string | null {
const regex = /.*gradle-(.*-(bin|all))\.zip/
const match = distUri.match(regex)
return match ? match[1] : null
}
function isWrapperCacheDisabled(): boolean {
// Check if either 'distributions' or 'wrapper' cache has been disabled
const wrapperCacheEnabled = core.getBooleanInput('wrapper-cache-enabled')
const distributionsCacheEnabled = core.getBooleanInput(
'distributions-cache-enabled'
)
return !wrapperCacheEnabled || !distributionsCacheEnabled
}
function getCacheKey(wrapperSlug: string): string {
return `wrapper-v1-${wrapperSlug}`
}
function getWrapperDir(wrapperSlug: string): string {
return path.resolve(
os.homedir(),
`.gradle/wrapper/dists/gradle-${wrapperSlug}`
)
}
function getCachePath(wrapperSlug: string): string {
return path.resolve(
os.homedir(),
`.gradle/wrapper/dists/gradle-${wrapperSlug}/*/gradle-${wrapperSlug}.zip`
)
}

View File

@@ -1,31 +1,35 @@
import * as exec from "@actions/exec";
import * as exec from '@actions/exec'
import * as cacheDependencies from './cache-dependencies'
import * as cacheConfiguration from './cache-configuration'
export async function execute(
executable: string,
root: string,
argv: string[]
): Promise<BuildResult> {
await cacheDependencies.restoreCachedDependencies(root)
await cacheConfiguration.restoreCachedConfiguration(root)
export async function execute(executable: string, root: string, argv: string[]): Promise<BuildResult> {
let publishing = false;
let buildScanUrl: string | undefined;
let publishing = false
let buildScanUrl: string | undefined
const status: number = await exec.exec(executable, argv, {
cwd: root,
ignoreReturnCode: true,
listeners: {
stdline: (line: string) => {
if (line.startsWith("Publishing build scan...")) {
publishing = true;
if (line.includes('Publishing build scan...')) {
publishing = true
}
if (publishing && line.length == 0) {
publishing = false
}
if (publishing && line.startsWith("http")) {
buildScanUrl = line.trim();
if (publishing && line.startsWith('http')) {
buildScanUrl = line.trim()
publishing = false
}
}
}
});
})
return new BuildResultImpl(status, buildScanUrl);
return new BuildResultImpl(status, buildScanUrl)
}
export interface BuildResult {
@@ -34,9 +38,5 @@ export interface BuildResult {
}
class BuildResultImpl implements BuildResult {
constructor(
readonly status: number,
readonly buildScanUrl?: string
) {
}
constructor(readonly status: number, readonly buildScanUrl?: string) {}
}

View File

@@ -1,11 +1,24 @@
const IS_WINDOWS = process.platform === "win32";
import * as path from 'path'
import fs from 'fs'
const IS_WINDOWS = process.platform === 'win32'
export function wrapperFilename(): string {
return IS_WINDOWS ? "gradlew.bat" : "gradlew";
return IS_WINDOWS ? 'gradlew.bat' : 'gradlew'
}
export function installScriptFilename(): string {
return IS_WINDOWS ? "gradle.bat" : "gradle";
return IS_WINDOWS ? 'gradle.bat' : 'gradle'
}
export function validateGradleWrapper(gradlewDirectory: string): void {
const wrapperProperties = path.resolve(
gradlewDirectory,
'gradle/wrapper/gradle-wrapper.properties'
)
if (!fs.existsSync(wrapperProperties)) {
throw new Error(
`Cannot locate a Gradle wrapper properties file at '${wrapperProperties}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.`
)
}
}

View File

@@ -1,80 +1,82 @@
import * as core from "@actions/core";
import * as path from "path";
import {parseArgsStringToArgv} from "string-argv";
import * as core from '@actions/core'
import * as path from 'path'
import {parseArgsStringToArgv} from 'string-argv'
import * as execution from "./execution";
import * as gradlew from "./gradlew";
import * as provision from "./provision";
import * as cacheWrapper from './cache-wrapper'
import * as execution from './execution'
import * as gradlew from './gradlew'
import * as provision from './provision'
// Invoked by Github Actions
export async function run() {
// Invoked by GitHub Actions
export async function run(): Promise<void> {
try {
const workspaceDirectory = process.env[`GITHUB_WORKSPACE`] || ''
const buildRootDirectory = resolveBuildRootDirectory(workspaceDirectory)
const baseDirectory = process.env[`GITHUB_WORKSPACE`] || "";
let result = await execution.execute(
await resolveGradleExecutable(baseDirectory),
resolveBuildRootDirectory(baseDirectory),
const result = await execution.execute(
await resolveGradleExecutable(
workspaceDirectory,
buildRootDirectory
),
buildRootDirectory,
parseCommandLineArguments()
);
)
if (result.buildScanUrl) {
core.setOutput("build-scan-url", result.buildScanUrl);
core.setOutput('build-scan-url', result.buildScanUrl)
}
if (result.status != 0) {
if (result.status !== 0) {
core.setFailed(`Gradle process exited with status ${result.status}`)
}
} catch (error) {
core.setFailed(error.message);
core.setFailed(error.message)
}
}
run()
run();
async function resolveGradleExecutable(baseDirectory: string): Promise<string> {
const gradleVersion = inputOrNull("gradle-version");
if (gradleVersion != null) {
async function resolveGradleExecutable(
workspaceDirectory: string,
buildRootDirectory: string
): Promise<string> {
const gradleVersion = core.getInput('gradle-version')
if (gradleVersion !== '' && gradleVersion !== 'wrapper') {
return path.resolve(await provision.gradleVersion(gradleVersion))
}
const gradleExecutable = inputOrNull("gradle-executable");
if (gradleExecutable != null) {
return path.resolve(baseDirectory, gradleExecutable)
const gradleExecutable = core.getInput('gradle-executable')
if (gradleExecutable !== '') {
if (gradleExecutable.endsWith(gradlew.wrapperFilename())) {
await cacheWrapper.restoreCachedWrapperDist(
path.resolve(gradleExecutable, '..')
)
}
return path.resolve(workspaceDirectory, gradleExecutable)
}
const wrapperDirectory = inputOrNull("wrapper-directory");
const executableDirectory = wrapperDirectory != null
? path.join(baseDirectory, wrapperDirectory)
: baseDirectory;
const wrapperDirectory = core.getInput('wrapper-directory')
const gradlewDirectory =
wrapperDirectory !== ''
? path.resolve(workspaceDirectory, wrapperDirectory)
: buildRootDirectory
return path.resolve(executableDirectory, gradlew.wrapperFilename());
gradlew.validateGradleWrapper(gradlewDirectory)
await cacheWrapper.restoreCachedWrapperDist(gradlewDirectory)
return path.resolve(gradlewDirectory, gradlew.wrapperFilename())
}
function resolveBuildRootDirectory(baseDirectory: string): string {
let buildRootDirectory = inputOrNull("build-root-directory");
return buildRootDirectory == null
? path.resolve(baseDirectory)
: path.resolve(baseDirectory, buildRootDirectory);
const buildRootDirectory = core.getInput('build-root-directory')
const resolvedBuildRootDirectory =
buildRootDirectory === ''
? path.resolve(baseDirectory)
: path.resolve(baseDirectory, buildRootDirectory)
return resolvedBuildRootDirectory
}
function parseCommandLineArguments(): string[] {
const input = inputOrNull("arguments");
return input == null ? [] : parseArgsStringToArgv(input)
}
function inputOrNull(name: string): string | null {
const inputString = core.getInput(name);
if (inputString.length == 0) {
return null;
}
return inputString
const input = core.getInput('arguments')
return parseArgsStringToArgv(input)
}

20
src/post.ts Normal file
View File

@@ -0,0 +1,20 @@
import * as core from '@actions/core'
import * as cacheWrapper from './cache-wrapper'
import * as cacheDependencies from './cache-dependencies'
import * as cacheConfiguration from './cache-configuration'
// Invoked by GitHub Actions
export async function run(): Promise<void> {
if (isCacheReadOnly()) return
await cacheWrapper.cacheWrapperDist()
await cacheDependencies.cacheDependencies()
await cacheConfiguration.cacheConfiguration()
}
function isCacheReadOnly(): boolean {
return core.getBooleanInput('cache-read-only')
}
run()

View File

@@ -1,163 +1,193 @@
import * as fs from "fs";
import * as path from "path";
import * as httpm from 'typed-rest-client/HttpClient';
import * as unzip from "unzipper"
import * as core from "@actions/core";
import * as io from '@actions/io';
import * as toolCache from "@actions/tool-cache";
import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as httpm from '@actions/http-client'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as toolCache from '@actions/tool-cache'
import * as gradlew from "./gradlew";
const httpc = new httpm.HttpClient("eskatos/gradle-command-action");
const gradleVersionsBaseUrl = "https://services.gradle.org/versions";
import * as gradlew from './gradlew'
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
/**
* @return Gradle executable path
*/
export async function gradleVersion(gradleVersion: string): Promise<string> {
switch (gradleVersion) {
case "current":
return gradleCurrent();
case "rc":
return gradleReleaseCandidate();
case "nightly":
return gradleNightly();
case "release-nightly":
return gradleReleaseNightly();
export async function gradleVersion(version: string): Promise<string> {
switch (version) {
case 'current':
return gradleCurrent()
case 'rc':
core.warning(
`Specifying gradle-version 'rc' has been deprecated. Use 'release-candidate' instead.`
)
return gradleReleaseCandidate()
case 'release-candidate':
return gradleReleaseCandidate()
case 'nightly':
return gradleNightly()
case 'release-nightly':
return gradleReleaseNightly()
default:
return gradle(gradleVersion);
return gradle(version)
}
}
async function gradleCurrent(): Promise<string> {
const json = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/current`);
return provisionGradle(json.version, json.downloadUrl);
const versionInfo = await gradleVersionDeclaration(
`${gradleVersionsBaseUrl}/current`
)
return provisionGradle(versionInfo)
}
async function gradleReleaseCandidate(): Promise<string> {
const json = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/release-candidate`);
if (json) {
return provisionGradle(json.version, json.downloadUrl);
const versionInfo = await gradleVersionDeclaration(
`${gradleVersionsBaseUrl}/release-candidate`
)
if (versionInfo && versionInfo.version && versionInfo.downloadUrl) {
return provisionGradle(versionInfo)
}
return gradleCurrent();
core.info('No current release-candidate found, will fallback to current')
return gradleCurrent()
}
async function gradleNightly(): Promise<string> {
const json = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/nightly`);
return provisionGradle(json.version, json.downloadUrl);
const versionInfo = await gradleVersionDeclaration(
`${gradleVersionsBaseUrl}/nightly`
)
return provisionGradle(versionInfo)
}
async function gradleReleaseNightly(): Promise<string> {
const json = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/release-nightly`);
return provisionGradle(json.version, json.downloadUrl);
const versionInfo = await gradleVersionDeclaration(
`${gradleVersionsBaseUrl}/release-nightly`
)
return provisionGradle(versionInfo)
}
async function gradle(version: string): Promise<string> {
const declaration = await findGradleVersionDeclaration(version);
if (!declaration) {
throw new Error(`Gradle version ${version} does not exists`);
const versionInfo = await findGradleVersionDeclaration(version)
if (!versionInfo) {
throw new Error(`Gradle version ${version} does not exists`)
}
return provisionGradle(declaration.version, declaration.downloadUrl);
return provisionGradle(versionInfo)
}
async function gradleVersionDeclaration(url: string): Promise<any | undefined> {
const json: any = await httpGetJson(url);
return (json.version && json.version.length > 0) ? json : undefined
async function gradleVersionDeclaration(
url: string
): Promise<GradleVersionInfo> {
return await httpGetGradleVersion(url)
}
async function findGradleVersionDeclaration(version: string): Promise<any | undefined> {
const json: any = await httpGetJson(`${gradleVersionsBaseUrl}/all`);
const found: any = json.find((entry: any) => {
return entry.version === version;
});
return found ? found : undefined
async function findGradleVersionDeclaration(
version: string
): Promise<GradleVersionInfo | undefined> {
const gradleVersions = await httpGetGradleVersions(
`${gradleVersionsBaseUrl}/all`
)
return gradleVersions.find((entry: GradleVersionInfo) => {
return entry.version === version
})
}
async function provisionGradle(version: string, url: string): Promise<string> {
const cachedInstall: string = toolCache.find("gradle", version);
if (cachedInstall.length > 0) {
const cachedExecutable = executableFrom(cachedInstall);
core.info(`Provisioned Gradle executable ${cachedExecutable}`);
return cachedExecutable;
async function provisionGradle(
versionInfo: GradleVersionInfo
): Promise<string> {
const installsDir = path.join(os.homedir(), 'gradle-installations/installs')
const installDir = path.join(installsDir, `gradle-${versionInfo.version}`)
if (fs.existsSync(installDir)) {
core.info(`Gradle installation already exists at ${installDir}`)
return executableFrom(installDir)
}
const home = process.env["HOME"] || "";
const tmpdir = path.join(home, "gradle-provision-tmpdir");
const downloadsDir = path.join(tmpdir, "downloads");
const installsDir = path.join(tmpdir, "installs");
await io.mkdirP(downloadsDir);
await io.mkdirP(installsDir);
const downloadPath = await downloadAndCacheGradleDistribution(versionInfo)
await toolCache.extractZip(downloadPath, installsDir)
core.info(`Extracted Gradle ${versionInfo.version} to ${installDir}`)
core.info(`Downloading ${url}`);
const executable = executableFrom(installDir)
fs.chmodSync(executable, '755')
core.info(`Provisioned Gradle executable ${executable}`)
const downloadPath = path.join(downloadsDir, `gradle-${version}-bin.zip`);
await httpDownload(url, downloadPath);
core.info(`Downloaded at ${downloadPath}, size ${fs.statSync(downloadPath).size}`);
await extractZip(downloadPath, installsDir);
const installDir = path.join(installsDir, `gradle-${version}`);
core.info(`Extracted in ${installDir}`);
const executable = executableFrom(installDir);
fs.chmodSync(executable, "755");
core.info(`Provisioned Gradle executable ${executable}`);
toolCache.cacheDir(installDir, "gradle", version);
return executable;
return executable
}
async function downloadAndCacheGradleDistribution(
versionInfo: GradleVersionInfo
): Promise<string> {
const downloadPath = path.join(
os.homedir(),
`gradle-installations/downloads/gradle-${versionInfo.version}-bin.zip`
)
if (isDistributionsCacheDisabled()) {
await downloadGradleDistribution(versionInfo, downloadPath)
return downloadPath
}
const cacheKey = `gradle-${versionInfo.version}`
const restoreKey = await cache.restoreCache([downloadPath], cacheKey)
if (restoreKey) {
core.info(
`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`
)
} else {
core.info(
`Gradle distribution ${versionInfo.version} not found in cache. Will download.`
)
await downloadGradleDistribution(versionInfo, downloadPath)
try {
await cache.saveCache([downloadPath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
}
return downloadPath
}
async function downloadGradleDistribution(
versionInfo: GradleVersionInfo,
downloadPath: string
): Promise<void> {
await toolCache.downloadTool(versionInfo.downloadUrl, downloadPath)
core.info(
`Downloaded ${versionInfo.downloadUrl} to ${downloadPath} (size ${
fs.statSync(downloadPath).size
})`
)
}
function executableFrom(installDir: string): string {
return path.join(installDir, "bin", `${gradlew.installScriptFilename()}`);
return path.join(installDir, 'bin', `${gradlew.installScriptFilename()}`)
}
async function httpGetJson(url: string): Promise<any> {
const response = await httpc.get(url);
const body = await response.readBody();
return JSON.parse(body);
async function httpGetGradleVersion(url: string): Promise<GradleVersionInfo> {
return JSON.parse(await httpGetString(url))
}
async function httpDownload(url: string, path: string): Promise<void> {
return new Promise<void>(function (resolve, reject) {
const writeStream = fs.createWriteStream(path);
httpc.get(url).then(response => {
response.message.pipe(writeStream)
.on("close", () => {
resolve();
})
.on("error", err => {
reject(err)
});
}).catch(reason => {
reject(reason);
});
});
async function httpGetGradleVersions(
url: string
): Promise<GradleVersionInfo[]> {
return JSON.parse(await httpGetString(url))
}
async function extractZip(zip: string, destination: string): Promise<void> {
return new Promise<void>(function (resolve, reject) {
fs.createReadStream(zip)
.pipe(unzip.Extract({"path": destination}))
.on("close", () => {
resolve();
})
.on("error", err => {
reject(err)
});
});
async function httpGetString(url: string): Promise<string> {
const httpClient = new httpm.HttpClient('gradle/gradle-build-action')
const response = await httpClient.get(url)
return response.readBody()
}
function isDistributionsCacheDisabled(): boolean {
return !core.getBooleanInput('distributions-cache-enabled')
}
interface GradleVersionInfo {
version: string
downloadUrl: string
}

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
/* Basic Options */
"incremental": true, /* Enable incremental compilation */
"incremental": false, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "allowJs": true, /* Allow javascript files to be compiled. */