From 5ccb29d8773c3f3f653e1705f474dfaa8a06a912 Mon Sep 17 00:00:00 2001 From: Dmitry Shibanov Date: Thu, 22 Dec 2022 13:02:09 +0100 Subject: [PATCH] Install multiple python versions (#567) --- .github/workflows/test-pypy.yml | 42 ++++++++++++++++++ .github/workflows/test-python.yml | 31 +++++++++++++- dist/setup/index.js | 46 +++++++++++--------- docs/advanced-usage.md | 57 +++++++++++++++++++++++++ src/setup-python.ts | 71 ++++++++++++++++--------------- 5 files changed, 190 insertions(+), 57 deletions(-) diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml index 692cda664..5340c12f3 100644 --- a/.github/workflows/test-pypy.yml +++ b/.github/workflows/test-pypy.yml @@ -124,4 +124,46 @@ jobs: EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe ${EXECUTABLE} --version + shell: bash + + setup-pypy-multiple-versions: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v3 + - name: Setup PyPy and check latest + uses: ./ + with: + python-version: | + pypy-3.7-v7.3.x + pypy3.8 + check-latest: true + - name: PyPy and Python version + run: python --version + + - name: Run simple code + run: python -c 'import math; print(math.factorial(5))' + + - name: Assert PyPy is running + run: | + import platform + assert platform.python_implementation().lower() == "pypy" + shell: python + + - name: Assert expected binaries (or symlinks) are present + run: | + EXECUTABLE="pypy-3.7-v7.3.x" + EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name + EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe + ${EXECUTABLE} --version + shell: bash + - name: Assert expected binaries (or symlinks) are present + run: | + EXECUTABLE='pypy3.8' + EXECUTABLE=${EXECUTABLE/pypy-/pypy} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name + EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe + ${EXECUTABLE} --version shell: bash \ No newline at end of file diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 37a17241b..4f1ffd7f2 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -191,8 +191,35 @@ jobs: - name: Validate version run: | $pythonVersion = (python --version) - if ("$pythonVersion" -NotMatch "${{ matrix.python }}"){ - Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}" + if ("$pythonVersion" -NotMatch "${{ matrix.python-version }}"){ + Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python-version }}" + exit 1 + } + $pythonVersion + shell: pwsh + + setup-python-multiple-python-versions: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v3 + - name: Setup Python and check latest + uses: ./ + with: + python-version: | + 3.7 + 3.8 + 3.9 + 3.10 + check-latest: true + - name: Validate version + run: | + $pythonVersion = (python --version) + if ("$pythonVersion" -NotMatch "3.10"){ + Write-Host "The current version is $pythonVersion; expected version is 3.10" exit 1 } $pythonVersion diff --git a/dist/setup/index.js b/dist/setup/index.js index 2d155710c..7d1b84946 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -66867,31 +66867,31 @@ function cacheDependencies(cache, pythonVersion) { }); } function resolveVersionInput() { - let version = core.getInput('python-version'); + let versions = core.getMultilineInput('python-version'); let versionFile = core.getInput('python-version-file'); - if (version && versionFile) { + if (versions.length && versionFile) { core.warning('Both python-version and python-version-file inputs are specified, only python-version will be used.'); } - if (version) { - return version; + if (versions.length) { + return versions; } if (versionFile) { if (!fs_1.default.existsSync(versionFile)) { throw new Error(`The specified python version file at: ${versionFile} doesn't exist.`); } - version = fs_1.default.readFileSync(versionFile, 'utf8'); + const version = fs_1.default.readFileSync(versionFile, 'utf8'); core.info(`Resolved ${versionFile} as ${version}`); - return version; + return [version]; } utils_1.logWarning("Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file."); versionFile = '.python-version'; if (fs_1.default.existsSync(versionFile)) { - version = fs_1.default.readFileSync(versionFile, 'utf8'); + const version = fs_1.default.readFileSync(versionFile, 'utf8'); core.info(`Resolved ${versionFile} as ${version}`); - return version; + return [version]; } utils_1.logWarning(`${versionFile} doesn't exist.`); - return version; + return versions; } function run() { var _a; @@ -66904,22 +66904,26 @@ function run() { } core.debug(`Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}`); try { - const version = resolveVersionInput(); + const versions = resolveVersionInput(); const checkLatest = core.getBooleanInput('check-latest'); - if (version) { - let pythonVersion; + if (versions.length) { + let pythonVersion = ''; const arch = core.getInput('architecture') || os.arch(); const updateEnvironment = core.getBooleanInput('update-environment'); - if (isPyPyVersion(version)) { - const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment, checkLatest); - pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; - core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`); - } - else { - const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest); - pythonVersion = installed.version; - core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); + core.startGroup('Installed versions'); + for (const version of versions) { + if (isPyPyVersion(version)) { + const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment, checkLatest); + pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; + core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`); + } + else { + const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest); + pythonVersion = installed.version; + core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); + } } + core.endGroup(); const cache = core.getInput('cache'); if (cache && utils_1.isCacheFeatureAvailable()) { yield cacheDependencies(cache, pythonVersion); diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 643e73dd6..fa022d930 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -2,6 +2,7 @@ - [Using the python-version input](advanced-usage.md#using-the-python-version-input) - [Specifying a Python version](advanced-usage.md#specifying-a-python-version) - [Specifying a PyPy version](advanced-usage.md#specifying-a-pypy-version) + - [Specifying multiple Python and PyPy versions](advanced-usage.md#specifying-multiple-python/pypy-version) - [Matrix Testing](advanced-usage.md#matrix-testing) - [Using the python-version-file input](advanced-usage.md#using-the-python-version-file-input) - [Check latest version](advanced-usage.md#check-latest-version) @@ -132,6 +133,62 @@ jobs: ``` More details on PyPy syntax can be found in the [Available versions of PyPy](#pypy) section. +### Specifying multiple Python/PyPy version +The python-version input can get multiple python/pypy versions. The last specified version will be used as a default one. + +Download and set up multiple Python versions: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: | + 3.8 + 3.9 + 3.10 + - run: python my_script.py +``` + +Download and set up multiple PyPy versions: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: | + pypy-3.7-v7.3.x + pypy3.9-nightly + pypy3.8 + - run: python my_script.py +``` + +Download and set up multiple Python/PyPy versions: + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: | + 3.8 + 3.9 + pypy3.9-nightly + pypy3.8 + 3.10 + - run: python my_script.py +``` + ### Matrix Testing Using `setup-python` it's possible to use [matrix syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) to install several versions of Python or PyPy: diff --git a/src/setup-python.ts b/src/setup-python.ts index d6e6bdaf0..0089b4016 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -22,18 +22,18 @@ async function cacheDependencies(cache: string, pythonVersion: string) { await cacheDistributor.restoreCache(); } -function resolveVersionInput(): string { - let version = core.getInput('python-version'); +function resolveVersionInput() { + let versions = core.getMultilineInput('python-version'); let versionFile = core.getInput('python-version-file'); - if (version && versionFile) { + if (versions.length && versionFile) { core.warning( 'Both python-version and python-version-file inputs are specified, only python-version will be used.' ); } - if (version) { - return version; + if (versions.length) { + return versions; } if (versionFile) { @@ -42,9 +42,9 @@ function resolveVersionInput(): string { `The specified python version file at: ${versionFile} doesn't exist.` ); } - version = fs.readFileSync(versionFile, 'utf8'); + const version = fs.readFileSync(versionFile, 'utf8'); core.info(`Resolved ${versionFile} as ${version}`); - return version; + return [version]; } logWarning( @@ -52,14 +52,14 @@ function resolveVersionInput(): string { ); versionFile = '.python-version'; if (fs.existsSync(versionFile)) { - version = fs.readFileSync(versionFile, 'utf8'); + const version = fs.readFileSync(versionFile, 'utf8'); core.info(`Resolved ${versionFile} as ${version}`); - return version; + return [version]; } logWarning(`${versionFile} doesn't exist.`); - return version; + return versions; } async function run() { @@ -75,35 +75,38 @@ async function run() { `Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}` ); try { - const version = resolveVersionInput(); + const versions = resolveVersionInput(); const checkLatest = core.getBooleanInput('check-latest'); - if (version) { - let pythonVersion: string; + if (versions.length) { + let pythonVersion = ''; const arch: string = core.getInput('architecture') || os.arch(); const updateEnvironment = core.getBooleanInput('update-environment'); - if (isPyPyVersion(version)) { - const installed = await finderPyPy.findPyPyVersion( - version, - arch, - updateEnvironment, - checkLatest - ); - pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; - core.info( - `Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})` - ); - } else { - const installed = await finder.useCpythonVersion( - version, - arch, - updateEnvironment, - checkLatest - ); - pythonVersion = installed.version; - core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); + core.startGroup('Installed versions'); + for (const version of versions) { + if (isPyPyVersion(version)) { + const installed = await finderPyPy.findPyPyVersion( + version, + arch, + updateEnvironment, + checkLatest + ); + pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`; + core.info( + `Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})` + ); + } else { + const installed = await finder.useCpythonVersion( + version, + arch, + updateEnvironment, + checkLatest + ); + pythonVersion = installed.version; + core.info(`Successfully set up ${installed.impl} (${pythonVersion})`); + } } - + core.endGroup(); const cache = core.getInput('cache'); if (cache && isCacheFeatureAvailable()) { await cacheDependencies(cache, pythonVersion);