diff --git a/README.md b/README.md index 05b42909..7a663f49 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,25 @@ steps: - uses: actions/setup-java@v1 with: java-version: '4.0.0' - architecture: x64 jdkFile: # Optional - jdkFile to install java from. Useful for versions not found on Zulu Community CDN - run: java -cp java HelloWorldApp ``` +## Maven/Gradle Versions +```yaml +steps: +- uses: actions/checkout@v1 +- uses: actions/setup-java@v1 + with: + java-version: '9.0.4' + maven-version: '3.6.2' + maven-mirror: # Optional - defaults to https://archive.apache.org/dist/maven/maven-3/ + maven-file: # Optional - to install maven from. + gradle-version: '5.6.2' + gradle-file: # Optional - to install gradle from. +- run: java -cp java HelloWorldApp +``` + ## Matrix Testing ```yaml jobs: @@ -87,7 +101,7 @@ jobs: server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy - name: Publish to Apache Maven Central - run: mvn deploy + run: mvn deploy env: MAVEN_USERNAME: maven_username123 MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} @@ -117,7 +131,7 @@ The two `settings.xml` files created from the above example look like the follow ``` -***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.*** +***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.*** See the help docs on [Publishing a Package](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-apache-maven-for-use-with-github-packages#publishing-a-package) for more information on the `pom.xml` file. @@ -144,7 +158,7 @@ jobs: PASSWORD: ${{ secrets.GITHUB_TOKEN }} ``` -***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.*** +***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.*** See the help docs on [Publishing a Package with Gradle](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-gradle-for-use-with-github-packages#example-using-gradle-groovy-for-a-single-package-in-a-repository) for more information on the `build.gradle` configuration file. diff --git a/__tests__/gradle-installer.test.ts b/__tests__/gradle-installer.test.ts new file mode 100644 index 00000000..b06555cb --- /dev/null +++ b/__tests__/gradle-installer.test.ts @@ -0,0 +1,127 @@ +import io = require('@actions/io'); +import fs = require('fs'); +import path = require('path'); +import child_process = require('child_process'); + +const toolDir = path.join(__dirname, 'runnerg', 'tools'); +const tempDir = path.join(__dirname, 'runnerg', 'temp'); +const gradleDir = path.join(__dirname, 'runnerg', 'gradle'); + +process.env['RUNNER_TOOL_CACHE'] = toolDir; +process.env['RUNNER_TEMP'] = tempDir; +import * as installer from '../src/gradle-installer'; + +let gradleFilePath = ''; +let gradleUrl = ''; +if (process.platform === 'win32') { + gradleFilePath = path.join(gradleDir, 'gradle_win.zip'); + gradleUrl = 'https://services.gradle.org/distributions/gradle-6.0.1-bin.zip'; +} else if (process.platform === 'darwin') { + gradleFilePath = path.join(gradleDir, 'gradle_mac.zip'); + gradleUrl = 'https://services.gradle.org/distributions/gradle-6.0.1-bin.zip'; +} else { + gradleFilePath = path.join(gradleDir, 'gradle_linux.zip'); + gradleUrl = 'https://services.gradle.org/distributions/gradle-6.0.1-bin.zip'; +} + +describe('gradle installer tests', () => { + beforeAll(async () => { + await io.rmRF(toolDir); + await io.rmRF(tempDir); + await io.rmRF(gradleDir); + if (!fs.existsSync(`${gradleFilePath}.complete`)) { + // Download gradle + await io.mkdirP(gradleDir); + + console.log('Downloading gradle'); + child_process.execSync(`curl -L "${gradleUrl}" > "${gradleFilePath}"`); + // Write complete file so we know it was successful + fs.writeFileSync(`${gradleFilePath}.complete`, 'content'); + } + }, 300000); + + afterAll(async () => { + try { + await io.rmRF(toolDir); + await io.rmRF(tempDir); + await io.rmRF(gradleDir); + } catch { + console.log('Failed to remove test directories'); + } + }, 100000); + + it('Installs version of Gradle from gradleFile if no matching version is installed', async () => { + await installer.getGradle('6.0.1', gradleFilePath); + const gradleDir = path.join(toolDir, 'gradle', '6.0.1', 'x64'); + + expect(fs.existsSync(`${gradleDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(gradleDir, 'bin'))).toBe(true); + }, 100000); + + it('Throws if invalid directory to gradle', async () => { + let thrown = false; + try { + await installer.getGradle('1000', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + }); + + it('Downloads gradle if no file given', async () => { + await installer.getGradle('5.6.3', ''); + const gradleDir = path.join(toolDir, 'gradle', '5.6.3', 'x64'); + + expect(fs.existsSync(`${gradleDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(gradleDir, 'bin'))).toBe(true); + }, 100000); + + it('Downloads gradle with 1.x syntax', async () => { + await installer.getGradle('4.10', ''); + const gradleDir = path.join(toolDir, 'gradle', '4.10.3', 'x64'); + + expect(fs.existsSync(`${gradleDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(gradleDir, 'bin'))).toBe(true); + }, 100000); + + it('Downloads gradle with normal semver syntax', async () => { + await installer.getGradle('4.8.x', ''); + const gradleDir = path.join(toolDir, 'gradle', '4.8.1', 'x64'); + + expect(fs.existsSync(`${gradleDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(gradleDir, 'bin'))).toBe(true); + }, 100000); + + it('Throws if invalid directory to gradle', async () => { + let thrown = false; + try { + await installer.getGradle('1000', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + }); + + it('Uses version of gradle installed in cache', async () => { + const gradleDir: string = path.join(toolDir, 'gradle', '250.0.0', 'x64'); + await io.mkdirP(gradleDir); + fs.writeFileSync(`${gradleDir}.complete`, 'hello'); + // This will throw if it doesn't find it in the cache (because no such version exists) + await installer.getGradle('250', 'path shouldnt matter, found in cache'); + return; + }); + + it('Doesnt use version of gradle that was only partially installed in cache', async () => { + const gradleDir: string = path.join(toolDir, 'gradle', '251.0.0', 'x64'); + await io.mkdirP(gradleDir); + let thrown = false; + try { + // This will throw if it doesn't find it in the cache (because no such version exists) + await installer.getGradle('251', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + return; + }); +}); diff --git a/__tests__/maven-installer.test.ts b/__tests__/maven-installer.test.ts new file mode 100644 index 00000000..ed2ed438 --- /dev/null +++ b/__tests__/maven-installer.test.ts @@ -0,0 +1,134 @@ +import io = require('@actions/io'); +import fs = require('fs'); +import path = require('path'); +import child_process = require('child_process'); + +const toolDir = path.join(__dirname, 'runnerm', 'tools'); +const tempDir = path.join(__dirname, 'runnerm', 'temp'); +const mavenDir = path.join(__dirname, 'runnerm', 'maven'); + +process.env['RUNNER_TOOL_CACHE'] = toolDir; +process.env['RUNNER_TEMP'] = tempDir; +import * as installer from '../src/maven-installer'; + +let mavenFilePath = ''; +let mavenUrl = ''; +if (process.platform === 'win32') { + mavenFilePath = path.join(mavenDir, 'maven_win.zip'); + mavenUrl = + 'https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip'; +} else if (process.platform === 'darwin') { + mavenFilePath = path.join(mavenDir, 'maven_mac.tar.gz'); + mavenUrl = + 'https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz'; +} else { + mavenFilePath = path.join(mavenDir, 'maven_linux.tar.gz'); + mavenUrl = + 'https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz'; +} + +describe('maven installer tests', () => { + beforeAll(async () => { + await io.rmRF(toolDir); + await io.rmRF(tempDir); + await io.rmRF(mavenDir); + if (!fs.existsSync(`${mavenFilePath}.complete`)) { + // Download maven + await io.mkdirP(mavenDir); + + console.log('Downloading maven'); + child_process.execSync(`curl "${mavenUrl}" > "${mavenFilePath}"`); + // Write complete file so we know it was successful + fs.writeFileSync(`${mavenFilePath}.complete`, 'content'); + } + }, 300000); + + afterAll(async () => { + try { + await io.rmRF(toolDir); + await io.rmRF(tempDir); + await io.rmRF(mavenDir); + } catch { + console.log('Failed to remove test directories'); + } + }, 100000); + + it('Installs version of Maven from maven-file if no matching version is installed', async () => { + await installer.getMaven( + '3.6.3', + mavenFilePath, + 'https://archive.apache.org/dist/maven/maven-3/' + ); + const mavenDir = path.join(toolDir, 'maven', '3.6.3', 'x64'); + + expect(fs.existsSync(`${mavenDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(mavenDir, 'bin'))).toBe(true); + }, 100000); + + it('Throws if invalid directory to maven', async () => { + let thrown = false; + try { + await installer.getMaven('1000', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + }); + + it('Downloads maven if no file given', async () => { + await installer.getMaven('3.6.2', ''); + const mavenDir = path.join(toolDir, 'maven', '3.6.2', 'x64'); + + expect(fs.existsSync(`${mavenDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(mavenDir, 'bin'))).toBe(true); + }, 100000); + + it('Downloads maven with 1.x syntax', async () => { + await installer.getMaven('3.1', ''); + const mavenDir = path.join(toolDir, 'maven', '3.1.1', 'x64'); + + expect(fs.existsSync(`${mavenDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(mavenDir, 'bin'))).toBe(true); + }, 100000); + + it('Downloads maven with normal semver syntax', async () => { + await installer.getMaven('3.5.x', ''); + const mavenDir = path.join(toolDir, 'maven', '3.5.4', 'x64'); + + expect(fs.existsSync(`${mavenDir}.complete`)).toBe(true); + expect(fs.existsSync(path.join(mavenDir, 'bin'))).toBe(true); + }, 100000); + + it('Throws if invalid directory to maven', async () => { + let thrown = false; + try { + await installer.getMaven('1000', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + }); + + it('Uses version of Maven installed in cache', async () => { + const mavenDir: string = path.join(toolDir, 'maven', '250.0.0', 'x64'); + await io.mkdirP(mavenDir); + fs.writeFileSync(`${mavenDir}.complete`, 'hello'); + // This will throw if it doesn't find it in the cache (because no such version exists) + await installer.getMaven('250', 'path shouldnt matter, found in cache'); + return; + }); + + it('Doesnt use version of Maven that was only partially installed in cache', async () => { + const mavenDir: string = path.join(toolDir, 'maven', '251.0.0', 'x64'); + await io.mkdirP(mavenDir); + let thrown = false; + try { + // This will throw if it doesn't find it in the cache (because no such version exists) + await installer.getMaven('251', 'bad path'); + } catch { + thrown = true; + } + expect(thrown).toBe(true); + return; + }); +}); diff --git a/action.yml b/action.yml index 6337613f..1995940a 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,26 @@ inputs: settings-path: description: 'Path to where the settings.xml file will be written. Default is ~/.m2.' required: false + maven-version: + description: 'The Maven version to make available on the path. Takes a whole + or semver Maven version, or 3.x syntax (e.g. 3.6 => Maven 3.x)' + required: false + maven-file: + description: 'Path to where the compressed Maven is located. The path could + be in your source repository or a local path on the agent.' + required: false + maven-mirror: + description: 'Uri hosting Maven3 mirror packages.' + required: false + default: 'https://archive.apache.org/dist/maven/maven-3/' + gradle-version: + description: 'The Gradle version to make available on the path. Takes a whole + or semver Gradle version, or 6.x syntax (e.g. 6.0 => Gradle 6.x)' + required: false + gradle-file: + description: 'Path to where the compressed Gradle is located. The path could + be in your source repository or a local path on the agent.' + required: false runs: using: 'node12' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 9e0ab8c4..1db34ab1 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/package-lock.json b/package-lock.json index d0a41b10..0564ca19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,31 +5,48 @@ "requires": true, "dependencies": { "@actions/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.0.0.tgz", - "integrity": "sha512-aMIlkx96XH4E/2YZtEOeyrYQfhlas9jIRkfGPqMwXD095Rdkzo4lB6ZmbxPQSzD+e1M+Xsm98ZhuSMYGv/AlqA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.1.tgz", + "integrity": "sha512-xD+CQd9p4lU7ZfRqmUcbJpqR+Ss51rJRVeXMyOLrZQImN9/8Sy/BEUBnHO/UKD3z03R686PVTLfEPmkropGuLw==" }, "@actions/exec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.0.tgz", "integrity": "sha512-nquH0+XKng+Ll7rZfCojN7NWSbnGh+ltwUJhzfbLkmOJgxocGX2/yXcZLMyT9fa7+tByEow/NSTrBExNlEj9fw==" }, + "@actions/http-client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.1.tgz", + "integrity": "sha512-vy5DhqTJ1gtEkpRrD/6BHhUlkeyccrOX0BT9KmtO5TWxe5KSSwVHFE+J15Z0dG+tJwZJ/nHC4slUIyqpkahoMg==" + }, "@actions/io": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.0.tgz", - "integrity": "sha512-ezrJSRdqtXtdx1WXlfYL85+40F7gB39jCK9P0jZVODW3W6xUYmu6ZOEc/UmmElUwhRyDRm1R4yNZu1Joq2kuQg==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", + "integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" }, "@actions/tool-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.0.0.tgz", - "integrity": "sha512-l3zT0IfDfi5Ik5aMpnXqGHGATxN8xa9ls4ue+X/CBXpPhRMRZS4vcuh5Q9T98WAGbkysRCfhpbksTPHIcKnNwQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.0.tgz", + "integrity": "sha512-pbv32I89niDShw1YTDo0OFQmWPkZPElE7e3So1jfEzjIyzGRfYIzshwOVhemJZLcDtzo3kxO3GFDAmuVvub/6w==", "requires": { - "@actions/core": "^1.0.0", + "@actions/core": "^1.2.0", "@actions/exec": "^1.0.0", - "@actions/io": "^1.0.0", + "@actions/http-client": "^1.0.1", + "@actions/io": "^1.0.1", "semver": "^6.1.0", - "typed-rest-client": "^1.4.0", "uuid": "^3.3.2" + }, + "dependencies": { + "@actions/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.1.tgz", + "integrity": "sha512-xD+CQd9p4lU7ZfRqmUcbJpqR+Ss51rJRVeXMyOLrZQImN9/8Sy/BEUBnHO/UKD3z03R686PVTLfEPmkropGuLw==" + }, + "@actions/io": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", + "integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" + } } }, "@babel/code-frame": { @@ -1711,7 +1728,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1732,12 +1750,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1752,17 +1772,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1879,7 +1902,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1891,6 +1915,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1905,6 +1930,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1912,12 +1938,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1936,6 +1964,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2016,7 +2045,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2028,6 +2058,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2113,7 +2144,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2149,6 +2181,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2168,6 +2201,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2211,12 +2245,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/package.json b/package.json index 00922b49..28aa2624 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,10 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@actions/core": "^1.0.0", + "@actions/core": "^1.2.1", "@actions/exec": "^1.0.0", - "@actions/io": "^1.0.0", - "@actions/tool-cache": "^1.0.0", + "@actions/io": "^1.0.2", + "@actions/tool-cache": "^1.3.0", "semver": "^6.1.1", "typed-rest-client": "1.5.0" }, diff --git a/src/gradle-installer.ts b/src/gradle-installer.ts new file mode 100644 index 00000000..e6739898 --- /dev/null +++ b/src/gradle-installer.ts @@ -0,0 +1,204 @@ +let tempDirectory = process.env['RUNNER_TEMP'] || ''; + +import * as core from '@actions/core'; +import * as io from '@actions/io'; +import * as tc from '@actions/tool-cache'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as semver from 'semver'; +import * as httpm from 'typed-rest-client/HttpClient'; + +const IS_WINDOWS = process.platform === 'win32'; + +if (!tempDirectory) { + let baseLocation; + if (IS_WINDOWS) { + // On windows use the USERPROFILE env variable + baseLocation = process.env['USERPROFILE'] || 'C:\\'; + } else { + if (process.platform === 'darwin') { + baseLocation = '/Users'; + } else { + baseLocation = '/home'; + } + } + tempDirectory = path.join(baseLocation, 'actions', 'temp'); +} + +export async function getGradle( + version: string, + gradleFile: string, + gradleMirror: string = 'https://services.gradle.org/distributions/' +): Promise { + const toolName = 'gradle'; + let toolPath = tc.find(toolName, version); + + if (toolPath) { + core.debug(`Tool found in cache ${toolPath}`); + } else { + let compressedFileExtension = ''; + if (!gradleFile) { + core.debug('Downloading Gradle from gradle.org'); + let http: httpm.HttpClient = new httpm.HttpClient('spring-build-action'); + let contents = await (await http.get(gradleMirror)).readBody(); + let refs: string[] = []; + const regex = /gradle-([\d\.]+)-bin\.zip<\/span>/g; + let match = regex.exec(contents); + while (match != null) { + refs.push(match[1]); + match = regex.exec(contents); + } + core.debug(`Found refs ${refs}`); + + const downloadInfo = getDownloadInfo(refs, version, gradleMirror); + + gradleFile = await tc.downloadTool(downloadInfo.url); + version = downloadInfo.version; + compressedFileExtension = '.zip'; + } else { + core.debug('Retrieving Gradle from local path'); + } + compressedFileExtension = + compressedFileExtension || getFileEnding(gradleFile); + let tempDir: string = path.join( + tempDirectory, + 'temp_' + Math.floor(Math.random() * 2000000000) + ); + const gradleDir = await unzipGradleDownload( + gradleFile, + compressedFileExtension, + tempDir + ); + core.debug(`gradle extracted to ${gradleDir}`); + toolPath = await tc.cacheDir( + gradleDir, + toolName, + getCacheVersionString(version) + ); + } + + core.exportVariable('GRADLE_HOME', toolPath); + core.addPath(path.join(toolPath, 'bin')); +} + +function getCacheVersionString(version: string) { + const versionArray = version.split('.'); + const major = versionArray[0]; + const minor = versionArray.length > 1 ? versionArray[1] : '0'; + const patch = versionArray.length > 2 ? versionArray[2] : '0'; + return `${major}.${minor}.${patch}`; +} + +function getFileEnding(file: string): string { + let fileEnding = ''; + + if (file.endsWith('.zip')) { + fileEnding = '.zip'; + } else { + throw new Error(`${file} has an unsupported file extension`); + } + + return fileEnding; +} + +async function extractFiles( + file: string, + fileEnding: string, + destinationFolder: string +): Promise { + const stats = fs.statSync(file); + if (!stats) { + throw new Error(`Failed to extract ${file} - it doesn't exist`); + } else if (stats.isDirectory()) { + throw new Error(`Failed to extract ${file} - it is a directory`); + } + + if ('.zip' === fileEnding) { + await tc.extractZip(file, destinationFolder); + } else { + throw new Error(`Failed to extract ${file} - only .zip supported`); + } +} + +async function unzipGradleDownload( + repoRoot: string, + fileEnding: string, + destinationFolder: string +): Promise { + // Create the destination folder if it doesn't exist + await io.mkdirP(destinationFolder); + + const gradleFile = path.normalize(repoRoot); + const stats = fs.statSync(gradleFile); + if (stats.isFile()) { + await extractFiles(path.resolve(gradleFile), fileEnding, destinationFolder); + const gradleDirectory = path.join( + destinationFolder, + fs.readdirSync(destinationFolder)[0] + ); + return gradleDirectory; + } else { + throw new Error(`Gradle argument ${gradleFile} is not a file`); + } +} + +function getDownloadInfo( + refs: string[], + version: string, + gradleMirror: string +): {version: string; url: string} { + version = normalizeVersion(version); + const extension = '.zip'; + + // Maps version to url + let versionMap = new Map(); + + // Filter by platform + refs.forEach(ref => { + if (semver.satisfies(ref, version)) { + core.debug(`VersionMap add ${ref} ${version}`); + versionMap.set(ref, `${gradleMirror}gradle-${ref}-bin${extension}`); + } + }); + + // Choose the most recent satisfying version + let curVersion = '0.0.0'; + let curUrl = ''; + for (const entry of versionMap.entries()) { + const entryVersion = entry[0]; + const entryUrl = entry[1]; + core.debug(`VersionMap Entry ${entryVersion} ${entryUrl}`); + if (semver.gt(entryVersion, curVersion)) { + core.debug(`VersionMap semver gt ${entryVersion} ${entryUrl}`); + curUrl = entryUrl; + curVersion = entryVersion; + } + } + + if (curUrl == '') { + throw new Error( + `No valid download found for version ${version}. Check ${gradleMirror} for a list of valid versions or download your own gradle file and add the gradleFile argument` + ); + } + + return {version: curVersion, url: curUrl}; +} + +function normalizeVersion(version: string): string { + if (version.slice(0, 2) === '1.') { + // Trim leading 1. for versions like 1.8 + version = version.slice(2); + if (!version) { + throw new Error('1. is not a valid version'); + } + } + + if (version.split('.').length < 3) { + // Add trailing .x if it is missing + if (version[version.length - 1] != 'x') { + version = version + '.x'; + } + } + + return version; +} diff --git a/src/installer.ts b/src/installer.ts index ab4f466b..9b97c0ad 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -118,12 +118,12 @@ async function extractFiles( } if ('.tar' === fileEnding || '.tar.gz' === fileEnding) { - await tc.extractTar(file, destinationFolder); + await tc.extractTar(path.resolve(file), destinationFolder); } else if ('.zip' === fileEnding) { - await tc.extractZip(file, destinationFolder); + await tc.extractZip(path.resolve(file), destinationFolder); } else { // fall through and use sevenZip - await tc.extract7z(file, destinationFolder); + await tc.extract7z(path.resolve(file), destinationFolder); } } diff --git a/src/maven-installer.ts b/src/maven-installer.ts new file mode 100644 index 00000000..b0a60fac --- /dev/null +++ b/src/maven-installer.ts @@ -0,0 +1,218 @@ +let tempDirectory = process.env['RUNNER_TEMP'] || ''; + +import * as core from '@actions/core'; +import * as io from '@actions/io'; +import * as tc from '@actions/tool-cache'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as semver from 'semver'; +import * as httpm from 'typed-rest-client/HttpClient'; + +const IS_WINDOWS = process.platform === 'win32'; + +if (!tempDirectory) { + let baseLocation; + if (IS_WINDOWS) { + // On windows use the USERPROFILE env variable + baseLocation = process.env['USERPROFILE'] || 'C:\\'; + } else { + if (process.platform === 'darwin') { + baseLocation = '/Users'; + } else { + baseLocation = '/home'; + } + } + tempDirectory = path.join(baseLocation, 'actions', 'temp'); +} + +export async function getMaven( + version: string, + mavenFile: string, + mavenMirror: string = 'https://archive.apache.org/dist/maven/maven-3/' +): Promise { + const toolName = 'maven'; + let toolPath = tc.find(toolName, version); + + if (toolPath) { + core.debug(`Tool found in cache ${toolPath}`); + } else { + let compressedFileExtension = ''; + if (!mavenFile) { + core.debug('Downloading Maven from Apache Mirror'); + let http: httpm.HttpClient = new httpm.HttpClient('spring-build-action'); + let contents = await (await http.get(mavenMirror)).readBody(); + let refs: string[] = []; + const regex = /([\d\.]+)\/<\/a>/g; + let match = regex.exec(contents); + while (match != null) { + refs.push(match[1]); + match = regex.exec(contents); + } + core.debug(`Found refs ${refs}`); + + const downloadInfo = getDownloadInfo(refs, version, mavenMirror); + + mavenFile = await tc.downloadTool(downloadInfo.url); + version = downloadInfo.version; + compressedFileExtension = IS_WINDOWS ? '.zip' : '.tar.gz'; + } else { + core.debug('Retrieving Maven from local path'); + } + compressedFileExtension = + compressedFileExtension || getFileEnding(mavenFile); + let tempDir: string = path.join( + tempDirectory, + 'temp_' + Math.floor(Math.random() * 2000000000) + ); + const mavenDir = await unzipMavenDownload( + mavenFile, + compressedFileExtension, + tempDir + ); + core.debug(`maven extracted to ${mavenDir}`); + toolPath = await tc.cacheDir( + mavenDir, + toolName, + getCacheVersionString(version) + ); + } + + core.exportVariable('M2_HOME', toolPath); + core.addPath(path.join(toolPath, 'bin')); +} + +function getCacheVersionString(version: string) { + const versionArray = version.split('.'); + const major = versionArray[0]; + const minor = versionArray.length > 1 ? versionArray[1] : '0'; + const patch = versionArray.length > 2 ? versionArray[2] : '0'; + return `${major}.${minor}.${patch}`; +} + +function getFileEnding(file: string): string { + let fileEnding = ''; + + if (file.endsWith('.tar.gz')) { + fileEnding = '.tar.gz'; + } else if (file.endsWith('.zip')) { + fileEnding = '.zip'; + } else { + throw new Error(`${file} has an unsupported file extension`); + } + + return fileEnding; +} + +async function extractFiles( + file: string, + fileEnding: string, + destinationFolder: string +): Promise { + const stats = fs.statSync(file); + if (!stats) { + throw new Error(`Failed to extract ${file} - it doesn't exist`); + } else if (stats.isDirectory()) { + throw new Error(`Failed to extract ${file} - it is a directory`); + } + + if ('.tar.gz' === fileEnding) { + await tc.extractTar(file, destinationFolder); + } else if ('.zip' === fileEnding) { + await tc.extractZip(file, destinationFolder); + } else { + throw new Error( + `Failed to extract ${file} - only .zip or .tar.gz supported` + ); + } +} + +async function unzipMavenDownload( + repoRoot: string, + fileEnding: string, + destinationFolder: string +): Promise { + // Create the destination folder if it doesn't exist + await io.mkdirP(destinationFolder); + + const mavenFile = path.normalize(repoRoot); + const stats = fs.statSync(mavenFile); + if (stats.isFile()) { + await extractFiles(path.resolve(mavenFile), fileEnding, destinationFolder); + const mavenDirectory = path.join( + destinationFolder, + fs.readdirSync(destinationFolder)[0] + ); + return mavenDirectory; + } else { + throw new Error(`Maven argument ${mavenFile} is not a file`); + } +} + +function getDownloadInfo( + refs: string[], + version: string, + mavenMirror: string +): {version: string; url: string} { + version = normalizeVersion(version); + let extension = ''; + if (IS_WINDOWS) { + extension = `.zip`; + } else { + extension = `.tar.gz`; + } + + // Maps version to url + let versionMap = new Map(); + + // Filter by platform + refs.forEach(ref => { + if (semver.satisfies(ref, version)) { + core.debug(`VersionMap add ${ref} ${version}`); + versionMap.set( + ref, + `${mavenMirror}${ref}/binaries/apache-maven-${ref}-bin${extension}` + ); + } + }); + + // Choose the most recent satisfying version + let curVersion = '0.0.0'; + let curUrl = ''; + for (const entry of versionMap.entries()) { + const entryVersion = entry[0]; + const entryUrl = entry[1]; + core.debug(`VersionMap Entry ${entryVersion} ${entryUrl}`); + if (semver.gt(entryVersion, curVersion)) { + core.debug(`VersionMap semver gt ${entryVersion} ${entryUrl}`); + curUrl = entryUrl; + curVersion = entryVersion; + } + } + + if (curUrl == '') { + throw new Error( + `No valid download found for version ${version}. Check ${mavenMirror} for a list of valid versions or download your own maven file and add the mavenFile argument` + ); + } + + return {version: curVersion, url: curUrl}; +} + +function normalizeVersion(version: string): string { + if (version.slice(0, 2) === '1.') { + // Trim leading 1. for versions like 1.8 + version = version.slice(2); + if (!version) { + throw new Error('1. is not a valid version'); + } + } + + if (version.split('.').length < 3) { + // Add trailing .x if it is missing + if (version[version.length - 1] != 'x') { + version = version + '.x'; + } + } + + return version; +} diff --git a/src/setup-java.ts b/src/setup-java.ts index d0392175..d9cbb071 100644 --- a/src/setup-java.ts +++ b/src/setup-java.ts @@ -1,5 +1,7 @@ import * as core from '@actions/core'; import * as installer from './installer'; +import * as mavenInstaller from './maven-installer'; +import * as gradleInstaller from './gradle-installer'; import * as auth from './auth'; import * as path from 'path'; @@ -15,6 +17,19 @@ async function run() { await installer.getJava(version, arch, jdkFile, javaPackage); + const mavenVersion = core.getInput('maven-version', {required: false}); + const mavenFile = core.getInput('maven-file', {required: false}) || ''; + const mavenMirror = core.getInput('maven-mirror', {required: false}); + if (mavenVersion) { + await mavenInstaller.getMaven(mavenVersion, mavenFile, mavenMirror); + } + + const gradleVersion = core.getInput('gradle-version', {required: false}); + const gradleFile = core.getInput('gradle-file', {required: false}) || ''; + if (gradleVersion) { + await gradleInstaller.getGradle(gradleVersion, gradleFile); + } + const matchersPath = path.join(__dirname, '..', '.github'); console.log(`##[add-matcher]${path.join(matchersPath, 'java.json')}`);