diff --git a/.gitignore b/.gitignore index 283c045..97de0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.*.*.*.*.json +.scannerwork/ # Runtime data pids diff --git a/README.md b/README.md index 7bb90f9..1d16d98 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ jobs: - name: Set up Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.8.2 + maven-version: 3.8 ``` ### Development using [Docker](https://docs.docker.com/) @@ -41,5 +41,5 @@ Run `SonarScanner` from [the Docker image](https://hub.docker.com/r/sonarsource/ ```batch docker run --rm -it --link docker-sonarqube -v "%PWD%:/usr/src/app" -w /usr/src/app ^ -e "SONAR_HOST_URL=http://docker-sonarqube:9000" -e "SONAR_LOGIN=" sonarsource/sonar-scanner-cli ^ - -Dsonar.projectKey=setup-maven -Dsonar.language=js -Dsonar.sources=. "-Dsonar.exclusions=dist/**" + -Dsonar.projectKey=setup-maven -Dsonar.sources=. "-Dsonar.exclusions=dist/**,lib/**" ``` diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 7363f55..1d70ec2 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -1,5 +1,10 @@ -describe('maven installer tests', () => { - it('square root of 4 to equal 2', () => { - expect(Math.sqrt(4)).toBe(2); +import * as installer from '../src/installer'; + +describe('getAvailableVersions', () => { + it('load real available versions', async () => { + const availableVersions = await installer.getAvailableVersions(); + + expect(availableVersions).toBeTruthy(); + expect(availableVersions).toEqual(expect.arrayContaining(['3.2.5', '3.3.3', '3.8.2'])); }); }); diff --git a/action.yml b/action.yml index 5e8dcaf..dd784e9 100644 --- a/action.yml +++ b/action.yml @@ -5,7 +5,10 @@ inputs: maven-version: description: 'Version Spec of the version to use. Examples: 3.x, 3.1.1, >=3.8.0' required: false - default: '3.8.2' + default: '3' +outputs: + version: + description: 'Actual version of Apache Maven that has been installed' runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 2f8ee13..8f45dd4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4965,37 +4965,81 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getMaven = void 0; +exports.findVersionForDownload = exports.downloadMaven = exports.getAvailableVersions = exports.setupMaven = void 0; +const path = __importStar(__nccwpck_require__(17)); const core = __importStar(__nccwpck_require__(186)); const tc = __importStar(__nccwpck_require__(784)); -const path = __importStar(__nccwpck_require__(17)); -function getMaven(version) { +const http_client_1 = __nccwpck_require__(925); +const semver = __importStar(__nccwpck_require__(911)); +const utils_1 = __nccwpck_require__(314); +function setupMaven(versionSpec, installedVersion) { return __awaiter(this, void 0, void 0, function* () { - let toolPath = tc.find('maven', version); - if (!toolPath) { - toolPath = yield downloadMaven(version); + let toolPath = tc.find('maven', versionSpec); + let resolvedVersion = utils_1.getVersionFromToolcachePath(toolPath); + if (installedVersion) { + if (!toolPath || semver.gte(installedVersion, resolvedVersion)) { + core.info(`Use system Maven version ${installedVersion} instead of the cached one: ${resolvedVersion}`); + return installedVersion; + } + } + else if (!toolPath) { + resolvedVersion = yield findVersionForDownload(versionSpec); + toolPath = yield downloadMaven(resolvedVersion); } core.addPath(path.join(toolPath, 'bin')); + return resolvedVersion; }); } -exports.getMaven = getMaven; +exports.setupMaven = setupMaven; const DOWNLOAD_BASE_URL = 'https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven'; -function downloadMaven(version) { +function getAvailableVersions() { return __awaiter(this, void 0, void 0, function* () { - const toolDirectoryName = `apache-maven-${version}`; - const downloadUrl = `${DOWNLOAD_BASE_URL}/${version}/${toolDirectoryName}-bin.tar.gz`; - core.info(`Downloading Maven ${version} from ${downloadUrl} ...`); + const resourceUrl = `${DOWNLOAD_BASE_URL}/maven-metadata.xml`; + const http = new http_client_1.HttpClient('setup-maven', undefined, { allowRetries: true }); + core.info(`Downloading Maven versions manifest from ${resourceUrl} ...`); + const response = yield http.get(resourceUrl); + const body = yield response.readBody(); + if (response.message.statusCode !== http_client_1.HttpCodes.OK || !body) { + throw new Error(`Unable to get available versions from ${resourceUrl}`); + } + const availableVersions = body.match(/(?<=)[^<>]+(?=<\/version>)/g) || []; + core.debug(`Available Maven versions: [${availableVersions}]`); + return availableVersions; + }); +} +exports.getAvailableVersions = getAvailableVersions; +/** + * Download and extract a specified Maven version to the tool-cache. + */ +function downloadMaven(fullVersion) { + return __awaiter(this, void 0, void 0, function* () { + const toolDirectoryName = `apache-maven-${fullVersion}`; + const downloadUrl = `${DOWNLOAD_BASE_URL}/${fullVersion}/${toolDirectoryName}-bin.tar.gz`; + core.info(`Downloading Maven ${fullVersion} from ${downloadUrl} ...`); const downloadPath = yield tc.downloadTool(downloadUrl); const extractedPath = yield tc.extractTar(downloadPath); const toolRoot = path.join(extractedPath, toolDirectoryName); - return tc.cacheDir(toolRoot, 'maven', version); + return tc.cacheDir(toolRoot, 'maven', fullVersion); }); } +exports.downloadMaven = downloadMaven; +function findVersionForDownload(versionSpec) { + return __awaiter(this, void 0, void 0, function* () { + const availableVersions = yield getAvailableVersions(); + const resolvedVersion = semver.maxSatisfying(availableVersions, versionSpec); + if (!resolvedVersion) { + throw new Error(`Could not find satisfied version for SemVer ${versionSpec}`); + } + core.debug(`Resolved version for download: ${resolvedVersion}`); + return resolvedVersion; + }); +} +exports.findVersionForDownload = findVersionForDownload; /***/ }), -/***/ 587: +/***/ 399: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -5029,23 +5073,115 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.run = void 0; const core = __importStar(__nccwpck_require__(186)); -const installer = __importStar(__nccwpck_require__(574)); +const semver = __importStar(__nccwpck_require__(911)); +const utils_1 = __nccwpck_require__(314); +const installer_1 = __nccwpck_require__(574); function run() { return __awaiter(this, void 0, void 0, function* () { try { - const version = core.getInput('maven-version'); - if (version) { - yield installer.getMaven(version); + const versionSpec = core.getInput('maven-version') || '3'; + if (!semver.validRange(versionSpec)) { + core.setFailed(`Invalid SemVer notation '${versionSpec}' for a Maven version`); + return; } + let installedVersion = yield utils_1.getActiveMavenVersion(); + if (installedVersion && !semver.satisfies(installedVersion, versionSpec)) { + installedVersion = undefined; + } + installedVersion = yield installer_1.setupMaven(versionSpec, installedVersion); + core.setOutput('version', installedVersion); } catch (error) { - core.setFailed(error.message); + core.setFailed(error.toString()); } }); } -// noinspection JSIgnoredPromiseFromCall -run(); +exports.run = run; + + +/***/ }), + +/***/ 314: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getExecOutput = exports.getActiveMavenVersion = exports.getVersionFromToolcachePath = void 0; +const path = __importStar(__nccwpck_require__(17)); +const core = __importStar(__nccwpck_require__(186)); +const exec = __importStar(__nccwpck_require__(514)); +function getVersionFromToolcachePath(toolPath) { + return !toolPath ? toolPath : path.basename(path.dirname(toolPath)); +} +exports.getVersionFromToolcachePath = getVersionFromToolcachePath; +/** + * Determine version of the current used Maven. + */ +function getActiveMavenVersion() { + return __awaiter(this, void 0, void 0, function* () { + try { + const { output } = yield getExecOutput('mvn', ['-v']); + const found = output.match(/^[^\d]*(\S+)/); + const installedVersion = !found ? '' : found[1]; + core.debug(`Retrieved activated Maven version: ${installedVersion}`); + return installedVersion; + } + catch (error) { + core.info(`Failed to get activated Maven version. ${error}`); + } + return undefined; + }); +} +exports.getActiveMavenVersion = getActiveMavenVersion; +/** + * Exec a command and get the standard output. + * + * @throws {Error} If the exit-code is non-zero. + */ +function getExecOutput(command, args) { + return __awaiter(this, void 0, void 0, function* () { + let output = ''; + const exitCode = yield exec.exec(command, args, { + silent: true, + listeners: { + stdout: (data) => (output += data.toString()) + } + }); + return { exitCode, output }; + }); +} +exports.getExecOutput = getExecOutput; /***/ }), @@ -5192,12 +5328,19 @@ module.exports = require("util"); /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; /******/ /************************************************************************/ -/******/ -/******/ // startup -/******/ // Load entry module and return exports -/******/ // This entry module is referenced by other modules so it can't be inlined -/******/ var __webpack_exports__ = __nccwpck_require__(587); -/******/ module.exports = __webpack_exports__; -/******/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +(() => { +"use strict"; +var exports = __webpack_exports__; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +const main_1 = __nccwpck_require__(399); +// noinspection JSIgnoredPromiseFromCall +main_1.run(); + +})(); + +module.exports = __webpack_exports__; /******/ })() ; \ No newline at end of file diff --git a/package.json b/package.json index 6791424..5e3bce2 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,10 @@ "typescript": "^4.2.3" }, "prettier": { + "printWidth": 100, "semi": true, + "singleQuote": true, "trailingComma": "none", - "bracketSpacing": true, "arrowParens": "avoid" }, "jest": { diff --git a/src/installer.ts b/src/installer.ts index 76bb96c..a90c380 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -1,29 +1,77 @@ +import * as path from 'path'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; +import { HttpClient, HttpCodes } from '@actions/http-client'; +import * as semver from 'semver'; -import * as path from 'path'; +import { getVersionFromToolcachePath } from './utils'; -export async function getMaven(version: string) { - let toolPath = tc.find('maven', version); +export async function setupMaven(versionSpec: string, installedVersion?: string): Promise { + let toolPath = tc.find('maven', versionSpec); + let resolvedVersion = getVersionFromToolcachePath(toolPath); - if (!toolPath) { - toolPath = await downloadMaven(version); + if (installedVersion) { + if (!toolPath || semver.gte(installedVersion, resolvedVersion)) { + core.info( + `Use system Maven version ${installedVersion} instead of the cached one: ${resolvedVersion}` + ); + + return installedVersion; + } + } else if (!toolPath) { + resolvedVersion = await findVersionForDownload(versionSpec); + + toolPath = await downloadMaven(resolvedVersion); } core.addPath(path.join(toolPath, 'bin')); + return resolvedVersion; } const DOWNLOAD_BASE_URL = 'https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven'; -async function downloadMaven(version: string): Promise { - const toolDirectoryName = `apache-maven-${version}`; - const downloadUrl = `${DOWNLOAD_BASE_URL}/${version}/${toolDirectoryName}-bin.tar.gz`; +export async function getAvailableVersions(): Promise { + const resourceUrl = `${DOWNLOAD_BASE_URL}/maven-metadata.xml`; + const http = new HttpClient('setup-maven', undefined, { allowRetries: true }); - core.info(`Downloading Maven ${version} from ${downloadUrl} ...`); + core.info(`Downloading Maven versions manifest from ${resourceUrl} ...`); + const response = await http.get(resourceUrl); + const body = await response.readBody(); + + if (response.message.statusCode !== HttpCodes.OK || !body) { + throw new Error(`Unable to get available versions from ${resourceUrl}`); + } + + const availableVersions = body.match(/(?<=)[^<>]+(?=<\/version>)/g) || []; + core.debug(`Available Maven versions: [${availableVersions}]`); + + return availableVersions; +} + +/** + * Download and extract a specified Maven version to the tool-cache. + */ +export async function downloadMaven(fullVersion: string): Promise { + const toolDirectoryName = `apache-maven-${fullVersion}`; + const downloadUrl = `${DOWNLOAD_BASE_URL}/${fullVersion}/${toolDirectoryName}-bin.tar.gz`; + + core.info(`Downloading Maven ${fullVersion} from ${downloadUrl} ...`); const downloadPath = await tc.downloadTool(downloadUrl); const extractedPath = await tc.extractTar(downloadPath); const toolRoot = path.join(extractedPath, toolDirectoryName); - return tc.cacheDir(toolRoot, 'maven', version); + return tc.cacheDir(toolRoot, 'maven', fullVersion); +} + +export async function findVersionForDownload(versionSpec: string): Promise { + const availableVersions = await getAvailableVersions(); + + const resolvedVersion = semver.maxSatisfying(availableVersions, versionSpec); + if (!resolvedVersion) { + throw new Error(`Could not find satisfied version for SemVer ${versionSpec}`); + } + + core.debug(`Resolved version for download: ${resolvedVersion}`); + return resolvedVersion; } diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5249e65 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,26 @@ +import * as core from '@actions/core'; +import * as semver from 'semver'; + +import { getActiveMavenVersion } from './utils'; +import { setupMaven } from './installer'; + +export async function run() { + try { + const versionSpec = core.getInput('maven-version') || '3'; + + if (!semver.validRange(versionSpec)) { + core.setFailed(`Invalid SemVer notation '${versionSpec}' for a Maven version`); + return; + } + + let installedVersion = await getActiveMavenVersion(); + if (installedVersion && !semver.satisfies(installedVersion, versionSpec)) { + installedVersion = undefined; + } + + installedVersion = await setupMaven(versionSpec, installedVersion); + core.setOutput('version', installedVersion); + } catch (error) { + core.setFailed(error.toString()); + } +} diff --git a/src/setup-maven.ts b/src/setup-maven.ts index 6eca19a..59534a3 100644 --- a/src/setup-maven.ts +++ b/src/setup-maven.ts @@ -1,17 +1,4 @@ -import * as core from '@actions/core'; - -import * as installer from './installer'; - -async function run() { - try { - const version = core.getInput('maven-version'); - if (version) { - await installer.getMaven(version); - } - } catch (error) { - core.setFailed(error.message); - } -} +import { run } from './main'; // noinspection JSIgnoredPromiseFromCall run(); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..36571d4 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,47 @@ +import * as path from 'path'; +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; + +export function getVersionFromToolcachePath(toolPath: string) { + return !toolPath ? toolPath : path.basename(path.dirname(toolPath)); +} + +/** + * Determine version of the current used Maven. + */ +export async function getActiveMavenVersion(): Promise { + try { + const { output } = await getExecOutput('mvn', ['-v']); + + const found = output.match(/^[^\d]*(\S+)/); + const installedVersion = !found ? '' : found[1]; + core.debug(`Retrieved activated Maven version: ${installedVersion}`); + + return installedVersion; + } catch (error) { + core.info(`Failed to get activated Maven version. ${error}`); + } + + return undefined; +} + +/** + * Exec a command and get the standard output. + * + * @throws {Error} If the exit-code is non-zero. + */ +export async function getExecOutput( + command: string, + args?: string[] +): Promise<{ exitCode: number; output: string }> { + let output = ''; + + const exitCode = await exec.exec(command, args, { + silent: true, + listeners: { + stdout: (data: Buffer) => (output += data.toString()) + } + }); + + return { exitCode, output }; +}