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_TEMP'] = tempDir;
|
||||
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as finder from '../src/find-python';
|
||||
import * as installer from '../src/install-python';
|
||||
|
||||
const pythonRelease = require('./data/python-release.json');
|
||||
|
||||
describe('Finder tests', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Finds Python if it is installed', async () => {
|
||||
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
|
||||
await io.mkdirP(pythonDir);
|
||||
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');
|
||||
});
|
||||
|
||||
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 () => {
|
||||
// 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;
|
||||
try {
|
||||
await finder.findPythonVersion('3.300000', 'x64');
|
||||
|
@ -9,6 +9,11 @@ inputs:
|
||||
architecture:
|
||||
description: 'The target architecture (x86, x64) of the Python interpreter.'
|
||||
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:
|
||||
python-version:
|
||||
description: "The installed python version. Useful when given a version range as input."
|
||||
|
7417
dist/index.js
vendored
7417
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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.6.tgz",
|
||||
"integrity": "sha512-LGmio4w98UyGX33b/W6V6Nx/sQHRXZ859YlMkn36wPsXPB82u8xTVlA/Dq2DXrm6lEq9RVmisRJa1c+HETAIJA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
@ -34,14 +34,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"@actions/tool-cache": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.3.tgz",
|
||||
"integrity": "sha512-AFVyTLcIxusDVI1gMhbZW8m/On7YNJG+xYaxorV+qic+f7lO7h37aT2mfzxqAq7mwHxtP1YlVFNrXe9QDf/bPg==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.5.2.tgz",
|
||||
"integrity": "sha512-40A1St0GEo+QvHV1YRjStEoQcKKMaip+zNXPgGHcjYXCdZ7cl1LGlwOpsVVqwk+6ue/shFTS76KC1A02mVVCQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@actions/core": "^1.2.0",
|
||||
"@actions/exec": "^1.0.0",
|
||||
"@actions/http-client": "^1.0.3",
|
||||
"@actions/http-client": "^1.0.8",
|
||||
"@actions/io": "^1.0.1",
|
||||
"semver": "^6.1.0",
|
||||
"uuid": "^3.3.2"
|
||||
|
@ -28,7 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/io": "^1.0.2",
|
||||
"@actions/tool-cache": "^1.3.3",
|
||||
"@actions/tool-cache": "^1.5.2",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^12.12.31",
|
||||
"@types/semver": "^7.1.0",
|
||||
|
@ -3,6 +3,8 @@ import * as path from 'path';
|
||||
|
||||
import * as semver from 'semver';
|
||||
|
||||
import * as installer from './install-python';
|
||||
|
||||
let cacheDirectory = process.env['RUNNER_TOOLSDIRECTORY'] || '';
|
||||
|
||||
if (!cacheDirectory) {
|
||||
@ -92,29 +94,31 @@ async function useCpythonVersion(
|
||||
const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
|
||||
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
|
||||
|
||||
const installDir: string | null = tc.find(
|
||||
let installDir: string | null = tc.find(
|
||||
'Python',
|
||||
semanticVersionSpec,
|
||||
architecture
|
||||
);
|
||||
if (!installDir) {
|
||||
// Fail and list available versions
|
||||
const x86Versions = tc
|
||||
.findAllVersions('Python', 'x86')
|
||||
.map(s => `${s} (x86)`)
|
||||
.join(os.EOL);
|
||||
core.info(`Version ${semanticVersionSpec} is not found in local cache`);
|
||||
const foundRelease = await installer.findReleaseFromManifest(
|
||||
semanticVersionSpec,
|
||||
architecture
|
||||
);
|
||||
|
||||
const x64Versions = tc
|
||||
.findAllVersions('Python', 'x64')
|
||||
.map(s => `${s} (x64)`)
|
||||
.join(os.EOL);
|
||||
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
|
||||
core.info(`Version ${semanticVersionSpec} is available for downloading`);
|
||||
await installer.installCpythonFromRelease(foundRelease);
|
||||
|
||||
installDir = tc.find('Python', semanticVersionSpec, architecture);
|
||||
}
|
||||
}
|
||||
|
||||
if (!installDir) {
|
||||
throw new Error(
|
||||
[
|
||||
`Version ${version} with arch ${architecture} not found`,
|
||||
'Available versions:',
|
||||
x86Versions,
|
||||
x64Versions
|
||||
`The list of all available versions can be found here: ${installer.MANIFEST_URL}`
|
||||
].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