mirror of
https://github.com/actions/setup-python.git
synced 2025-04-19 19:33:29 +00:00
Add ability to download specific version of Python from releases
This commit is contained in:
parent
985150d1f6
commit
81b3a118a7
57
.github/workflows/test.yml
vendored
Normal file
57
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
name: Validate 'setup-python'
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: 0 0 * * *
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
default-version:
|
||||||
|
name: Setup default version
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: setup default python
|
||||||
|
uses: ./
|
||||||
|
|
||||||
|
- name: Validate version
|
||||||
|
run: python --version
|
||||||
|
|
||||||
|
- name: Run simple python code
|
||||||
|
run: python -c 'import math; print(math.factorial(5))'
|
||||||
|
|
||||||
|
setup-versions-from-manifest:
|
||||||
|
name: Setup ${{ matrix.python }} ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04]
|
||||||
|
python: [3.5.3, 3.6.7, 3.7.5, 3.8.1]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: setup-python ${{ matrix.python }}
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
|
||||||
|
- name: Validate version
|
||||||
|
run: |
|
||||||
|
$pythonVersion = (python --version)
|
||||||
|
if ("Python ${{ matrix.python }}" -ne "$pythonVersion"){
|
||||||
|
Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$pythonVersion
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Run simple code
|
||||||
|
run: python -c 'import math; print(math.factorial(5))'
|
13
__tests__/data/python-release.json
Normal file
13
__tests__/data/python-release.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "1.2.3",
|
||||||
|
"stable": true,
|
||||||
|
"release_url": "https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"filename": "sometool-1.2.3-linux-x64.tar.gz",
|
||||||
|
"arch": "x64",
|
||||||
|
"platform": "linux",
|
||||||
|
"download_url": "https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6/sometool-1.2.3-linux-x64.tar.gz"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -18,19 +18,48 @@ const tempDir = path.join(
|
|||||||
process.env['RUNNER_TOOL_CACHE'] = toolDir;
|
process.env['RUNNER_TOOL_CACHE'] = toolDir;
|
||||||
process.env['RUNNER_TEMP'] = tempDir;
|
process.env['RUNNER_TEMP'] = tempDir;
|
||||||
|
|
||||||
|
import * as tc from '@actions/tool-cache';
|
||||||
import * as finder from '../src/find-python';
|
import * as finder from '../src/find-python';
|
||||||
|
import * as installer from '../src/install-python';
|
||||||
|
|
||||||
|
const pythonRelease = require('./data/python-release.json');
|
||||||
|
|
||||||
describe('Finder tests', () => {
|
describe('Finder tests', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('Finds Python if it is installed', async () => {
|
it('Finds Python if it is installed', async () => {
|
||||||
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
|
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
|
||||||
await io.mkdirP(pythonDir);
|
await io.mkdirP(pythonDir);
|
||||||
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
|
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
|
||||||
// This will throw if it doesn't find it in the cache (because no such version exists)
|
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
|
||||||
await finder.findPythonVersion('3.x', 'x64');
|
await finder.findPythonVersion('3.x', 'x64');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Finds Python if it is not installed, but exists in the manifest', async () => {
|
||||||
|
const findSpy: jest.SpyInstance = jest.spyOn(
|
||||||
|
installer,
|
||||||
|
'findReleaseFromManifest'
|
||||||
|
);
|
||||||
|
findSpy.mockImplementation(() => <tc.IToolRelease>pythonRelease);
|
||||||
|
|
||||||
|
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||||
|
installer,
|
||||||
|
'installCpythonFromRelease'
|
||||||
|
);
|
||||||
|
installSpy.mockImplementation(async () => {
|
||||||
|
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
|
||||||
|
await io.mkdirP(pythonDir);
|
||||||
|
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
|
||||||
|
});
|
||||||
|
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
|
||||||
|
await finder.findPythonVersion('1.2.3', 'x64');
|
||||||
|
});
|
||||||
|
|
||||||
it('Errors if Python is not installed', async () => {
|
it('Errors if Python is not installed', async () => {
|
||||||
// This will throw if it doesn't find it in the cache (because no such version exists)
|
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
|
||||||
let thrown = false;
|
let thrown = false;
|
||||||
try {
|
try {
|
||||||
await finder.findPythonVersion('3.300000', 'x64');
|
await finder.findPythonVersion('3.300000', 'x64');
|
||||||
|
@ -9,6 +9,11 @@ inputs:
|
|||||||
architecture:
|
architecture:
|
||||||
description: 'The target architecture (x86, x64) of the Python interpreter.'
|
description: 'The target architecture (x86, x64) of the Python interpreter.'
|
||||||
default: 'x64'
|
default: 'x64'
|
||||||
|
token:
|
||||||
|
description: >
|
||||||
|
Personal access token (PAT) used to fetch the manifest file from master of repository
|
||||||
|
[Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
|
||||||
|
default: ${{ github.token }}
|
||||||
outputs:
|
outputs:
|
||||||
python-version:
|
python-version:
|
||||||
description: "The installed python version. Useful when given a version range as input."
|
description: "The installed python version. Useful when given a version range as input."
|
||||||
|
7445
dist/index.js
vendored
7445
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
14
package-lock.json
generated
14
package-lock.json
generated
@ -19,9 +19,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
|
||||||
"integrity": "sha512-LGmio4w98UyGX33b/W6V6Nx/sQHRXZ859YlMkn36wPsXPB82u8xTVlA/Dq2DXrm6lEq9RVmisRJa1c+HETAIJA==",
|
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "0.0.6"
|
||||||
@ -34,14 +34,14 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@actions/tool-cache": {
|
"@actions/tool-cache": {
|
||||||
"version": "1.3.3",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.5.2.tgz",
|
||||||
"integrity": "sha512-AFVyTLcIxusDVI1gMhbZW8m/On7YNJG+xYaxorV+qic+f7lO7h37aT2mfzxqAq7mwHxtP1YlVFNrXe9QDf/bPg==",
|
"integrity": "sha512-40A1St0GEo+QvHV1YRjStEoQcKKMaip+zNXPgGHcjYXCdZ7cl1LGlwOpsVVqwk+6ue/shFTS76KC1A02mVVCQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/core": "^1.2.0",
|
"@actions/core": "^1.2.0",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^1.0.3",
|
"@actions/http-client": "^1.0.8",
|
||||||
"@actions/io": "^1.0.1",
|
"@actions/io": "^1.0.1",
|
||||||
"semver": "^6.1.0",
|
"semver": "^6.1.0",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/io": "^1.0.2",
|
"@actions/io": "^1.0.2",
|
||||||
"@actions/tool-cache": "^1.3.3",
|
"@actions/tool-cache": "^1.5.2",
|
||||||
"@types/jest": "^25.1.4",
|
"@types/jest": "^25.1.4",
|
||||||
"@types/node": "^12.12.31",
|
"@types/node": "^12.12.31",
|
||||||
"@types/semver": "^7.1.0",
|
"@types/semver": "^7.1.0",
|
||||||
|
@ -3,6 +3,8 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
|
import * as installer from './install-python';
|
||||||
|
|
||||||
let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || '';
|
let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || '';
|
||||||
|
|
||||||
if (!cacheDirectory) {
|
if (!cacheDirectory) {
|
||||||
@ -92,29 +94,31 @@ 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) {
|
if (!installDir) {
|
||||||
// Fail and list available versions
|
core.info(`Version ${semanticVersionSpec} is not found in local cache`);
|
||||||
const x86Versions = tc
|
const foundRelease = await installer.findReleaseFromManifest(
|
||||||
.findAllVersions('Python', 'x86')
|
semanticVersionSpec,
|
||||||
.map(s => `${s} (x86)`)
|
architecture
|
||||||
.join(os.EOL);
|
);
|
||||||
|
|
||||||
const x64Versions = tc
|
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
|
||||||
.findAllVersions('Python', 'x64')
|
core.info(`Version ${semanticVersionSpec} is available for downloading`);
|
||||||
.map(s => `${s} (x64)`)
|
await installer.installCpythonFromRelease(foundRelease);
|
||||||
.join(os.EOL);
|
|
||||||
|
|
||||||
|
installDir = tc.find('Python', semanticVersionSpec, architecture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!installDir) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
[
|
[
|
||||||
`Version ${version} with arch ${architecture} not found`,
|
`Version ${version} with arch ${architecture} not found`,
|
||||||
'Available versions:',
|
`The list of all available versions can be found here: ${installer.MANIFEST_URL}`
|
||||||
x86Versions,
|
|
||||||
x64Versions
|
|
||||||
].join(os.EOL)
|
].join(os.EOL)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
64
src/install-python.ts
Normal file
64
src/install-python.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import * as tc from '@actions/tool-cache';
|
||||||
|
import * as exec from '@actions/exec';
|
||||||
|
import {ExecOptions} from '@actions/exec/lib/interfaces';
|
||||||
|
|
||||||
|
const AUTH_TOKEN = core.getInput('token');
|
||||||
|
const MANIFEST_REPO_OWNER = 'actions';
|
||||||
|
const MANIFEST_REPO_NAME = 'python-versions';
|
||||||
|
export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/master/versions-manifest.json`;
|
||||||
|
|
||||||
|
const IS_WINDOWS = process.platform === 'win32';
|
||||||
|
const IS_LINUX = process.platform === 'linux';
|
||||||
|
|
||||||
|
export async function findReleaseFromManifest(
|
||||||
|
semanticVersionSpec: string,
|
||||||
|
architecture: string
|
||||||
|
): Promise<tc.IToolRelease | undefined> {
|
||||||
|
const manifest: tc.IToolRelease[] = await tc.getManifestFromRepo(
|
||||||
|
MANIFEST_REPO_OWNER,
|
||||||
|
MANIFEST_REPO_NAME,
|
||||||
|
AUTH_TOKEN
|
||||||
|
);
|
||||||
|
return await tc.findFromManifest(
|
||||||
|
semanticVersionSpec,
|
||||||
|
true,
|
||||||
|
manifest,
|
||||||
|
architecture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function installCpythonFromRelease(release: tc.IToolRelease) {
|
||||||
|
const downloadUrl = release.files[0].download_url;
|
||||||
|
|
||||||
|
core.info(`Download from "${downloadUrl}"`);
|
||||||
|
const pythonPath = await tc.downloadTool(downloadUrl, undefined, AUTH_TOKEN);
|
||||||
|
const fileName = path.basename(pythonPath, '.zip');
|
||||||
|
core.info('Extract downloaded archive');
|
||||||
|
let pythonExtractedFolder;
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
pythonExtractedFolder = await tc.extractZip(pythonPath, `./${fileName}`);
|
||||||
|
} else {
|
||||||
|
pythonExtractedFolder = await tc.extractTar(pythonPath, `./${fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: ExecOptions = {
|
||||||
|
cwd: pythonExtractedFolder,
|
||||||
|
silent: true,
|
||||||
|
listeners: {
|
||||||
|
stdout: (data: Buffer) => {
|
||||||
|
core.debug(data.toString().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
core.info('Execute installation script');
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
await exec.exec('powershell', ['./setup.ps1'], options);
|
||||||
|
} else if (IS_LINUX) {
|
||||||
|
await exec.exec('sudo', ['bash', './setup.sh'], options);
|
||||||
|
} else {
|
||||||
|
await exec.exec('bash', ['./setup.sh'], options);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user