|
| 1 | +"use strict"; |
| 2 | +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
| 3 | + return new (P || (P = Promise))(function (resolve, reject) { |
| 4 | + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
| 5 | + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
| 6 | + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } |
| 7 | + step((generator = generator.apply(thisArg, _arguments || [])).next()); |
| 8 | + }); |
| 9 | +}; |
| 10 | +var __importStar = (this && this.__importStar) || function (mod) { |
| 11 | + if (mod && mod.__esModule) return mod; |
| 12 | + var result = {}; |
| 13 | + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; |
| 14 | + result["default"] = mod; |
| 15 | + return result; |
| 16 | +}; |
| 17 | +Object.defineProperty(exports, "__esModule", { value: true }); |
| 18 | +const os = __importStar(require("os")); |
| 19 | +const path = __importStar(require("path")); |
| 20 | +const semver = __importStar(require("semver")); |
| 21 | +let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || ''; |
| 22 | +if (!cacheDirectory) { |
| 23 | + let baseLocation; |
| 24 | + if (process.platform === 'win32') { |
| 25 | + // On windows use the USERPROFILE env variable |
| 26 | + baseLocation = process.env['USERPROFILE'] || 'C:\\'; |
| 27 | + } |
| 28 | + else { |
| 29 | + if (process.platform === 'darwin') { |
| 30 | + baseLocation = '/Users'; |
| 31 | + } |
| 32 | + else { |
| 33 | + baseLocation = '/home'; |
| 34 | + } |
| 35 | + } |
| 36 | + cacheDirectory = path.join(baseLocation, 'actions', 'cache'); |
| 37 | +} |
| 38 | +const core = __importStar(require("@actions/core")); |
| 39 | +const tc = __importStar(require("@actions/tool-cache")); |
| 40 | +const IS_WINDOWS = process.platform === 'win32'; |
| 41 | +// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. |
| 42 | +// This is where pip is, along with anything that pip installs. |
| 43 | +// There is a seperate directory for `pip install --user`. |
| 44 | +// |
| 45 | +// For reference, these directories are as follows: |
| 46 | +// macOS / Linux: |
| 47 | +// <sys.prefix>/bin (by default /usr/local/bin, but not on hosted agents -- see the `else`) |
| 48 | +// (--user) ~/.local/bin |
| 49 | +// Windows: |
| 50 | +// <Python installation dir>\Scripts |
| 51 | +// (--user) %APPDATA%\Python\PythonXY\Scripts |
| 52 | +// See https://docs.python.org/3/library/sysconfig.html |
| 53 | +function binDir(installDir) { |
| 54 | + if (IS_WINDOWS) { |
| 55 | + return path.join(installDir, 'Scripts'); |
| 56 | + } |
| 57 | + else { |
| 58 | + return path.join(installDir, 'bin'); |
| 59 | + } |
| 60 | +} |
| 61 | +// Note on the tool cache layout for PyPy: |
| 62 | +// PyPy has its own versioning scheme that doesn't follow the Python versioning scheme. |
| 63 | +// A particular version of PyPy may contain one or more versions of the Python interpreter. |
| 64 | +// For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. |
| 65 | +// We only care about the Python version, so we don't use the PyPy version for the tool cache. |
| 66 | +function usePyPy(majorVersion, architecture) { |
| 67 | + const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion.toString()); |
| 68 | + let installDir = findPyPy(architecture); |
| 69 | + if (!installDir && IS_WINDOWS) { |
| 70 | + // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. |
| 71 | + // On Hosted VS2017, we only install an x86 version. |
| 72 | + // Fall back to x86. |
| 73 | + installDir = findPyPy('x86'); |
| 74 | + } |
| 75 | + if (!installDir) { |
| 76 | + // PyPy not installed in $(Agent.ToolsDirectory) |
| 77 | + throw new Error(`PyPy ${majorVersion} not found`); |
| 78 | + } |
| 79 | + // For PyPy, Windows uses 'bin', not 'Scripts'. |
| 80 | + const _binDir = path.join(installDir, 'bin'); |
| 81 | + // On Linux and macOS, the Python interpreter is in 'bin'. |
| 82 | + // On Windows, it is in the installation root. |
| 83 | + const pythonLocation = IS_WINDOWS ? installDir : _binDir; |
| 84 | + core.exportVariable('pythonLocation', pythonLocation); |
| 85 | + core.addPath(installDir); |
| 86 | + core.addPath(_binDir); |
| 87 | +} |
| 88 | +function useCpythonVersion(version, architecture) { |
| 89 | + return __awaiter(this, void 0, void 0, function* () { |
| 90 | + const desugaredVersionSpec = desugarDevVersion(version); |
| 91 | + const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec); |
| 92 | + core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); |
| 93 | + const installDir = tc.find('Python', semanticVersionSpec, architecture); |
| 94 | + if (!installDir) { |
| 95 | + // Fail and list available versions |
| 96 | + const x86Versions = tc |
| 97 | + .findAllVersions('Python', 'x86') |
| 98 | + .map(s => `${s} (x86)`) |
| 99 | + .join(os.EOL); |
| 100 | + const x64Versions = tc |
| 101 | + .findAllVersions('Python', 'x64') |
| 102 | + .map(s => `${s} (x64)`) |
| 103 | + .join(os.EOL); |
| 104 | + throw new Error([ |
| 105 | + `Version ${version} with arch ${architecture} not found`, |
| 106 | + 'Available versions:', |
| 107 | + x86Versions, |
| 108 | + x64Versions |
| 109 | + ].join(os.EOL)); |
| 110 | + } |
| 111 | + core.exportVariable('pythonLocation', installDir); |
| 112 | + core.addPath(installDir); |
| 113 | + core.addPath(binDir(installDir)); |
| 114 | + if (IS_WINDOWS) { |
| 115 | + // Add --user directory |
| 116 | + // `installDir` from tool cache should look like $AGENT_TOOLSDIRECTORY/Python/<semantic version>/x64/ |
| 117 | + // So if `findLocalTool` succeeded above, we must have a conformant `installDir` |
| 118 | + const version = path.basename(path.dirname(installDir)); |
| 119 | + const major = semver.major(version); |
| 120 | + const minor = semver.minor(version); |
| 121 | + const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts'); |
| 122 | + core.addPath(userScriptsDir); |
| 123 | + } |
| 124 | + // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. |
| 125 | + }); |
| 126 | +} |
| 127 | +/** Convert versions like `3.8-dev` to a version like `>= 3.8.0-a0`. */ |
| 128 | +function desugarDevVersion(versionSpec) { |
| 129 | + if (versionSpec.endsWith('-dev')) { |
| 130 | + const versionRoot = versionSpec.slice(0, -'-dev'.length); |
| 131 | + return `>= ${versionRoot}.0-a0`; |
| 132 | + } |
| 133 | + else { |
| 134 | + return versionSpec; |
| 135 | + } |
| 136 | +} |
| 137 | +/** |
| 138 | + * Python's prelease versions look like `3.7.0b2`. |
| 139 | + * This is the one part of Python versioning that does not look like semantic versioning, which specifies `3.7.0-b2`. |
| 140 | + * If the version spec contains prerelease versions, we need to convert them to the semantic version equivalent. |
| 141 | + */ |
| 142 | +function pythonVersionToSemantic(versionSpec) { |
| 143 | + const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc)\d*)/g; |
| 144 | + return versionSpec.replace(prereleaseVersion, '$1-$2'); |
| 145 | +} |
| 146 | +exports.pythonVersionToSemantic = pythonVersionToSemantic; |
| 147 | +function findPythonVersion(version, architecture) { |
| 148 | + return __awaiter(this, void 0, void 0, function* () { |
| 149 | + switch (version.toUpperCase()) { |
| 150 | + case 'PYPY2': |
| 151 | + return usePyPy(2, architecture); |
| 152 | + case 'PYPY3': |
| 153 | + return usePyPy(3, architecture); |
| 154 | + default: |
| 155 | + return yield useCpythonVersion(version, architecture); |
| 156 | + } |
| 157 | + }); |
| 158 | +} |
| 159 | +exports.findPythonVersion = findPythonVersion; |
0 commit comments