Support installation specified of Python version for Linux

This commit is contained in:
SerVB 2020-04-14 19:14:52 +03:00
parent 4ff1108c39
commit d3f7e79d54
3 changed files with 336 additions and 212 deletions

495
dist/index.js vendored
View file

@ -1250,6 +1250,73 @@ module.exports = SemVer
module.exports = require("os"); module.exports = require("os");
/***/ }),
/***/ 98:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
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());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const exec = __importStar(__webpack_require__(986));
function getVariable(variableName) {
return __awaiter(this, void 0, void 0, function* () {
let variableValue = '';
const options = {
listeners: {
stdout: (data) => {
variableValue += data.toString();
}
}
};
yield exec.exec('bash', ['-c', `echo $${variableName}`], options);
return variableValue.trim();
});
}
function downloadLinuxCpython(version) {
return __awaiter(this, void 0, void 0, function* () {
const home = yield getVariable('HOME');
yield exec.exec('bash', [
'-c',
`
set -e # Any command which returns non-zero exit code will cause this shell script to exit immediately
set -x # Activate debugging to show execution details: all commands will be printed before execution
sudo apt-get install build-essential checkinstall
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
cd $HOME
wget https://www.python.org/ftp/python/${version}/Python-${version}.tgz
tar -xvf Python-${version}.tgz
cd Python-${version}
./configure
make
sudo checkinstall -y
`
]);
return `${home}/Python-${version}`;
});
}
exports.downloadLinuxCpython = downloadLinuxCpython;
/***/ }), /***/ }),
/***/ 120: /***/ 120:
@ -2235,45 +2302,45 @@ const Range = __webpack_require__(124)
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 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 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); } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod; result["default"] = mod;
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(470)); const core = __importStar(__webpack_require__(470));
const finder = __importStar(__webpack_require__(927)); const finder = __importStar(__webpack_require__(927));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
let version = core.getInput('python-version'); let version = core.getInput('python-version');
if (version) { if (version) {
const arch = core.getInput('architecture', { required: true }); const arch = core.getInput('architecture', { required: true });
const installed = yield finder.findPythonVersion(version, arch); const installed = yield finder.findPythonVersion(version, arch);
core.info(`Successfully setup ${installed.impl} (${installed.version})`); core.info(`Successfully setup ${installed.impl} (${installed.version})`);
} }
const matchersPath = path.join(__dirname, '..', '.github'); const matchersPath = path.join(__dirname, '..', '.github');
core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`);
} }
catch (err) { catch (err) {
core.setFailed(err.message); core.setFailed(err.message);
} }
}); });
} }
run(); run();
/***/ }), /***/ }),
@ -6258,178 +6325,184 @@ module.exports = lte
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 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 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); } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod; result["default"] = mod;
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
const semver = __importStar(__webpack_require__(876)); const semver = __importStar(__webpack_require__(876));
let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || ''; const downloader = __importStar(__webpack_require__(98));
if (!cacheDirectory) { let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || '';
let baseLocation; if (!cacheDirectory) {
if (process.platform === 'win32') { let baseLocation;
// On windows use the USERPROFILE env variable if (process.platform === 'win32') {
baseLocation = process.env['USERPROFILE'] || 'C:\\'; // On windows use the USERPROFILE env variable
} baseLocation = process.env['USERPROFILE'] || 'C:\\';
else { }
if (process.platform === 'darwin') { else {
baseLocation = '/Users'; if (process.platform === 'darwin') {
} baseLocation = '/Users';
else { }
baseLocation = '/home'; else {
} baseLocation = '/home';
} }
cacheDirectory = path.join(baseLocation, 'actions', 'cache'); }
} cacheDirectory = path.join(baseLocation, 'actions', 'cache');
const core = __importStar(__webpack_require__(470)); }
const tc = __importStar(__webpack_require__(533)); const core = __importStar(__webpack_require__(470));
const IS_WINDOWS = process.platform === 'win32'; const tc = __importStar(__webpack_require__(533));
// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. const IS_WINDOWS = process.platform === 'win32';
// This is where pip is, along with anything that pip installs. const IS_LINUX = process.platform === 'linux';
// There is a seperate directory for `pip install --user`. // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
// // This is where pip is, along with anything that pip installs.
// For reference, these directories are as follows: // There is a seperate directory for `pip install --user`.
// macOS / Linux: //
// <sys.prefix>/bin (by default /usr/local/bin, but not on hosted agents -- see the `else`) // For reference, these directories are as follows:
// (--user) ~/.local/bin // macOS / Linux:
// Windows: // <sys.prefix>/bin (by default /usr/local/bin, but not on hosted agents -- see the `else`)
// <Python installation dir>\Scripts // (--user) ~/.local/bin
// (--user) %APPDATA%\Python\PythonXY\Scripts // Windows:
// See https://docs.python.org/3/library/sysconfig.html // <Python installation dir>\Scripts
function binDir(installDir) { // (--user) %APPDATA%\Python\PythonXY\Scripts
if (IS_WINDOWS) { // See https://docs.python.org/3/library/sysconfig.html
return path.join(installDir, 'Scripts'); function binDir(installDir) {
} if (IS_WINDOWS) {
else { return path.join(installDir, 'Scripts');
return path.join(installDir, 'bin'); }
} else {
} return path.join(installDir, 'bin');
// Note on the tool cache layout for PyPy: }
// PyPy has its own versioning scheme that doesn't follow the Python versioning scheme. }
// A particular version of PyPy may contain one or more versions of the Python interpreter. // Note on the tool cache layout for PyPy:
// For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. // PyPy has its own versioning scheme that doesn't follow the Python versioning scheme.
// We only care about the Python version, so we don't use the PyPy version for the tool cache. // A particular version of PyPy may contain one or more versions of the Python interpreter.
function usePyPy(majorVersion, architecture) { // For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha.
const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion.toString()); // We only care about the Python version, so we don't use the PyPy version for the tool cache.
let installDir = findPyPy(architecture); function usePyPy(majorVersion, architecture) {
if (!installDir && IS_WINDOWS) { const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion.toString());
// PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. let installDir = findPyPy(architecture);
// On our Windows virtual environments, we only install an x86 version. if (!installDir && IS_WINDOWS) {
// Fall back to x86. // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64.
installDir = findPyPy('x86'); // On our Windows virtual environments, we only install an x86 version.
} // Fall back to x86.
if (!installDir) { installDir = findPyPy('x86');
// PyPy not installed in $(Agent.ToolsDirectory) }
throw new Error(`PyPy ${majorVersion} not found`); if (!installDir) {
} // PyPy not installed in $(Agent.ToolsDirectory)
// For PyPy, Windows uses 'bin', not 'Scripts'. throw new Error(`PyPy ${majorVersion} not found`);
const _binDir = path.join(installDir, 'bin'); }
// On Linux and macOS, the Python interpreter is in 'bin'. // For PyPy, Windows uses 'bin', not 'Scripts'.
// On Windows, it is in the installation root. const _binDir = path.join(installDir, 'bin');
const pythonLocation = IS_WINDOWS ? installDir : _binDir; // On Linux and macOS, the Python interpreter is in 'bin'.
core.exportVariable('pythonLocation', pythonLocation); // On Windows, it is in the installation root.
core.addPath(installDir); const pythonLocation = IS_WINDOWS ? installDir : _binDir;
core.addPath(_binDir); core.exportVariable('pythonLocation', pythonLocation);
const impl = 'pypy' + majorVersion.toString(); core.addPath(installDir);
core.setOutput('python-version', impl); core.addPath(_binDir);
return { impl: impl, version: versionFromPath(installDir) }; const impl = 'pypy' + majorVersion.toString();
} core.setOutput('python-version', impl);
function useCpythonVersion(version, architecture) { return { impl: impl, version: versionFromPath(installDir) };
return __awaiter(this, void 0, void 0, function* () { }
const desugaredVersionSpec = desugarDevVersion(version); function useCpythonVersion(version, architecture) {
const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec); return __awaiter(this, void 0, void 0, function* () {
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); const desugaredVersionSpec = desugarDevVersion(version);
const installDir = tc.find('Python', semanticVersionSpec, architecture); const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
if (!installDir) { core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
// Fail and list available versions let installDir = tc.find('Python', semanticVersionSpec, architecture);
const x86Versions = tc if (!installDir && IS_LINUX) {
.findAllVersions('Python', 'x86') core.info(`Can't find installed CPython ${version}; trying to download`);
.map(s => `${s} (x86)`) installDir = yield downloader.downloadLinuxCpython(version);
.join(os.EOL); }
const x64Versions = tc if (!installDir) {
.findAllVersions('Python', 'x64') // Fail and list available versions
.map(s => `${s} (x64)`) const x86Versions = tc
.join(os.EOL); .findAllVersions('Python', 'x86')
throw new Error([ .map(s => `${s} (x86)`)
`Version ${version} with arch ${architecture} not found`, .join(os.EOL);
'Available versions:', const x64Versions = tc
x86Versions, .findAllVersions('Python', 'x64')
x64Versions .map(s => `${s} (x64)`)
].join(os.EOL)); .join(os.EOL);
} throw new Error([
core.exportVariable('pythonLocation', installDir); `Version ${version} with arch ${architecture} not found`,
core.addPath(installDir); 'Available versions:',
core.addPath(binDir(installDir)); x86Versions,
if (IS_WINDOWS) { x64Versions
// Add --user directory ].join(os.EOL));
// `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/ }
// So if `findLocalTool` succeeded above, we must have a conformant `installDir` core.exportVariable('pythonLocation', installDir);
const version = path.basename(path.dirname(installDir)); core.addPath(installDir);
const major = semver.major(version); core.addPath(binDir(installDir));
const minor = semver.minor(version); if (IS_WINDOWS) {
const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts'); // Add --user directory
core.addPath(userScriptsDir); // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/
} // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed. const version = path.basename(path.dirname(installDir));
const installed = versionFromPath(installDir); const major = semver.major(version);
core.setOutput('python-version', installed); const minor = semver.minor(version);
return { impl: 'CPython', version: installed }; const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
}); core.addPath(userScriptsDir);
} }
/** Convert versions like `3.8-dev` to a version like `>= 3.8.0-a0`. */ // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
function desugarDevVersion(versionSpec) { const installed = versionFromPath(installDir);
if (versionSpec.endsWith('-dev')) { core.setOutput('python-version', installed);
const versionRoot = versionSpec.slice(0, -'-dev'.length); return { impl: 'CPython', version: installed };
return `>= ${versionRoot}.0-a0`; });
} }
else { /** Convert versions like `3.8-dev` to a version like `>= 3.8.0-a0`. */
return versionSpec; function desugarDevVersion(versionSpec) {
} if (versionSpec.endsWith('-dev')) {
} const versionRoot = versionSpec.slice(0, -'-dev'.length);
/** Extracts python version from install path from hosted tool cache as described in README.md */ return `>= ${versionRoot}.0-a0`;
function versionFromPath(installDir) { }
const parts = installDir.split(path.sep); else {
const idx = parts.findIndex(part => part === 'PyPy' || part === 'Python'); return versionSpec;
return parts[idx + 1] || ''; }
} }
/** /** Extracts python version from install path from hosted tool cache as described in README.md */
* Python's prelease versions look like `3.7.0b2`. function versionFromPath(installDir) {
* This is the one part of Python versioning that does not look like semantic versioning, which specifies `3.7.0-b2`. const parts = installDir.split(path.sep);
* If the version spec contains prerelease versions, we need to convert them to the semantic version equivalent. const idx = parts.findIndex(part => part === 'PyPy' || part === 'Python');
*/ return parts[idx + 1] || '';
function pythonVersionToSemantic(versionSpec) { }
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc)\d*)/g; /**
return versionSpec.replace(prereleaseVersion, '$1-$2'); * Python's prelease versions look like `3.7.0b2`.
} * This is the one part of Python versioning that does not look like semantic versioning, which specifies `3.7.0-b2`.
exports.pythonVersionToSemantic = pythonVersionToSemantic; * If the version spec contains prerelease versions, we need to convert them to the semantic version equivalent.
function findPythonVersion(version, architecture) { */
return __awaiter(this, void 0, void 0, function* () { function pythonVersionToSemantic(versionSpec) {
switch (version.toUpperCase()) { const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc)\d*)/g;
case 'PYPY2': return versionSpec.replace(prereleaseVersion, '$1-$2');
return usePyPy(2, architecture); }
case 'PYPY3': exports.pythonVersionToSemantic = pythonVersionToSemantic;
return usePyPy(3, architecture); function findPythonVersion(version, architecture) {
default: return __awaiter(this, void 0, void 0, function* () {
return yield useCpythonVersion(version, architecture); switch (version.toUpperCase()) {
} case 'PYPY2':
}); return usePyPy(2, architecture);
} case 'PYPY3':
exports.findPythonVersion = findPythonVersion; return usePyPy(3, architecture);
default:
return yield useCpythonVersion(version, architecture);
}
});
}
exports.findPythonVersion = findPythonVersion;
/***/ }), /***/ }),

