diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 9bd6dad..b7832e5 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -69,6 +69,29 @@ jobs: - name: Verify Java run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}" shell: bash + + setup-java-check-latest: + name: ${{ matrix.distribution }} ${{ matrix.version }} - check-latest flag - ${{ matrix.os }} + needs: setup-java-major-versions + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + distribution: ['adopt', 'zulu'] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: setup-java + uses: ./ + id: setup-java + with: + distribution: ${{ matrix.distribution }} + java-version: 11 + check-latest: true + - name: Verify Java + run: bash __tests__/verify-java.sh "11" "${{ steps.setup-java.outputs.path }}" + shell: bash setup-java-ea-versions-zulu: name: zulu ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }} @@ -169,4 +192,7 @@ jobs: with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.version }} - architecture: x86 \ No newline at end of file + architecture: x86 + - name: Verify Java + run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}" + shell: bash \ No newline at end of file diff --git a/.licenses/npm/@actions/exec.dep.yml b/.licenses/npm/@actions/exec.dep.yml index bf80463..b1effd3 100644 Binary files a/.licenses/npm/@actions/exec.dep.yml and b/.licenses/npm/@actions/exec.dep.yml differ diff --git a/.licenses/npm/@actions/http-client.dep.yml b/.licenses/npm/@actions/http-client.dep.yml index d18a24f..2e3a424 100644 Binary files a/.licenses/npm/@actions/http-client.dep.yml and b/.licenses/npm/@actions/http-client.dep.yml differ diff --git a/.licenses/npm/@actions/tool-cache.dep.yml b/.licenses/npm/@actions/tool-cache.dep.yml index 9bc438f..16376d8 100644 Binary files a/.licenses/npm/@actions/tool-cache.dep.yml and b/.licenses/npm/@actions/tool-cache.dep.yml differ diff --git a/.licenses/npm/@oozcitak/dom.dep.yml b/.licenses/npm/@oozcitak/dom.dep.yml index 6f41b69..a810093 100644 Binary files a/.licenses/npm/@oozcitak/dom.dep.yml and b/.licenses/npm/@oozcitak/dom.dep.yml differ diff --git a/.licenses/npm/@oozcitak/infra-1.0.3.dep.yml b/.licenses/npm/@oozcitak/infra-1.0.3.dep.yml deleted file mode 100644 index 4e6a4fb..0000000 Binary files a/.licenses/npm/@oozcitak/infra-1.0.3.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/infra-1.0.5.dep.yml b/.licenses/npm/@oozcitak/infra-1.0.5.dep.yml deleted file mode 100644 index acee98b..0000000 Binary files a/.licenses/npm/@oozcitak/infra-1.0.5.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/infra.dep.yml b/.licenses/npm/@oozcitak/infra.dep.yml new file mode 100644 index 0000000..eeac49d Binary files /dev/null and b/.licenses/npm/@oozcitak/infra.dep.yml differ diff --git a/.licenses/npm/@oozcitak/url.dep.yml b/.licenses/npm/@oozcitak/url.dep.yml index 6a1c62a..6f3ae39 100644 Binary files a/.licenses/npm/@oozcitak/url.dep.yml and b/.licenses/npm/@oozcitak/url.dep.yml differ diff --git a/.licenses/npm/@oozcitak/util-1.0.1.dep.yml b/.licenses/npm/@oozcitak/util-1.0.1.dep.yml deleted file mode 100644 index 2d7a5a6..0000000 Binary files a/.licenses/npm/@oozcitak/util-1.0.1.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/util-1.0.2.dep.yml b/.licenses/npm/@oozcitak/util-1.0.2.dep.yml deleted file mode 100644 index 0b1fb26..0000000 Binary files a/.licenses/npm/@oozcitak/util-1.0.2.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/util-8.0.0.dep.yml b/.licenses/npm/@oozcitak/util-8.0.0.dep.yml deleted file mode 100644 index a8ff7da..0000000 Binary files a/.licenses/npm/@oozcitak/util-8.0.0.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/util-8.3.3.dep.yml b/.licenses/npm/@oozcitak/util-8.3.3.dep.yml deleted file mode 100644 index 6e54d83..0000000 Binary files a/.licenses/npm/@oozcitak/util-8.3.3.dep.yml and /dev/null differ diff --git a/.licenses/npm/@oozcitak/util.dep.yml b/.licenses/npm/@oozcitak/util.dep.yml new file mode 100644 index 0000000..dcee45c Binary files /dev/null and b/.licenses/npm/@oozcitak/util.dep.yml differ diff --git a/.licenses/npm/@types/node.dep.yml b/.licenses/npm/@types/node.dep.yml new file mode 100644 index 0000000..7584aa9 Binary files /dev/null and b/.licenses/npm/@types/node.dep.yml differ diff --git a/.licenses/npm/argparse.dep.yml b/.licenses/npm/argparse.dep.yml new file mode 100644 index 0000000..2cd2843 Binary files /dev/null and b/.licenses/npm/argparse.dep.yml differ diff --git a/.licenses/npm/esprima.dep.yml b/.licenses/npm/esprima.dep.yml new file mode 100644 index 0000000..538091c Binary files /dev/null and b/.licenses/npm/esprima.dep.yml differ diff --git a/.licenses/npm/js-yaml.dep.yml b/.licenses/npm/js-yaml.dep.yml new file mode 100644 index 0000000..3b8875d Binary files /dev/null and b/.licenses/npm/js-yaml.dep.yml differ diff --git a/.licenses/npm/lru-cache.dep.yml b/.licenses/npm/lru-cache.dep.yml new file mode 100644 index 0000000..920e7dc Binary files /dev/null and b/.licenses/npm/lru-cache.dep.yml differ diff --git a/.licenses/npm/semver.dep.yml b/.licenses/npm/semver-6.3.0.dep.yml similarity index 100% rename from .licenses/npm/semver.dep.yml rename to .licenses/npm/semver-6.3.0.dep.yml diff --git a/.licenses/npm/semver-7.3.4.dep.yml b/.licenses/npm/semver-7.3.4.dep.yml new file mode 100644 index 0000000..3062ac0 Binary files /dev/null and b/.licenses/npm/semver-7.3.4.dep.yml differ diff --git a/.licenses/npm/sprintf-js.dep.yml b/.licenses/npm/sprintf-js.dep.yml new file mode 100644 index 0000000..e715763 Binary files /dev/null and b/.licenses/npm/sprintf-js.dep.yml differ diff --git a/.licenses/npm/xmlbuilder2.dep.yml b/.licenses/npm/xmlbuilder2.dep.yml index 53c33f6..fecc99a 100644 Binary files a/.licenses/npm/xmlbuilder2.dep.yml and b/.licenses/npm/xmlbuilder2.dep.yml differ diff --git a/.licenses/npm/yallist.dep.yml b/.licenses/npm/yallist.dep.yml new file mode 100644 index 0000000..1e1c46f Binary files /dev/null and b/.licenses/npm/yallist.dep.yml differ diff --git a/README.md b/README.md index 84429bd..d7c90e5 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,6 @@ The `java-version` input supports an exact version or a version range using [Sem - more specific versions: `11.0`, `11.0.4`, `8.0.232`, `8.0.282+8` - early access (EA) versions: `15-ea`, `15.0.0-ea`, `15.0.0-ea.2`, `15.0.0+2-ea` -**Note:** 4-digit notation will always force action to skip checking pre-cached versions and download version in runtime. - #### Supported distributions Currently, the following distributions are supported: | Keyword | Distribution | Official site | License | @@ -59,7 +57,26 @@ Currently, the following distributions are supported: **NOTE:** The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions. -#### Testing against different Java versions +### Check latest +In the basic examples above, the `check-latest` flag defaults to `false`. When set to `false`, the action tries to first resolve a version of Java from the local tool cache on the runner. If unable to find a specific version in the cache, the action will download a version of Java. Use the default or set `check-latest` to `false` if you prefer a faster more consistent setup experience that prioritizes trying to use the cached versions at the expense of newer versions sometimes being available for download. + +If `check-latest` is set to `true`, the action first checks if the cached version is the latest one. If the locally cached version is not the most up-to-date, the latest version of Java will be downloaded. Set `check-latest` to `true` if you want the most up-to-date version of Java to always be used. Setting `check-latest` to `true` has performance implications as downloading versions of Java is slower than using cached versions. + +For Java distributions that are not cached on Hosted images, `check-latest` always behaves as `true` and downloads Java on-flight. Check out [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache) for more details about pre-cached Java versions. + + +```yaml +steps: +- uses: actions/checkout@v2 +- uses: actions/setup-java@v2-preview + with: + distribution: 'adopt' + java-version: '11' + check-latest: true +- run: java -cp java HelloWorldApp +``` + +### Testing against different Java versions ```yaml jobs: build: @@ -89,7 +106,7 @@ jobs: - [Testing against different platforms](docs/advanced-usage.md#Testing-against-different-platforms) - [Publishing using Apache Maven](docs/advanced-usage.md#Publishing-using-Apache-Maven) - [Publishing using Gradle](docs/advanced-usage.md#Publishing-using-Gradle) - +- [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache) ## License diff --git a/__tests__/distributors/adopt-installer.test.ts b/__tests__/distributors/adopt-installer.test.ts index 4b4a3c0..9e2825e 100644 --- a/__tests__/distributors/adopt-installer.test.ts +++ b/__tests__/distributors/adopt-installer.test.ts @@ -27,19 +27,19 @@ describe('getAvailableVersions', () => { it.each([ [ - { version: '11', architecture: 'x64', packageType: 'jdk' }, + { version: '11', architecture: 'x64', packageType: 'jdk', checkLatest: false }, 'os=mac&architecture=x64&image_type=jdk&release_type=ga&page_size=20&page=0' ], [ - { version: '11', architecture: 'x86', packageType: 'jdk' }, + { version: '11', architecture: 'x86', packageType: 'jdk', checkLatest: false }, 'os=mac&architecture=x86&image_type=jdk&release_type=ga&page_size=20&page=0' ], [ - { version: '11', architecture: 'x64', packageType: 'jre' }, + { version: '11', architecture: 'x64', packageType: 'jre', checkLatest: false }, 'os=mac&architecture=x64&image_type=jre&release_type=ga&page_size=20&page=0' ], [ - { version: '11-ea', architecture: 'x64', packageType: 'jdk' }, + { version: '11-ea', architecture: 'x64', packageType: 'jdk', checkLatest: false }, 'os=mac&architecture=x64&image_type=jdk&release_type=ea&page_size=20&page=0' ] ])( @@ -79,7 +79,8 @@ describe('getAvailableVersions', () => { const distribution = new AdoptDistribution({ version: '11', architecture: 'x64', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); const availableVersions = await distribution['getAvailableVersions'](); expect(availableVersions).not.toBeNull(); @@ -104,7 +105,8 @@ describe('findPackageForDownload', () => { const distribution = new AdoptDistribution({ version: '11', architecture: 'x64', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => manifestData; const resolvedVersion = await distribution['findPackageForDownload'](input); @@ -115,7 +117,8 @@ describe('findPackageForDownload', () => { const distribution = new AdoptDistribution({ version: '11', architecture: 'x64', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => manifestData; await expect(distribution['findPackageForDownload']('9.0.8')).rejects.toThrowError( @@ -127,7 +130,8 @@ describe('findPackageForDownload', () => { const distribution = new AdoptDistribution({ version: '11', architecture: 'x64', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => manifestData; await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrowError( @@ -139,7 +143,8 @@ describe('findPackageForDownload', () => { const distribution = new AdoptDistribution({ version: '11', architecture: 'x64', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => []; await expect(distribution['findPackageForDownload']('11')).rejects.toThrowError( diff --git a/__tests__/distributors/base-installer.test.ts b/__tests__/distributors/base-installer.test.ts index e23c972..177c7ac 100644 --- a/__tests__/distributors/base-installer.test.ts +++ b/__tests__/distributors/base-installer.test.ts @@ -19,13 +19,13 @@ class EmptyJavaBase extends JavaBase { protected async downloadTool(javaRelease: JavaDownloadRelease): Promise { return { - version: '11.0.8', - path: `/toolcache/${this.toolcacheFolderName}/11.0.8/${this.architecture}` + version: '11.0.9', + path: path.join('toolcache', this.toolcacheFolderName, '11.0.9', this.architecture) }; } protected async findPackageForDownload(range: string): Promise { - const availableVersion = '11.0.8'; + const availableVersion = '11.0.9'; if (!semver.satisfies(availableVersion, range)) { throw new Error('Available version not found'); } @@ -38,7 +38,7 @@ class EmptyJavaBase extends JavaBase { } describe('findInToolcache', () => { - const actualJavaVersion = '11.1.10'; + const actualJavaVersion = '11.0.8'; const javaPath = path.join('Java_Empty_jdk', actualJavaVersion, 'x64'); let mockJavaBase: EmptyJavaBase; @@ -58,21 +58,33 @@ describe('findInToolcache', () => { it.each([ [ - { version: '11', architecture: 'x64', packageType: 'jdk' }, + { version: '11', architecture: 'x64', packageType: 'jdk', checkLatest: false }, { version: actualJavaVersion, path: javaPath } ], [ - { version: '11.1', architecture: 'x64', packageType: 'jdk' }, + { version: '11.0', architecture: 'x64', packageType: 'jdk', checkLatest: false }, { version: actualJavaVersion, path: javaPath } ], [ - { version: '11.1.10', architecture: 'x64', packageType: 'jdk' }, + { version: '11.0.8', architecture: 'x64', packageType: 'jdk', checkLatest: false }, { version: actualJavaVersion, path: javaPath } ], - [{ version: '11', architecture: 'x64', packageType: 'jre' }, null], - [{ version: '8', architecture: 'x64', packageType: 'jdk' }, null], - [{ version: '11', architecture: 'x86', packageType: 'jdk' }, null], - [{ version: '11', architecture: 'x86', packageType: 'jre' }, null] + [ + { version: '11', architecture: 'x64', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPath } + ], + [ + { version: '11.0', architecture: 'x64', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPath } + ], + [ + { version: '11.0.8', architecture: 'x64', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPath } + ], + [{ version: '11', architecture: 'x64', packageType: 'jre', checkLatest: false }, null], + [{ version: '8', architecture: 'x64', packageType: 'jdk', checkLatest: false }, null], + [{ version: '11', architecture: 'x86', packageType: 'jdk', checkLatest: false }, null], + [{ version: '11', architecture: 'x86', packageType: 'jre', checkLatest: false }, null] ])(`should find java for path %s -> %s`, (input, expected) => { spyTcFindAllVersions.mockReturnValue([actualJavaVersion]); spyGetToolcachePath.mockImplementation( @@ -91,39 +103,52 @@ describe('findInToolcache', () => { }); it.each([ - ['11', '11.0.3'], - ['11.0', '11.0.3'], - ['11.0.1', '11.0.1'], - ['11.0.3', '11.0.3'], - ['15', '15.0.2'], - ['x', '15.0.2'], - ['x-ea', '17.4.4-ea'], - ['11-ea', '11.3.2-ea'], - ['11.2-ea', '11.2.1-ea'], - ['11.2.1-ea', '11.2.1-ea'] + ['11', { version: '11.0.3+2', versionInPath: '11.0.3-2' }], + ['11.0', { version: '11.0.3+2', versionInPath: '11.0.3-2' }], + ['11.0.1', { version: '11.0.1', versionInPath: '11.0.1' }], + ['11.0.3', { version: '11.0.3+2', versionInPath: '11.0.3-2' }], + ['15', { version: '15.0.2+4', versionInPath: '15.0.2-4' }], + ['x', { version: '15.0.2+4', versionInPath: '15.0.2-4' }], + ['x-ea', { version: '17.4.4', versionInPath: '17.4.4-ea' }], + ['11-ea', { version: '11.3.3+5.2.1231421', versionInPath: '11.3.3-ea.5.2.1231421' }], + ['11.2-ea', { version: '11.2.1', versionInPath: '11.2.1-ea' }], + ['11.2.1-ea', { version: '11.2.1', versionInPath: '11.2.1-ea' }] ])('should choose correct java from tool-cache for input %s', (input, expected) => { spyTcFindAllVersions.mockReturnValue([ '17.4.4-ea', '11.0.2', - '15.0.2', - '11.0.3', + '15.0.2-4', + '11.0.3-2', '11.2.1-ea', '11.3.2-ea', + '11.3.2-ea.5', + '11.3.3-ea.5.2.1231421', + '12.3.2-0', '11.0.1' ]); spyGetToolcachePath.mockImplementation( (toolname: string, javaVersion: string, architecture: string) => `/hostedtoolcache/${toolname}/${javaVersion}/${architecture}` ); - mockJavaBase = new EmptyJavaBase({ version: input, architecture: 'x64', packageType: 'jdk' }); + mockJavaBase = new EmptyJavaBase({ + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); const foundVersion = mockJavaBase['findInToolcache'](); - expect(foundVersion?.version).toEqual(expected); + expect(foundVersion).toEqual({ + version: expected.version, + path: `/hostedtoolcache/Java_Empty_jdk/${expected.versionInPath}/x64` + }); }); }); describe('setupJava', () => { - const actualJavaVersion = '11.1.10'; - const javaPath = path.join('Java_Empty_jdk', actualJavaVersion, 'x86'); + const actualJavaVersion = '11.0.9'; + const installedJavaVersion = '11.0.8'; + const javaPath = path.join('Java_Empty_jdk', installedJavaVersion, 'x86'); + const javaPathInstalled = path.join('toolcache', 'Java_Empty_jdk', actualJavaVersion, 'x86'); let mockJavaBase: EmptyJavaBase; @@ -145,12 +170,12 @@ describe('setupJava', () => { return ''; } - return semver.satisfies(actualJavaVersion, semverVersion) ? javaPath : ''; + return semver.satisfies(installedJavaVersion, semverVersion) ? javaPath : ''; } ); spyTcFindAllVersions = jest.spyOn(tc, 'findAllVersions'); - spyTcFindAllVersions.mockReturnValue([actualJavaVersion]); + spyTcFindAllVersions.mockReturnValue([installedJavaVersion]); // Spy on core methods spyCoreDebug = jest.spyOn(core, 'debug'); @@ -177,35 +202,41 @@ describe('setupJava', () => { it.each([ [ - { version: '11', architecture: 'x86', packageType: 'jdk' }, - { version: actualJavaVersion, path: javaPath } + { version: '11', architecture: 'x86', packageType: 'jdk', checkLatest: false }, + { version: installedJavaVersion, path: javaPath } ], [ - { version: '11.1', architecture: 'x86', packageType: 'jdk' }, - { version: actualJavaVersion, path: javaPath } + { version: '11.0', architecture: 'x86', packageType: 'jdk', checkLatest: false }, + { version: installedJavaVersion, path: javaPath } ], [ - { version: '11.1.10', architecture: 'x86', packageType: 'jdk' }, - { version: actualJavaVersion, path: javaPath } + { version: '11.0.8', architecture: 'x86', packageType: 'jdk', checkLatest: false }, + { version: installedJavaVersion, path: javaPath } ] ])('should find java locally for %s', (input, expected) => { mockJavaBase = new EmptyJavaBase(input); expect(mockJavaBase.setupJava()).resolves.toEqual(expected); expect(spyGetToolcachePath).toHaveBeenCalled(); + expect(spyCoreInfo).toHaveBeenCalledWith(`Resolved Java ${expected.version} from tool-cache`); + expect(spyCoreInfo).toHaveBeenCalledWith(`Setting Java ${expected.version} as the default`); + expect(spyCoreInfo).not.toHaveBeenCalledWith( + 'Trying to resolve the latest version from remote' + ); + expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...'); }); it.each([ [ - { version: '11', architecture: 'x86', packageType: 'jre' }, - { path: `/toolcache/Java_Empty_jre/11.0.8/x86`, version: '11.0.8' } + { version: '11', architecture: 'x86', packageType: 'jre', checkLatest: false }, + { path: path.join('toolcache', 'Java_Empty_jre', '11.0.9', 'x86'), version: '11.0.9' } ], [ - { version: '11', architecture: 'x64', packageType: 'jdk' }, - { path: `/toolcache/Java_Empty_jdk/11.0.8/x64`, version: '11.0.8' } + { version: '11', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + { path: path.join('toolcache', 'Java_Empty_jdk', '11.0.9', 'x64'), version: '11.0.9' } ], [ - { version: '11', architecture: 'x64', packageType: 'jre' }, - { path: `/toolcache/Java_Empty_jre/11.0.8/x64`, version: '11.0.8' } + { version: '11', architecture: 'x64', packageType: 'jre', checkLatest: false }, + { path: path.join('toolcache', 'Java_Empty_jre', '11.0.9', 'x64'), version: '11.0.9' } ] ])('download java with configuration %s', async (input, expected) => { mockJavaBase = new EmptyJavaBase(input); @@ -214,11 +245,55 @@ describe('setupJava', () => { expect(spyCoreAddPath).toHaveBeenCalled(); expect(spyCoreExportVariable).toHaveBeenCalled(); expect(spyCoreSetOutput).toHaveBeenCalled(); + expect(spyCoreInfo).toHaveBeenCalledWith('Trying to resolve the latest version from remote'); + expect(spyCoreInfo).toHaveBeenCalledWith(`Resolved latest version as ${expected.version}`); + expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...'); + expect(spyCoreInfo).toHaveBeenCalledWith(`Java ${expected.version} was downloaded`); + expect(spyCoreInfo).toHaveBeenCalledWith(`Setting Java ${expected.version} as the default`); }); it.each([ - [{ version: '15', architecture: 'x86', packageType: 'jre' }], - [{ version: '11.0.7', architecture: 'x64', packageType: 'jre' }] + [ + { version: '11.0.9', architecture: 'x86', packageType: 'jdk', checkLatest: true }, + { version: '11.0.9', path: javaPathInstalled } + ] + ])('should check the latest java version for %s and resolve locally', async (input, expected) => { + mockJavaBase = new EmptyJavaBase(input); + mockJavaBase['findInToolcache'] = () => ({ version: '11.0.9', path: expected.path }); + await expect(mockJavaBase.setupJava()).resolves.toEqual(expected); + expect(spyCoreInfo).toHaveBeenCalledWith('Trying to resolve the latest version from remote'); + expect(spyCoreInfo).toHaveBeenCalledWith(`Resolved latest version as ${expected.version}`); + expect(spyCoreInfo).toHaveBeenCalledWith(`Resolved Java ${expected.version} from tool-cache`); + expect(spyCoreInfo).toHaveBeenCalledWith(`Setting Java ${expected.version} as the default`); + }); + + it.each([ + [ + { version: '11', architecture: 'x86', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPathInstalled } + ], + [ + { version: '11.0', architecture: 'x86', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPathInstalled } + ], + [ + { version: '11.0.x', architecture: 'x86', packageType: 'jdk', checkLatest: true }, + { version: actualJavaVersion, path: javaPathInstalled } + ] + ])('should check the latest java version for %s and download', async (input, expected) => { + mockJavaBase = new EmptyJavaBase(input); + await expect(mockJavaBase.setupJava()).resolves.toEqual(expected); + expect(spyGetToolcachePath).toHaveBeenCalled(); + expect(spyCoreInfo).toHaveBeenCalledWith('Trying to resolve the latest version from remote'); + expect(spyCoreInfo).toHaveBeenCalledWith(`Resolved latest version as ${actualJavaVersion}`); + expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...'); + expect(spyCoreInfo).toHaveBeenCalledWith(`Java ${actualJavaVersion} was downloaded`); + expect(spyCoreInfo).toHaveBeenCalledWith(`Setting Java ${expected.version} as the default`); + }); + + it.each([ + [{ version: '15', architecture: 'x86', packageType: 'jre', checkLatest: false }], + [{ version: '11.0.7', architecture: 'x64', packageType: 'jre', checkLatest: false }] ])('should throw an error for Available version not found for %s', async input => { mockJavaBase = new EmptyJavaBase(input); await expect(mockJavaBase.setupJava()).rejects.toThrowError('Available version not found'); @@ -249,3 +324,28 @@ describe('normalizeVersion', () => { ); }); }); + +describe('getToolcacheVersionName', () => { + const DummyJavaBase = JavaBase as any; + + it.each([ + [{ version: '11', stable: true }, '11'], + [{ version: '11.0.2', stable: true }, '11.0.2'], + [{ version: '11.0.2+4', stable: true }, '11.0.2-4'], + [{ version: '11.0.2+4.1.2563234', stable: true }, '11.0.2-4.1.2563234'], + [{ version: '11.0', stable: false }, '11.0-ea'], + [{ version: '11.0.3', stable: false }, '11.0.3-ea'], + [{ version: '11.0.3+4', stable: false }, '11.0.3-ea.4'], + [{ version: '11.0.3+4.2.256', stable: false }, '11.0.3-ea.4.2.256'] + ])('returns correct version name for %s', (input, expected) => { + const inputVersion = input.stable ? '11' : '11-ea'; + const mockJavaBase = new EmptyJavaBase({ + version: inputVersion, + packageType: 'jdk', + architecture: 'x64', + checkLatest: false + }); + const actual = mockJavaBase['getToolcacheVersionName'](input.version); + expect(actual).toBe(expected); + }); +}); diff --git a/__tests__/distributors/local-installer.test.ts b/__tests__/distributors/local-installer.test.ts index c3b085b..c161897 100644 --- a/__tests__/distributors/local-installer.test.ts +++ b/__tests__/distributors/local-installer.test.ts @@ -93,7 +93,12 @@ describe('setupJava', () => { }); it('java is resolved from toolcache, jdkfile is untouched', async () => { - const inputs = { version: actualJavaVersion, architecture: 'x86', packageType: 'jdk' }; + const inputs = { + version: actualJavaVersion, + architecture: 'x86', + packageType: 'jdk', + checkLatest: false + }; const jdkFile = 'not_existing_one'; const expected = { version: actualJavaVersion, @@ -110,7 +115,12 @@ describe('setupJava', () => { }); it("java is resolved from toolcache, jdkfile doesn't exist", async () => { - const inputs = { version: actualJavaVersion, architecture: 'x86', packageType: 'jdk' }; + const inputs = { + version: actualJavaVersion, + architecture: 'x86', + packageType: 'jdk', + checkLatest: false + }; const jdkFile = undefined; const expected = { version: actualJavaVersion, @@ -127,7 +137,12 @@ describe('setupJava', () => { }); it('java is unpacked from jdkfile', async () => { - const inputs = { version: '11.0.289', architecture: 'x86', packageType: 'jdk' }; + const inputs = { + version: '11.0.289', + architecture: 'x86', + packageType: 'jdk', + checkLatest: false + }; const jdkFile = expectedJdkFile; const expected = { version: '11.0.289', @@ -147,7 +162,12 @@ describe('setupJava', () => { }); it('jdk file is not found', async () => { - const inputs = { version: '11.0.289', architecture: 'x86', packageType: 'jdk' }; + const inputs = { + version: '11.0.289', + architecture: 'x86', + packageType: 'jdk', + checkLatest: false + }; const jdkFile = 'not_existing_one'; const expected = { javaVersion: '11.0.289', @@ -170,10 +190,22 @@ describe('setupJava', () => { }); it.each([ - [{ version: '8.0.289', architecture: 'x64', packageType: 'jdk' }, 'otherJdkFile'], - [{ version: '11.0.289', architecture: 'x64', packageType: 'jdk' }, 'otherJdkFile'], - [{ version: '12.0.289', architecture: 'x64', packageType: 'jdk' }, 'otherJdkFile'], - [{ version: '11.1.11', architecture: 'x64', packageType: 'jdk' }, 'not_existing_one'] + [ + { version: '8.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + 'otherJdkFile' + ], + [ + { version: '11.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + 'otherJdkFile' + ], + [ + { version: '12.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + 'otherJdkFile' + ], + [ + { version: '11.1.11', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + 'not_existing_one' + ] ])( `Throw an error if jdkfile has wrong path, inputs %s, jdkfile %s, real name ${expectedJdkFile}`, async (inputs, jdkFile) => { @@ -186,9 +218,15 @@ describe('setupJava', () => { ); it.each([ - [{ version: '8.0.289', architecture: 'x64', packageType: 'jdk' }, ''], - [{ version: '7.0.289', architecture: 'x64', packageType: 'jdk' }, undefined], - [{ version: '11.0.289', architecture: 'x64', packageType: 'jdk' }, undefined] + [{ version: '8.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, ''], + [ + { version: '7.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + undefined + ], + [ + { version: '11.0.289', architecture: 'x64', packageType: 'jdk', checkLatest: false }, + undefined + ] ])('Throw an error if jdkfile is not specified, inputs %s', async (inputs, jdkFile) => { mockJavaBase = new LocalDistribution(inputs, jdkFile); await expect(mockJavaBase.setupJava()).rejects.toThrowError("'jdkFile' is not specified"); diff --git a/__tests__/distributors/zulu-installer.test.ts b/__tests__/distributors/zulu-installer.test.ts index fe5958a..666cd90 100644 --- a/__tests__/distributors/zulu-installer.test.ts +++ b/__tests__/distributors/zulu-installer.test.ts @@ -30,27 +30,27 @@ describe('getAvailableVersions', () => { it.each([ [ - { version: '11', architecture: 'x86', packageType: 'jdk' }, + { version: '11', architecture: 'x86', packageType: 'jdk', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jdk&javafx=false&arch=x86&hw_bitness=32&release_status=ga' ], [ - { version: '11-ea', architecture: 'x86', packageType: 'jdk' }, + { version: '11-ea', architecture: 'x86', packageType: 'jdk', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jdk&javafx=false&arch=x86&hw_bitness=32&release_status=ea' ], [ - { version: '8', architecture: 'x64', packageType: 'jdk' }, + { version: '8', architecture: 'x64', packageType: 'jdk', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jdk&javafx=false&arch=x86&hw_bitness=64&release_status=ga' ], [ - { version: '8', architecture: 'x64', packageType: 'jre' }, + { version: '8', architecture: 'x64', packageType: 'jre', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jre&javafx=false&arch=x86&hw_bitness=64&release_status=ga' ], [ - { version: '8', architecture: 'x64', packageType: 'jdk+fx' }, + { version: '8', architecture: 'x64', packageType: 'jdk+fx', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jdk&javafx=true&arch=x86&hw_bitness=64&release_status=ga&features=fx' ], [ - { version: '8', architecture: 'x64', packageType: 'jre+fx' }, + { version: '8', architecture: 'x64', packageType: 'jre+fx', checkLatest: false }, '?os=macos&ext=tar.gz&bundle_type=jre&javafx=true&arch=x86&hw_bitness=64&release_status=ga&features=fx' ] ])('build correct url for %s -> %s', async (input, parsedUrl) => { @@ -68,7 +68,8 @@ describe('getAvailableVersions', () => { const distribution = new ZuluDistribution({ version: '11', architecture: 'x86', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); const availableVersions = await distribution['getAvailableVersions'](); expect(availableVersions).toHaveLength(manifestData.length); @@ -85,7 +86,8 @@ describe('getArchitectureOptions', () => { const distribution = new ZuluDistribution({ version: '11', architecture: input.architecture, - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); expect(distribution['getArchitectureOptions']()).toEqual(expected); }); @@ -108,7 +110,8 @@ describe('findPackageForDownload', () => { const distribution = new ZuluDistribution({ version: input, architecture: 'x86', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => manifestData; const result = await distribution['findPackageForDownload'](distribution['version']); @@ -119,7 +122,8 @@ describe('findPackageForDownload', () => { const distribution = new ZuluDistribution({ version: '', architecture: 'x86', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); distribution['getAvailableVersions'] = async () => manifestData; const result = await distribution['findPackageForDownload']('11.0.5'); @@ -132,7 +136,8 @@ describe('findPackageForDownload', () => { const distribution = new ZuluDistribution({ version: '18', architecture: 'x86', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); await expect( distribution['findPackageForDownload'](distribution['version']) @@ -151,7 +156,8 @@ describe('convertVersionToSemver', () => { const distribution = new ZuluDistribution({ version: '18', architecture: 'x86', - packageType: 'jdk' + packageType: 'jdk', + checkLatest: false }); const actual = distribution['convertVersionToSemver'](input); expect(actual).toBe(expected); diff --git a/action.yml b/action.yml index 406ad3a..5ba8228 100644 --- a/action.yml +++ b/action.yml @@ -20,6 +20,10 @@ inputs: jdkFile: description: 'Path to where the compressed JDK is located' required: false + check-latest: + description: 'Set this option if you want the action to check for the latest available version that satisfies the version spec' + required: false + default: false server-id: description: 'ID of the distributionManagement repository in the pom.xml file. Default is `github`' diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index ca595b1..be25b9b 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -6829,13 +6829,14 @@ function isUnixExecutable(stats) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; +exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_ARCHITECTURE = 'architecture'; exports.INPUT_JAVA_PACKAGE = 'java-package'; exports.INPUT_DISTRIBUTION = 'distribution'; exports.INPUT_JDK_FILE = 'jdkFile'; +exports.INPUT_CHECK_LATEST = 'check-latest'; exports.INPUT_SERVER_ID = 'server-id'; exports.INPUT_SERVER_USERNAME = 'server-username'; exports.INPUT_SERVER_PASSWORD = 'server-password'; diff --git a/dist/setup/index.js b/dist/setup/index.js index 28da6b3..fa160ea 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -3967,18 +3967,26 @@ class JavaBase { ({ version: this.version, stable: this.stable } = this.normalizeVersion(installerOptions.version)); this.architecture = installerOptions.architecture; this.packageType = installerOptions.packageType; + this.checkLatest = installerOptions.checkLatest; } setupJava() { return __awaiter(this, void 0, void 0, function* () { let foundJava = this.findInToolcache(); - if (foundJava) { + if (foundJava && !this.checkLatest) { core.info(`Resolved Java ${foundJava.version} from tool-cache`); } else { - core.info(`Java ${this.version} was not found in tool-cache. Trying to download...`); + core.info('Trying to resolve the latest version from remote'); const javaRelease = yield this.findPackageForDownload(this.version); - foundJava = yield this.downloadTool(javaRelease); - core.info(`Java ${foundJava.version} was downloaded`); + core.info(`Resolved latest version as ${javaRelease.version}`); + if ((foundJava === null || foundJava === void 0 ? void 0 : foundJava.version) === javaRelease.version) { + core.info(`Resolved Java ${foundJava.version} from tool-cache`); + } + else { + core.info('Trying to download...'); + foundJava = yield this.downloadTool(javaRelease); + core.info(`Java ${foundJava.version} was downloaded`); + } } // JDK folder may contain postfix "Contents/Home" on macOS const macOSPostfixPath = path_1.default.join(foundJava.path, constants_1.MACOS_JAVA_CONTENT_POSTFIX); @@ -3995,30 +4003,49 @@ class JavaBase { } getToolcacheVersionName(version) { if (!this.stable) { - const cleanVersion = semver_1.default.clean(version); - return `${cleanVersion}-ea`; + if (version.includes('+')) { + return version.replace('+', '-ea.'); + } + else { + return `${version}-ea`; + } } - return version; + // Kotlin and some Java dependencies don't work properly when Java path contains "+" sign + // so replace "/hostedtoolcache/Java/11.0.3+4/x64" to "/hostedtoolcache/Java/11.0.3-4/x64" when saves to cache + // related issue: https://github.com/actions/virtual-environments/issues/3014 + return version.replace('+', '-'); } findInToolcache() { // we can't use tc.find directly because firstly, we need to filter versions by stability flag // if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions const availableVersions = tc .findAllVersions(this.toolcacheFolderName, this.architecture) - .filter(item => item.endsWith('-ea') === !this.stable); + .map(item => { + return { + version: item + .replace('-ea.', '+') + .replace(/-ea$/, '') + // Kotlin and some Java dependencies don't work properly when Java path contains "+" sign + // so replace "/hostedtoolcache/Java/11.0.3-4/x64" to "/hostedtoolcache/Java/11.0.3+4/x64" when retrieves to cache + // related issue: https://github.com/actions/virtual-environments/issues/3014 + .replace('-', '+'), + path: util_1.getToolcachePath(this.toolcacheFolderName, item, this.architecture) || '', + stable: !item.includes('-ea') + }; + }) + .filter(item => item.stable === this.stable); const satisfiedVersions = availableVersions - .filter(item => util_1.isVersionSatisfies(this.version, item.replace(/-ea$/, ''))) - .sort(semver_1.default.rcompare); + .filter(item => util_1.isVersionSatisfies(this.version, item.version)) + .filter(item => item.path) + .sort((a, b) => { + return -semver_1.default.compareBuild(a.version, b.version); + }); if (!satisfiedVersions || satisfiedVersions.length === 0) { return null; } - const javaPath = util_1.getToolcachePath(this.toolcacheFolderName, satisfiedVersions[0], this.architecture); - if (!javaPath) { - return null; - } return { - version: util_1.getVersionFromToolcachePath(javaPath), - path: javaPath + version: satisfiedVersions[0].version, + path: satisfiedVersions[0].path }; } normalizeVersion(version) { @@ -11309,13 +11336,14 @@ exports.HTMLCollectionImpl = HTMLCollectionImpl; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; +exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0; exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; exports.INPUT_JAVA_VERSION = 'java-version'; exports.INPUT_ARCHITECTURE = 'architecture'; exports.INPUT_JAVA_PACKAGE = 'java-package'; exports.INPUT_DISTRIBUTION = 'distribution'; exports.INPUT_JDK_FILE = 'jdkFile'; +exports.INPUT_CHECK_LATEST = 'check-latest'; exports.INPUT_SERVER_ID = 'server-id'; exports.INPUT_SERVER_USERNAME = 'server-username'; exports.INPUT_SERVER_PASSWORD = 'server-password'; @@ -35646,6 +35674,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const auth = __importStar(__webpack_require__(331)); +const util_1 = __webpack_require__(322); const constants = __importStar(__webpack_require__(211)); const path = __importStar(__webpack_require__(622)); const distribution_factory_1 = __webpack_require__(24); @@ -35657,10 +35686,12 @@ function run() { const architecture = core.getInput(constants.INPUT_ARCHITECTURE); const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE); const jdkFile = core.getInput(constants.INPUT_JDK_FILE); + const checkLatest = util_1.getBooleanInput(constants.INPUT_CHECK_LATEST, false); const installerOptions = { architecture, packageType, - version + version, + checkLatest }; const distribution = distribution_factory_1.getJavaDistribution(distributionName, installerOptions, jdkFile); if (!distribution) { diff --git a/docs/adrs/0000-v2-setup-java.md b/docs/adrs/0000-v2-setup-java.md new file mode 100644 index 0000000..2d0c170 --- /dev/null +++ b/docs/adrs/0000-v2-setup-java.md @@ -0,0 +1,65 @@ +# 0. V2 setup-java + +Date: 2020-08-24 + +Status: Proposed + +# Context + +- The `v1` version of `setup-java` downloads and installs Zulu builds of OpenJDK. There is a huge ask from customers to offer AdoptOpenJDK/Adoptium builds of OpenJDK: https://github.com/actions/setup-java/issues/13 + +- Zulu and AdoptOpenJDK aren't the only distributions of Java though. Other providers include Oracle OpenJDK or Amazon Corretto and ideally it would be nice to support downloading Java from all providers. + +- GitHub Actions virtual environments install and default to AdoptOpenJDK builds of OpenJDK. `setup-java` should give users an option to download and use other distributions that may not be be installed + +# Decision + +## New input + +A new required input parameter (titled `distribution`) will be added to `setup-java` that will allow users to specify the distribution that they would like to download + +```yaml + with: + java-version: '11' + distribution: adoptium +``` + +## Default Behavior + +There will be no default distribution that we pick for the user. Users will have to specify a distribution in their YAML or else the action will fail. + +Requiring a default version will break users that are pinned to `@main` as they will have no `distribution` specified in their YAML. Telemetry indicates that only a small percentage of users would be effected though. Users pinned to `v1` will be uneffected. This change is meant to not be backward compatible and it is acceptable to change the default behavior because a new major version will be released alongside these changes. + +## Extensibility & Documentation + +`setup-java` should be structured in such a way that will allow the open source community to easily add support for extra distributions. + +Existing code will be restructured so that distribution specific code will be easily separated. Currently the core download logic is in a single file, `installer.ts`. This file will be split up and abstracted out so that there will be no vendor specified logic. Each distribution will have it's own files under `src/distributions` that will contain the core setup logic for a specific distribution. + +```yaml + ∟ src/ + installer.ts # core installer logic + distributions/ + ∟ adoptium/ # adoptium (formerly AdoptOpenJDK) specific logic + ∟ zulu/ # zulu specific logic + ∟ # future distributions will be added here +``` + +The contribution doc (`CONTRIBUTING.md`) will describe how a new distribution should be added and how everything should be structured. + +## v2-preview + +There will be a `v2-preview` branch that will be created for development and testing. Any changes will first be merged into `v2-preview` branch. After a period of testing & verification, the `v2-preview` branch will be merged into the `main` branch and a `v2` tag will be created. Any [GitHub public documentation](https://docs.github.com/en/actions/language-and-framework-guides/github-actions-for-java) and [starter workflows](https://github.com/actions/starter-workflows) that mention `setup-java` will then be updated to use `v2` instead of `v1`. + +## Goals & Anti-Goals + +The main focus of the `v2` version of `setup-java` will be to add support for adoptium builds of openJDK in addition to Zulu builds of openJDK. In addition, extensibility will be a priority so that other distributions can be added in the future. + +The `setup-java` action has some logic that creates a `settings.xml` file so that it is easier to publish packages. Any improvements or modifications to this logic or anything Gradle/Maven specific will be avoided during the development of the `v2-preview`. + +# Consequences + +- Users will have more flexibility and the freedom to choose a specific distribution that they would like (AdoptOpenJDK builds of OpenJDK in addition or Zulu builds of OpenJDK) +- `setup-java` will be structured in such a way that will allow for more distributions to be easily added in the future +- A large subset of users pin to `@main` or `@master` instead of to a specific version (`v1`). By introducing a breaking change that now requires a distribution to be specified, many users will have their workflows fail until they go and update their yaml +- Higher maintenance and support burden moving forward \ No newline at end of file diff --git a/docs/adrs/README.md b/docs/adrs/README.md new file mode 100644 index 0000000..f23a8f7 --- /dev/null +++ b/docs/adrs/README.md @@ -0,0 +1,19 @@ +# ADRs + +ADR, short for "Architecture Decision Record" is a way of capturing important architectural decisions, along with their context and consequences. + +This folder includes ADRs for the setup-java action. ADRs are proposed in the form of a pull request, and they commonly follow this format: + +* **Title**: short present tense imperative phrase, less than 50 characters, like a git commit message. + +* **Status**: proposed, accepted, rejected, deprecated, superseded, etc. + +* **Context**: what is the issue that we're seeing that is motivating this decision or change. + +* **Decision**: what is the change that we're actually proposing or doing. + +* **Consequences**: what becomes easier or more difficult to do because of this change. + +--- + +- More information about ADRs can be found [here](https://github.com/joelparkerhenderson/architecture_decision_record). \ No newline at end of file diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index c78ca68..214005e 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -9,6 +9,7 @@ - [Testing against different platforms](#Testing-against-different-platforms) - [Publishing using Apache Maven](#Publishing-using-Apache-Maven) - [Publishing using Gradle](#Publishing-using-Gradle) +- [Hosted Tool Cache](#Hosted-Tool-Cache) See [action.yml](../action.yml) for more details on task inputs. @@ -291,3 +292,10 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} ``` + +## Hosted Tool Cache +GitHub Hosted Runners have a tool cache that comes with some Java versions pre-installed. This tool cache helps speed up runs and tool setup by not requiring any new downloads. There is an environment variable called `RUNNER_TOOL_CACHE` on each runner that describes the location of this tools cache and this is where you can find the pre-installed versions of Java. `setup-java` works by taking a specific version of Java in this tool cache and adding it to PATH if the version, architecture and distribution match. + +Currently, LTS versions of Adopt OpenJDK (`adopt`) are cached on the GitHub Hosted Runners. + +The tools cache gets updated on a weekly basis. For information regarding locally cached versions of Java on GitHub hosted runners, check out [GitHub Actions Virtual Environments](https://github.com/actions/virtual-environments). diff --git a/src/constants.ts b/src/constants.ts index 99c8351..63d748a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,6 +4,7 @@ export const INPUT_ARCHITECTURE = 'architecture'; export const INPUT_JAVA_PACKAGE = 'java-package'; export const INPUT_DISTRIBUTION = 'distribution'; export const INPUT_JDK_FILE = 'jdkFile'; +export const INPUT_CHECK_LATEST = 'check-latest'; export const INPUT_SERVER_ID = 'server-id'; export const INPUT_SERVER_USERNAME = 'server-username'; export const INPUT_SERVER_PASSWORD = 'server-password'; diff --git a/src/distributions/base-installer.ts b/src/distributions/base-installer.ts index 6f1312c..7db3494 100644 --- a/src/distributions/base-installer.ts +++ b/src/distributions/base-installer.ts @@ -14,6 +14,7 @@ export abstract class JavaBase { protected architecture: string; protected packageType: string; protected stable: boolean; + protected checkLatest: boolean; constructor(protected distribution: string, installerOptions: JavaInstallerOptions) { this.http = new httpm.HttpClient('actions/setup-java', undefined, { @@ -26,6 +27,7 @@ export abstract class JavaBase { )); this.architecture = installerOptions.architecture; this.packageType = installerOptions.packageType; + this.checkLatest = installerOptions.checkLatest; } protected abstract downloadTool(javaRelease: JavaDownloadRelease): Promise; @@ -33,13 +35,19 @@ export abstract class JavaBase { public async setupJava(): Promise { let foundJava = this.findInToolcache(); - if (foundJava) { + if (foundJava && !this.checkLatest) { core.info(`Resolved Java ${foundJava.version} from tool-cache`); } else { - core.info(`Java ${this.version} was not found in tool-cache. Trying to download...`); + core.info('Trying to resolve the latest version from remote'); const javaRelease = await this.findPackageForDownload(this.version); - foundJava = await this.downloadTool(javaRelease); - core.info(`Java ${foundJava.version} was downloaded`); + core.info(`Resolved latest version as ${javaRelease.version}`); + if (foundJava?.version === javaRelease.version) { + core.info(`Resolved Java ${foundJava.version} from tool-cache`); + } else { + core.info('Trying to download...'); + foundJava = await this.downloadTool(javaRelease); + core.info(`Java ${foundJava.version} was downloaded`); + } } // JDK folder may contain postfix "Contents/Home" on macOS @@ -60,10 +68,17 @@ export abstract class JavaBase { protected getToolcacheVersionName(version: string): string { if (!this.stable) { - const cleanVersion = semver.clean(version); - return `${cleanVersion}-ea`; + if (version.includes('+')) { + return version.replace('+', '-ea.'); + } else { + return `${version}-ea`; + } } - return version; + + // Kotlin and some Java dependencies don't work properly when Java path contains "+" sign + // so replace "/hostedtoolcache/Java/11.0.3+4/x64" to "/hostedtoolcache/Java/11.0.3-4/x64" when saves to cache + // related issue: https://github.com/actions/virtual-environments/issues/3014 + return version.replace('+', '-'); } protected findInToolcache(): JavaInstallerResults | null { @@ -71,27 +86,34 @@ export abstract class JavaBase { // if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions const availableVersions = tc .findAllVersions(this.toolcacheFolderName, this.architecture) - .filter(item => item.endsWith('-ea') === !this.stable); + .map(item => { + return { + version: item + .replace('-ea.', '+') + .replace(/-ea$/, '') + // Kotlin and some Java dependencies don't work properly when Java path contains "+" sign + // so replace "/hostedtoolcache/Java/11.0.3-4/x64" to "/hostedtoolcache/Java/11.0.3+4/x64" when retrieves to cache + // related issue: https://github.com/actions/virtual-environments/issues/3014 + .replace('-', '+'), + path: getToolcachePath(this.toolcacheFolderName, item, this.architecture) || '', + stable: !item.includes('-ea') + }; + }) + .filter(item => item.stable === this.stable); const satisfiedVersions = availableVersions - .filter(item => isVersionSatisfies(this.version, item.replace(/-ea$/, ''))) - .sort(semver.rcompare); + .filter(item => isVersionSatisfies(this.version, item.version)) + .filter(item => item.path) + .sort((a, b) => { + return -semver.compareBuild(a.version, b.version); + }); if (!satisfiedVersions || satisfiedVersions.length === 0) { return null; } - const javaPath = getToolcachePath( - this.toolcacheFolderName, - satisfiedVersions[0], - this.architecture - ); - if (!javaPath) { - return null; - } - return { - version: getVersionFromToolcachePath(javaPath), - path: javaPath + version: satisfiedVersions[0].version, + path: satisfiedVersions[0].path }; } diff --git a/src/distributions/base-models.ts b/src/distributions/base-models.ts index d7a7af7..82344d5 100644 --- a/src/distributions/base-models.ts +++ b/src/distributions/base-models.ts @@ -2,6 +2,7 @@ export interface JavaInstallerOptions { version: string; architecture: string; packageType: string; + checkLatest: boolean; } export interface JavaInstallerResults { diff --git a/src/setup-java.ts b/src/setup-java.ts index 1641b78..92a8619 100644 --- a/src/setup-java.ts +++ b/src/setup-java.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import * as auth from './auth'; - +import { getBooleanInput } from './util'; import * as constants from './constants'; import * as path from 'path'; import { getJavaDistribution } from './distributions/distribution-factory'; @@ -13,11 +13,13 @@ async function run() { const architecture = core.getInput(constants.INPUT_ARCHITECTURE); const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE); const jdkFile = core.getInput(constants.INPUT_JDK_FILE); + const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false); const installerOptions: JavaInstallerOptions = { architecture, packageType, - version + version, + checkLatest }; const distribution = getJavaDistribution(distributionName, installerOptions, jdkFile);