From 45f9f98a62ad735acf361947de69ac19b1eb47a1 Mon Sep 17 00:00:00 2001 From: Matteo Dell'Acqua <82184604+MatteoH2O1999@users.noreply.github.com> Date: Mon, 22 May 2023 17:08:28 +0200 Subject: [PATCH] Allow action to run on other runners The action now check the path variable in other plaforms for msbuild and fail if it does not find it (or it is not of the correct version) --- dist/index.js | 569 ++++++++++++++++++++++++++++++++-------------- package-lock.json | 82 ++----- package.json | 4 +- src/main.ts | 90 +++++++- src/vs-ver.ts | 48 ++++ 5 files changed, 564 insertions(+), 229 deletions(-) create mode 100644 src/vs-ver.ts diff --git a/dist/index.js b/dist/index.js index 134764c..0449621 100644 --- a/dist/index.js +++ b/dist/index.js @@ -48,6 +48,25 @@ module.exports = "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.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) { @@ -58,11 +77,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -const childProcess = __webpack_require__(129); -const path = __webpack_require__(622); -const util_1 = __webpack_require__(669); -const ioUtil = __webpack_require__(672); -const exec = util_1.promisify(childProcess.exec); +exports.findInPath = exports.which = exports.mkdirP = exports.rmRF = exports.mv = exports.cp = void 0; +const assert_1 = __webpack_require__(357); +const path = __importStar(__webpack_require__(622)); +const ioUtil = __importStar(__webpack_require__(672)); /** * Copies a file or folder. * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js @@ -73,14 +91,14 @@ const exec = util_1.promisify(childProcess.exec); */ function cp(source, dest, options = {}) { return __awaiter(this, void 0, void 0, function* () { - const { force, recursive } = readCopyOptions(options); + const { force, recursive, copySourceDirectory } = readCopyOptions(options); const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; // Dest is an existing file, but not forcing if (destStat && destStat.isFile() && !force) { return; } // If dest is an existing directory, should copy inside. - const newDest = destStat && destStat.isDirectory() + const newDest = destStat && destStat.isDirectory() && copySourceDirectory ? path.join(dest, path.basename(source)) : dest; if (!(yield ioUtil.exists(source))) { @@ -143,51 +161,23 @@ exports.mv = mv; function rmRF(inputPath) { return __awaiter(this, void 0, void 0, function* () { if (ioUtil.IS_WINDOWS) { - // Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another - // program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del. - try { - if (yield ioUtil.isDirectory(inputPath, true)) { - yield exec(`rd /s /q "${inputPath}"`); - } - else { - yield exec(`del /f /a "${inputPath}"`); - } - } - catch (err) { - // if you try to delete a file that doesn't exist, desired result is achieved - // other errors are valid - if (err.code !== 'ENOENT') - throw err; - } - // Shelling out fails to remove a symlink folder with missing source, this unlink catches that - try { - yield ioUtil.unlink(inputPath); - } - catch (err) { - // if you try to delete a file that doesn't exist, desired result is achieved - // other errors are valid - if (err.code !== 'ENOENT') - throw err; + // Check for invalid characters + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + if (/[*"<>|]/.test(inputPath)) { + throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'); } } - else { - let isDir = false; - try { - isDir = yield ioUtil.isDirectory(inputPath); - } - catch (err) { - // if you try to delete a file that doesn't exist, desired result is achieved - // other errors are valid - if (err.code !== 'ENOENT') - throw err; - return; - } - if (isDir) { - yield exec(`rm -rf "${inputPath}"`); - } - else { - yield ioUtil.unlink(inputPath); - } + try { + // note if path does not exist, error is silent + yield ioUtil.rm(inputPath, { + force: true, + maxRetries: 3, + recursive: true, + retryDelay: 300 + }); + } + catch (err) { + throw new Error(`File was unable to be removed ${err}`); } }); } @@ -201,7 +191,8 @@ exports.rmRF = rmRF; */ function mkdirP(fsPath) { return __awaiter(this, void 0, void 0, function* () { - yield ioUtil.mkdirP(fsPath); + assert_1.ok(fsPath, 'a path argument must be provided'); + yield ioUtil.mkdir(fsPath, { recursive: true }); }); } exports.mkdirP = mkdirP; @@ -229,62 +220,80 @@ function which(tool, check) { throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); } } + return result; } - try { - // build the list of extensions to try - const extensions = []; - if (ioUtil.IS_WINDOWS && process.env.PATHEXT) { - for (const extension of process.env.PATHEXT.split(path.delimiter)) { - if (extension) { - extensions.push(extension); - } - } - } - // if it's rooted, return it if exists. otherwise return empty. - if (ioUtil.isRooted(tool)) { - const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); - if (filePath) { - return filePath; - } - return ''; - } - // if any path separators, return empty - if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) { - return ''; - } - // build the list of directories - // - // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, - // it feels like we should not do this. Checking the current directory seems like more of a use - // case of a shell, and the which() function exposed by the toolkit should strive for consistency - // across platforms. - const directories = []; - if (process.env.PATH) { - for (const p of process.env.PATH.split(path.delimiter)) { - if (p) { - directories.push(p); - } - } - } - // return the first match - for (const directory of directories) { - const filePath = yield ioUtil.tryGetExecutablePath(directory + path.sep + tool, extensions); - if (filePath) { - return filePath; - } - } - return ''; - } - catch (err) { - throw new Error(`which failed with message ${err.message}`); + const matches = yield findInPath(tool); + if (matches && matches.length > 0) { + return matches[0]; } + return ''; }); } exports.which = which; +/** + * Returns a list of all occurrences of the given tool on the system path. + * + * @returns Promise the paths of the tool + */ +function findInPath(tool) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // build the list of extensions to try + const extensions = []; + if (ioUtil.IS_WINDOWS && process.env['PATHEXT']) { + for (const extension of process.env['PATHEXT'].split(path.delimiter)) { + if (extension) { + extensions.push(extension); + } + } + } + // if it's rooted, return it if exists. otherwise return empty. + if (ioUtil.isRooted(tool)) { + const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); + if (filePath) { + return [filePath]; + } + return []; + } + // if any path separators, return empty + if (tool.includes(path.sep)) { + return []; + } + // build the list of directories + // + // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, + // it feels like we should not do this. Checking the current directory seems like more of a use + // case of a shell, and the which() function exposed by the toolkit should strive for consistency + // across platforms. + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path.delimiter)) { + if (p) { + directories.push(p); + } + } + } + // find all matches + const matches = []; + for (const directory of directories) { + const filePath = yield ioUtil.tryGetExecutablePath(path.join(directory, tool), extensions); + if (filePath) { + matches.push(filePath); + } + } + return matches; + }); +} +exports.findInPath = findInPath; function readCopyOptions(options) { const force = options.force == null ? true : options.force; const recursive = Boolean(options.recursive); - return { force, recursive }; + const copySourceDirectory = options.copySourceDirectory == null + ? true + : Boolean(options.copySourceDirectory); + return { force, recursive, copySourceDirectory }; } function cpDirRecursive(sourceDir, destDir, currentDepth, force) { return __awaiter(this, void 0, void 0, function* () { @@ -360,6 +369,25 @@ exports.default = _default; "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.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) { @@ -370,12 +398,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -const os = __webpack_require__(87); -const events = __webpack_require__(614); -const child = __webpack_require__(129); -const path = __webpack_require__(622); -const io = __webpack_require__(1); -const ioUtil = __webpack_require__(672); +exports.argStringToArray = exports.ToolRunner = void 0; +const os = __importStar(__webpack_require__(87)); +const events = __importStar(__webpack_require__(614)); +const child = __importStar(__webpack_require__(129)); +const path = __importStar(__webpack_require__(622)); +const io = __importStar(__webpack_require__(1)); +const ioUtil = __importStar(__webpack_require__(672)); +const timers_1 = __webpack_require__(213); /* eslint-disable @typescript-eslint/unbound-method */ const IS_WINDOWS = process.platform === 'win32'; /* @@ -445,11 +475,12 @@ class ToolRunner extends events.EventEmitter { s = s.substring(n + os.EOL.length); n = s.indexOf(os.EOL); } - strBuffer = s; + return s; } catch (err) { // streaming lines to console is best effort. Don't fail a build. this._debug(`error processing line. Failed with error ${err}`); + return ''; } } _getSpawnFileName() { @@ -731,7 +762,7 @@ class ToolRunner extends events.EventEmitter { // if the tool is only a file name, then resolve it from the PATH // otherwise verify it exists (add extension on Windows if necessary) this.toolPath = yield io.which(this.toolPath, true); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { this._debug(`exec tool: ${this.toolPath}`); this._debug('arguments:'); for (const arg of this.args) { @@ -745,9 +776,12 @@ class ToolRunner extends events.EventEmitter { state.on('debug', (message) => { this._debug(message); }); + if (this.options.cwd && !(yield ioUtil.exists(this.options.cwd))) { + return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`)); + } const fileName = this._getSpawnFileName(); const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); - const stdbuffer = ''; + let stdbuffer = ''; if (cp.stdout) { cp.stdout.on('data', (data) => { if (this.options.listeners && this.options.listeners.stdout) { @@ -756,14 +790,14 @@ class ToolRunner extends events.EventEmitter { if (!optionsNonNull.silent && optionsNonNull.outStream) { optionsNonNull.outStream.write(data); } - this._processLineBuffer(data, stdbuffer, (line) => { + stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { if (this.options.listeners && this.options.listeners.stdline) { this.options.listeners.stdline(line); } }); }); } - const errbuffer = ''; + let errbuffer = ''; if (cp.stderr) { cp.stderr.on('data', (data) => { state.processStderr = true; @@ -778,7 +812,7 @@ class ToolRunner extends events.EventEmitter { : optionsNonNull.outStream; s.write(data); } - this._processLineBuffer(data, errbuffer, (line) => { + errbuffer = this._processLineBuffer(data, errbuffer, (line) => { if (this.options.listeners && this.options.listeners.errline) { this.options.listeners.errline(line); } @@ -819,7 +853,13 @@ class ToolRunner extends events.EventEmitter { resolve(exitCode); } }); - }); + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin'); + } + cp.stdin.end(this.options.input); + } + })); }); } } @@ -905,7 +945,7 @@ class ExecState extends events.EventEmitter { this._setResult(); } else if (this.processExited) { - this.timeout = setTimeout(ExecState.HandleTimeout, this.delay, this); + this.timeout = timers_1.setTimeout(ExecState.HandleTimeout, this.delay, this); } } _debug(message) { @@ -1648,6 +1688,7 @@ const exec = __importStar(__webpack_require__(986)); const fs = __importStar(__webpack_require__(747)); const path = __importStar(__webpack_require__(622)); const io = __importStar(__webpack_require__(1)); +const vsVer = __importStar(__webpack_require__(251)); const IS_WINDOWS = process.platform === 'win32'; const VS_VERSION = core.getInput('vs-version') || 'latest'; const VSWHERE_PATH = core.getInput('vswhere-path'); @@ -1662,12 +1703,91 @@ if (VS_VERSION !== 'latest') { VSWHERE_EXEC += `-version "${VS_VERSION}" `; } core.debug(`Execution arguments: ${VSWHERE_EXEC}`); +function checkVersionInPath() { + return __awaiter(this, void 0, void 0, function* () { + const tool = yield io.which('msbuild', false); + const execOutput = yield exec.getExecOutput(`"${tool}"`, ['--ver'], { + silent: true + }); + // Exit if path is wrong or version does not match regex + if (execOutput.exitCode !== 0) { + return false; + } + const versionMatch = execOutput.stdout.match(/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/); + const versionString = versionMatch ? versionMatch[0] : ''; + if (!versionString) { + return false; + } + // If "latest" every found version goes + if (VS_VERSION === 'latest') { + return true; + } + // Prepare arrays for tool version and min-max versions + const splitVersion = versionString.split('.'); + const version = [ + parseInt(splitVersion[0]), + parseInt(splitVersion[1]), + parseInt(splitVersion[2]), + parseInt(splitVersion[3]) + ]; + const constraints = VS_VERSION.split(','); + const minInclusive = !constraints[0].startsWith('('); + const maxInclusive = constraints.length === 2 ? constraints[1].endsWith(']') : false; + const minVersionString = (constraints[0].replace('[', '').replace('(', '') || '0.0').split('.'); + while (minVersionString.length !== 4) { + minVersionString.push('0'); + } + const minVersion = [ + parseInt(minVersionString[0]), + parseInt(minVersionString[1]), + parseInt(minVersionString[2]), + parseInt(minVersionString[3]) + ]; + const maxVersionString = ((constraints[1] ? constraints[1].replace(')', '').replace(']', '') : '') || + '65535.65535.65535.65535').split('.'); + while (maxVersionString.length !== 4) { + maxVersionString.push('0'); + } + const maxVersion = [ + parseInt(maxVersionString[0]), + parseInt(maxVersionString[1]), + parseInt(maxVersionString[2]), + parseInt(maxVersionString[3]) + ]; + // Check version + if (minInclusive) { + if (vsVer.lt(version, minVersion)) { + return false; + } + } + else { + if (vsVer.lte(version, minVersion)) { + return false; + } + } + if (maxInclusive) { + if (vsVer.gt(version, maxVersion)) { + return false; + } + } + else { + if (vsVer.gte(version, maxVersion)) { + return false; + } + } + return true; + }); +} function run() { return __awaiter(this, void 0, void 0, function* () { try { - // exit if non Windows runner + // exit if non Windows runner and msbuild not already in PATH if (IS_WINDOWS === false) { - core.setFailed('setup-msbuild can only be run on Windows runners'); + if (yield checkVersionInPath()) { + core.info('Correct msbuild version is already in PATH'); + return; + } + core.setFailed('setup-msbuild can only run vswhere on Windows runners'); return; } // check to see if we are using a specific path for vswhere @@ -1707,7 +1827,7 @@ function run() { if (MSBUILD_ARCH === 'x64') { MSBUILD_ARCH = 'amd64'; } - let toolPath = path.join(installationPath, `MSBuild\\Current\\Bin\\${MSBUILD_ARCH}\\MSBuild.exe`); + const toolPath = path.join(installationPath, `MSBuild\\Current\\Bin\\${MSBUILD_ARCH}\\MSBuild.exe`); core.debug(`Checking for path: ${toolPath}`); if (!fs.existsSync(toolPath)) { return; @@ -1743,7 +1863,12 @@ function run() { core.debug(`Tool path added to PATH: ${toolFolderPath}`); } catch (error) { - core.setFailed(error.message); + if (error instanceof Error) { + core.setFailed(error.message); + } + else { + core.setFailed('Unknown error'); + } } }); } @@ -1809,6 +1934,59 @@ exports.default = _default; module.exports = require("https"); +/***/ }), + +/***/ 213: +/***/ (function(module) { + +module.exports = require("timers"); + +/***/ }), + +/***/ 251: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function lt(a, b) { + for (let index = 0; index < 4; index++) { + if (a[index] < b[index]) { + return true; + } + else if (a[index] > b[index]) { + return false; + } + } + return false; +} +exports.lt = lt; +function lte(a, b) { + return !gt(a, b); +} +exports.lte = lte; +function gt(a, b) { + for (let index = 0; index < 4; index++) { + if (a[index] > b[index]) { + return true; + } + else if (a[index] < b[index]) { + return false; + } + } + return false; +} +exports.gt = gt; +function gte(a, b) { + return !lt(a, b); +} +exports.gte = gte; +function eq(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; +} +exports.eq = eq; + + /***/ }), /***/ 293: @@ -1855,6 +2033,13 @@ exports.default = _default; /***/ }), +/***/ 304: +/***/ (function(module) { + +module.exports = require("string_decoder"); + +/***/ }), + /***/ 329: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -2978,6 +3163,25 @@ module.exports = require("util"); "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.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) { @@ -2989,11 +3193,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); -const assert_1 = __webpack_require__(357); -const fs = __webpack_require__(747); -const path = __webpack_require__(622); -_a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0; +const fs = __importStar(__webpack_require__(747)); +const path = __importStar(__webpack_require__(622)); +_a = fs.promises +// export const {open} = 'fs' +, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +// export const {open} = 'fs' exports.IS_WINDOWS = process.platform === 'win32'; +// See https://github.com/nodejs/node/blob/d0153aee367422d0858105abec186da4dff0a0c5/deps/uv/include/uv/win.h#L691 +exports.UV_FS_O_EXLOCK = 0x10000000; +exports.READONLY = fs.constants.O_RDONLY; function exists(fsPath) { return __awaiter(this, void 0, void 0, function* () { try { @@ -3032,49 +3242,6 @@ function isRooted(p) { return p.startsWith('/'); } exports.isRooted = isRooted; -/** - * Recursively create a directory at `fsPath`. - * - * This implementation is optimistic, meaning it attempts to create the full - * path first, and backs up the path stack from there. - * - * @param fsPath The path to create - * @param maxDepth The maximum recursion depth - * @param depth The current recursion depth - */ -function mkdirP(fsPath, maxDepth = 1000, depth = 1) { - return __awaiter(this, void 0, void 0, function* () { - assert_1.ok(fsPath, 'a path argument must be provided'); - fsPath = path.resolve(fsPath); - if (depth >= maxDepth) - return exports.mkdir(fsPath); - try { - yield exports.mkdir(fsPath); - return; - } - catch (err) { - switch (err.code) { - case 'ENOENT': { - yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1); - yield exports.mkdir(fsPath); - return; - } - default: { - let stats; - try { - stats = yield exports.stat(fsPath); - } - catch (err2) { - throw err; - } - if (!stats.isDirectory()) - throw err; - } - } - } - }); -} -exports.mkdirP = mkdirP; /** * Best effort attempt to determine whether a file exists and is executable. * @param filePath file path to check @@ -3171,6 +3338,12 @@ function isUnixExecutable(stats) { ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || ((stats.mode & 64) > 0 && stats.uid === process.getuid())); } +// Get the path of cmd.exe in windows +function getCmdPath() { + var _a; + return (_a = process.env['COMSPEC']) !== null && _a !== void 0 ? _a : `cmd.exe`; +} +exports.getCmdPath = getCmdPath; //# sourceMappingURL=io-util.js.map /***/ }), @@ -3431,6 +3604,25 @@ exports.default = _default; "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.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) { @@ -3441,7 +3633,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -const tr = __webpack_require__(9); +exports.getExecOutput = exports.exec = void 0; +const string_decoder_1 = __webpack_require__(304); +const tr = __importStar(__webpack_require__(9)); /** * Exec a command. * Output will be streamed to the live console. @@ -3466,6 +3660,51 @@ function exec(commandLine, args, options) { }); } exports.exec = exec; +/** + * Exec a command and get the output. + * Output will be streamed to the live console. + * Returns promise with the exit code and collected stdout and stderr + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code, stdout, and stderr + */ +function getExecOutput(commandLine, args, options) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + let stdout = ''; + let stderr = ''; + //Using string decoder covers the case where a mult-byte character is split + const stdoutDecoder = new string_decoder_1.StringDecoder('utf8'); + const stderrDecoder = new string_decoder_1.StringDecoder('utf8'); + const originalStdoutListener = (_a = options === null || options === void 0 ? void 0 : options.listeners) === null || _a === void 0 ? void 0 : _a.stdout; + const originalStdErrListener = (_b = options === null || options === void 0 ? void 0 : options.listeners) === null || _b === void 0 ? void 0 : _b.stderr; + const stdErrListener = (data) => { + stderr += stderrDecoder.write(data); + if (originalStdErrListener) { + originalStdErrListener(data); + } + }; + const stdOutListener = (data) => { + stdout += stdoutDecoder.write(data); + if (originalStdoutListener) { + originalStdoutListener(data); + } + }; + const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener }); + const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + //flush any remaining characters + stdout += stdoutDecoder.end(); + stderr += stderrDecoder.end(); + return { + exitCode, + stdout, + stderr + }; + }); +} +exports.getExecOutput = getExecOutput; //# sourceMappingURL=exec.js.map /***/ }), diff --git a/package-lock.json b/package-lock.json index 00a3193..23688d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "setup-msbuild", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "setup-msbuild", - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "dependencies": { "@actions/core": "^1.10.0", - "@actions/exec": "^1.0.3", - "@actions/tool-cache": "^1.3.0" + "@actions/exec": "^1.1.1", + "@actions/io": "^1.1.3" }, "devDependencies": { "@types/jest": "^24.0.23", @@ -55,38 +55,17 @@ } }, "node_modules/@actions/exec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz", - "integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", "dependencies": { "@actions/io": "^1.0.1" } }, - "node_modules/@actions/http-client": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", - "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", - "dependencies": { - "tunnel": "0.0.6" - } - }, "node_modules/@actions/io": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", - "integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" - }, - "node_modules/@actions/tool-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.0.tgz", - "integrity": "sha512-pbv32I89niDShw1YTDo0OFQmWPkZPElE7e3So1jfEzjIyzGRfYIzshwOVhemJZLcDtzo3kxO3GFDAmuVvub/6w==", - "dependencies": { - "@actions/core": "^1.2.0", - "@actions/exec": "^1.0.0", - "@actions/http-client": "^1.0.1", - "@actions/io": "^1.0.1", - "semver": "^6.1.0", - "uuid": "^3.3.2" - } + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" }, "node_modules/@babel/code-frame": { "version": "7.5.5", @@ -6901,6 +6880,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -7866,6 +7846,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, "bin": { "uuid": "bin/uuid" } @@ -8206,38 +8187,17 @@ } }, "@actions/exec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz", - "integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", "requires": { "@actions/io": "^1.0.1" } }, - "@actions/http-client": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", - "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", - "requires": { - "tunnel": "0.0.6" - } - }, "@actions/io": { - "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.3.0", - "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.0.tgz", - "integrity": "sha512-pbv32I89niDShw1YTDo0OFQmWPkZPElE7e3So1jfEzjIyzGRfYIzshwOVhemJZLcDtzo3kxO3GFDAmuVvub/6w==", - "requires": { - "@actions/core": "^1.2.0", - "@actions/exec": "^1.0.0", - "@actions/http-client": "^1.0.1", - "@actions/io": "^1.0.1", - "semver": "^6.1.0", - "uuid": "^3.3.2" - } + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" }, "@babel/code-frame": { "version": "7.5.5", @@ -13770,7 +13730,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "set-blocking": { "version": "2.0.0", @@ -14552,7 +14513,8 @@ "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", diff --git a/package.json b/package.json index 2ab4da2..de906a0 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ "license": "MIT", "dependencies": { "@actions/core": "^1.10.0", - "@actions/exec": "^1.0.3", - "@actions/tool-cache": "^1.3.0" + "@actions/exec": "^1.1.1", + "@actions/io": "^1.1.3" }, "devDependencies": { "@types/jest": "^24.0.23", diff --git a/src/main.ts b/src/main.ts index 6a06cbd..9be9f5b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import * as exec from '@actions/exec' import * as fs from 'fs' import * as path from 'path' import * as io from '@actions/io' +import * as vsVer from './vs-ver' import {ExecOptions} from '@actions/exec/lib/interfaces' const IS_WINDOWS = process.platform === 'win32' @@ -24,11 +25,96 @@ if (VS_VERSION !== 'latest') { core.debug(`Execution arguments: ${VSWHERE_EXEC}`) +async function checkVersionInPath(): Promise { + const tool = await io.which('msbuild', false) + const execOutput = await exec.getExecOutput(`"${tool}"`, ['--ver'], { + silent: true + }) + + // Exit if path is wrong or version does not match regex + if (execOutput.exitCode !== 0) { + return false + } + const versionMatch = execOutput.stdout.match(/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) + const versionString = versionMatch ? versionMatch[0] : '' + if (!versionString) { + return false + } + + // If "latest" every found version goes + if (VS_VERSION === 'latest') { + return true + } + + // Prepare arrays for tool version and min-max versions + const splitVersion = versionString.split('.') + const version: [number, number, number, number] = [ + parseInt(splitVersion[0]), + parseInt(splitVersion[1]), + parseInt(splitVersion[2]), + parseInt(splitVersion[3]) + ] + const constraints = VS_VERSION.split(',') + const minInclusive = !constraints[0].startsWith('(') + const maxInclusive = + constraints.length === 2 ? constraints[1].endsWith(']') : false + const minVersionString = ( + constraints[0].replace('[', '').replace('(', '') || '0.0' + ).split('.') + while (minVersionString.length !== 4) { + minVersionString.push('0') + } + const minVersion: [number, number, number, number] = [ + parseInt(minVersionString[0]), + parseInt(minVersionString[1]), + parseInt(minVersionString[2]), + parseInt(minVersionString[3]) + ] + const maxVersionString = ( + (constraints[1] ? constraints[1].replace(')', '').replace(']', '') : '') || + '65535.65535.65535.65535' + ).split('.') + while (maxVersionString.length !== 4) { + maxVersionString.push('0') + } + const maxVersion: [number, number, number, number] = [ + parseInt(maxVersionString[0]), + parseInt(maxVersionString[1]), + parseInt(maxVersionString[2]), + parseInt(maxVersionString[3]) + ] + + // Check version + if (minInclusive) { + if (vsVer.lt(version, minVersion)) { + return false + } + } else { + if (vsVer.lte(version, minVersion)) { + return false + } + } + if (maxInclusive) { + if (vsVer.gt(version, maxVersion)) { + return false + } + } else { + if (vsVer.gte(version, maxVersion)) { + return false + } + } + return true +} + async function run(): Promise { try { - // exit if non Windows runner + // exit if non Windows runner and msbuild not already in PATH if (IS_WINDOWS === false) { - core.setFailed('setup-msbuild can only be run on Windows runners') + if (await checkVersionInPath()) { + core.info('Correct msbuild version is already in PATH') + return + } + core.setFailed('setup-msbuild can only run vswhere on Windows runners') return } diff --git a/src/vs-ver.ts b/src/vs-ver.ts new file mode 100644 index 0000000..d0247ff --- /dev/null +++ b/src/vs-ver.ts @@ -0,0 +1,48 @@ +export function lt( + a: [number, number, number, number], + b: [number, number, number, number] +): boolean { + for (let index = 0; index < 4; index++) { + if (a[index] < b[index]) { + return true + } else if (a[index] > b[index]) { + return false + } + } + return false +} + +export function lte( + a: [number, number, number, number], + b: [number, number, number, number] +): boolean { + return !gt(a, b) +} + +export function gt( + a: [number, number, number, number], + b: [number, number, number, number] +): boolean { + for (let index = 0; index < 4; index++) { + if (a[index] > b[index]) { + return true + } else if (a[index] < b[index]) { + return false + } + } + return false +} + +export function gte( + a: [number, number, number, number], + b: [number, number, number, number] +): boolean { + return !lt(a, b) +} + +export function eq( + a: [number, number, number, number], + b: [number, number, number, number] +): boolean { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] +}