43
src/download-python.ts Normal file
View file

@ -0,0 +1,43 @@
import * as exec from '@actions/exec';
async function getVariable(variableName: string): Promise<string> {
let variableValue = '';
const options = {
listeners: {
stdout: (data: Buffer) => {
variableValue += data.toString();
}
}
};
await exec.exec('bash', ['-c', `echo $${variableName}`], options);
return variableValue.trim();
}
export async function downloadLinuxCpython(version: string): Promise<string> {
const home = await getVariable('HOME');
await exec.exec('bash', [
'-c',
`
set -e # Any command which returns non-zero exit code will cause this shell script to exit immediately
set -x # Activate debugging to show execution details: all commands will be printed before execution
sudo apt-get install build-essential checkinstall
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
cd $HOME
wget https://www.python.org/ftp/python/${version}/Python-${version}.tgz
tar -xvf Python-${version}.tgz
cd Python-${version}
./configure
make
sudo checkinstall -y
`
]);
return `${home}/Python-${version}`;
}

View file

@ -3,6 +3,8 @@ import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
import * as downloader from './download-python';
let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || ''; let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || '';
if (!cacheDirectory) { if (!cacheDirectory) {
@ -24,6 +26,7 @@ import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
const IS_LINUX = process.platform === 'linux';
// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
// This is where pip is, along with anything that pip installs. // This is where pip is, along with anything that pip installs.
@ -92,11 +95,16 @@ async function useCpythonVersion(
const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec); const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`); core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
const installDir: string | null = tc.find( let installDir: string | null = tc.find(
'Python', 'Python',
semanticVersionSpec, semanticVersionSpec,
architecture architecture
); );
if (!installDir && IS_LINUX) {
core.info(`Can't find installed CPython ${version}; trying to download`);
installDir = await downloader.downloadLinuxCpython(version);
}
if (!installDir) { if (!installDir) {
// Fail and list available versions // Fail and list available versions
const x86Versions = tc const x86Versions = tc