diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml index f6362069..5f23b213 100644 --- a/.github/workflows/test-pypy.yml +++ b/.github/workflows/test-pypy.yml @@ -22,6 +22,7 @@ jobs: pypy: - 'pypy-2.7' - 'pypy-3.7' + - 'pypy3.9' - 'pypy-2.7-v7.3.4' - 'pypy-3.7-v7.3.5' - 'pypy-3.7-v7.3.4' @@ -29,16 +30,22 @@ jobs: - 'pypy-3.7-v7.x' - 'pypy-2.7-v7.3.4rc1' - 'pypy-3.7-nightly' + - 'pypy3.8-v7.3.7' steps: - name: Checkout uses: actions/checkout@v2 - name: setup-python ${{ matrix.pypy }} + id: setup-python uses: ./ with: python-version: ${{ matrix.pypy }} - + + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + - name: PyPy and Python version run: python --version @@ -54,7 +61,7 @@ jobs: - name: Assert expected binaries (or symlinks) are present run: | EXECUTABLE=${{ matrix.pypy }} - EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name + 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 diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 8f233015..00043f43 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -23,9 +23,14 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: setup default python + - name: setup default python + id: setup-python uses: ./ + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + - name: Validate version run: python --version @@ -45,10 +50,15 @@ jobs: uses: actions/checkout@v2 - name: setup-python ${{ matrix.python }} + id: setup-python uses: ./ with: python-version: ${{ matrix.python }} + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + - name: Validate version run: | $pythonVersion = (python --version) @@ -74,10 +84,15 @@ jobs: uses: actions/checkout@v2 - name: setup-python 3.9.0-beta.4 + id: setup-python uses: ./ with: python-version: '3.9.0-beta.4' + - name: Check python-path + run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' + shell: bash + - name: Validate version run: | $pythonVersion = (python --version) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index fee5ae79..171e2055 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -89,3 +89,13 @@ jobs: python-version: 3.8.1 - name: Verify 3.8.1 run: python __tests__/verify-python.py 3.8.1 + + - name: Run with setup-python 3.10 + id: cp310 + uses: ./ + with: + python-version: "3.10" + - name: Verify 3.10 + run: python __tests__/verify-python.py 3.10 + - name: Run python-path sample 3.10 + run: pipx run --python '${{ steps.cp310.outputs.python-path }}' nox --version diff --git a/README.md b/README.md index 68956645..7508d353 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '2.x', '3.x', 'pypy-2.7', 'pypy-3.7', 'pypy-3.8' ] + python-version: [ '2.x', '3.x', 'pypy2.7', 'pypy3.7', 'pypy3.8' ] name: Python ${{ matrix.python-version }} sample steps: - uses: actions/checkout@v3 @@ -63,7 +63,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', 'pypy-2.7', 'pypy-3.8'] + python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', 'pypy2.7', 'pypy3.8'] exclude: - os: macos-latest python-version: '3.8' @@ -125,9 +125,9 @@ jobs: strategy: matrix: python-version: - - 'pypy-3.7' # the latest available version of PyPy that supports Python 3.7 - - 'pypy-3.7-v7.3.3' # Python 3.7 and PyPy 7.3.3 - - 'pypy-3.8' # the latest available version of PyPy that supports Python 3.8 + - 'pypy3.7' # the latest available version of PyPy that supports Python 3.7 + - 'pypy3.7-v7.3.3' # Python 3.7 and PyPy 7.3.3 + - 'pypy3.8' # the latest available version of PyPy that supports Python 3.8 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 @@ -137,6 +137,20 @@ jobs: ``` More details on PyPy syntax and examples of using preview / nightly versions of PyPy can be found in the [Available versions of PyPy](#available-versions-of-pypy) section. +An output is available with the absolute path of the python interpreter executable if you need it: +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + id: cp310 + with: + python-version: "3.10" + - run: pipx run --python '${{ steps.cp310.outputs.python-path }}' nox --version +``` + # Getting started with Python + Actions Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions). @@ -150,6 +164,7 @@ Check out our detailed guide on using [Python with GitHub Actions](https://help. - For every minor version of Python, expect only the latest patch to be preinstalled. - If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tools cache. - If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache. + - Use `-dev` instead of a patch number (e.g., `3.11-dev`) to install the latest release of a minor version, *alpha and beta releases included*. - Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)). - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file. - If there is a specific version of Python that is not available, you can open an issue here @@ -163,7 +178,7 @@ Check out our detailed guide on using [Python with GitHub Actions](https://help. - Preinstalled versions of PyPy in the tools cache on GitHub-hosted runners - For detailed information regarding the available versions of PyPy that are installed, see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software). - For the latest PyPy release, all versions of Python are cached. - - Cache is updated with a 1-2 week delay. If you specify the PyPy version as `pypy-3.7`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy-3.7-v7.3.3`. + - Cache is updated with a 1-2 week delay. If you specify the PyPy version as `pypy3.7` or `pypy-3.7`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy3.7-v7.3.3` or `pypy-3.7-v7.3.3`. - Downloadable PyPy versions from the [official PyPy site](https://downloads.python.org/pypy/). - All available versions that we can download are listed in [versions.json](https://downloads.python.org/pypy/versions.json) file. @@ -196,17 +211,17 @@ You should specify only a major and minor version if you are okay with the most - Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version Python on the runner will be used. # Specifying a PyPy version -The version of PyPy should be specified in the format `pypy-[-v]`. +The version of PyPy should be specified in the format `pypy[-v]` or `pypy-[-v]`. The `` parameter is optional and can be skipped. The latest version will be used in this case. ``` -pypy-3.7 # the latest available version of PyPy that supports Python 3.7 -pypy-3.8 # the latest available version of PyPy that supports Python 3.8 -pypy-2.7 # the latest available version of PyPy that supports Python 2.7 -pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3 -pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x -pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy -pypy-3.7-nightly # Python 3.7 and nightly PyPy +pypy3.7 or pypy-3.7 # the latest available version of PyPy that supports Python 3.7 +pypy3.8 or pypy-3.8 # the latest available version of PyPy that supports Python 3.8 +pypy2.7 or pypy-2.7 # the latest available version of PyPy that supports Python 2.7 +pypy3.7-v7.3.3 or pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3 +pypy3.7-v7.x or pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x +pypy3.7-v7.3.3rc1 or pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy +pypy3.7-nightly or pypy-3.7-nightly # Python 3.7 and nightly PyPy ``` # Caching packages dependencies diff --git a/__tests__/check-python-path.sh b/__tests__/check-python-path.sh new file mode 100755 index 00000000..1ebee798 --- /dev/null +++ b/__tests__/check-python-path.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail + +PYTHON_PATH="$1" +PATH_EXECUTABLE=$(python -c 'import sys; print(sys.executable)') +PYTHON_PATH_EXECUTABLE=$("${PYTHON_PATH}" -c 'import sys; print(sys.executable)') +if [ "${PATH_EXECUTABLE}" != "${PYTHON_PATH_EXECUTABLE}" ]; then + echo "Executable mismatch." + echo "python in PATH is: ${PATH_EXECUTABLE}" + echo "python-path (${PYTHON_PATH}) is: ${PYTHON_PATH_EXECUTABLE}" + exit 1 +fi +echo "python-path: ${PYTHON_PATH}" diff --git a/__tests__/find-pypy.test.ts b/__tests__/find-pypy.test.ts index ddf7ebcf..8cb32509 100644 --- a/__tests__/find-pypy.test.ts +++ b/__tests__/find-pypy.test.ts @@ -37,16 +37,34 @@ describe('parsePyPyVersion', () => { ['pypy-3.6-v7.x', {pythonVersion: '3.6', pypyVersion: 'v7.x'}], ['pypy-3.6', {pythonVersion: '3.6', pypyVersion: 'x'}], ['pypy-3.6-nightly', {pythonVersion: '3.6', pypyVersion: 'nightly'}], - ['pypy-3.6-v7.3.3rc1', {pythonVersion: '3.6', pypyVersion: 'v7.3.3-rc.1'}] + ['pypy-3.6-v7.3.3rc1', {pythonVersion: '3.6', pypyVersion: 'v7.3.3-rc.1'}], + ['pypy3.8-v7.3.7', {pythonVersion: '3.8', pypyVersion: 'v7.3.7'}], + ['pypy3.8-v7.3.x', {pythonVersion: '3.8', pypyVersion: 'v7.3.x'}], + ['pypy3.8-v7.x', {pythonVersion: '3.8', pypyVersion: 'v7.x'}], + ['pypy3.8', {pythonVersion: '3.8', pypyVersion: 'x'}], + ['pypy3.9-nightly', {pythonVersion: '3.9', pypyVersion: 'nightly'}], + ['pypy3.9-v7.3.8rc1', {pythonVersion: '3.9', pypyVersion: 'v7.3.8-rc.1'}] ])('%s -> %s', (input, expected) => { expect(finder.parsePyPyVersion(input)).toEqual(expected); }); - it('throw on invalid input', () => { - expect(() => finder.parsePyPyVersion('pypy-')).toThrowError( - "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-'. See README for examples and documentation." - ); - }); + it.each(['', 'pypy-', 'pypy', 'p', 'notpypy-'])( + 'throw on invalid input "%s"', + input => { + expect(() => finder.parsePyPyVersion(input)).toThrowError( + "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy' or 'pypy-'. See README for examples and documentation." + ); + } + ); + + it.each(['pypy-2', 'pypy-3', 'pypy2', 'pypy3', 'pypy3.x', 'pypy3.8.10'])( + 'throw on invalid input "%s"', + input => { + expect(() => finder.parsePyPyVersion(input)).toThrowError( + "Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation." + ); + } + ); }); describe('getPyPyVersionFromPath', () => { diff --git a/action.yml b/action.yml index bda521dd..00c1e5e4 100644 --- a/action.yml +++ b/action.yml @@ -21,6 +21,8 @@ outputs: description: "The installed python version. Useful when given a version range as input." cache-hit: description: 'A boolean value to indicate a cache entry was found' + python-path: + description: "The absolute path to the Python executable." runs: using: 'node16' main: 'dist/setup/index.js' diff --git a/dist/setup/index.js b/dist/setup/index.js index fcea6fa1..f3a99313 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -6073,7 +6073,7 @@ const os = __importStar(__webpack_require__(87)); const cache_factory_1 = __webpack_require__(633); const utils_1 = __webpack_require__(163); function isPyPyVersion(versionSpec) { - return versionSpec.startsWith('pypy-'); + return versionSpec.startsWith('pypy'); } function cacheDependencies(cache, pythonVersion) { return __awaiter(this, void 0, void 0, function* () { @@ -6112,6 +6112,9 @@ function run() { yield cacheDependencies(cache, pythonVersion); } } + else { + core.warning('The `python-version` input is not set. The version of Python currently in `PATH` will be used.'); + } const matchersPath = path.join(__dirname, '../..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); } @@ -52385,12 +52388,15 @@ function findPyPyVersion(versionSpec, architecture) { } const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin'; const _binDir = path.join(installDir, pipDir); + const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; + const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`); const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); core.exportVariable('pythonLocation', pythonLocation); core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); core.addPath(pythonLocation); core.addPath(_binDir); core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim()); + core.setOutput('python-path', pythonPath); return { resolvedPyPyVersion, resolvedPythonVersion }; }); } @@ -52421,8 +52427,12 @@ function findPyPyToolCache(pythonVersion, pypyVersion, architecture) { exports.findPyPyToolCache = findPyPyToolCache; function parsePyPyVersion(versionSpec) { const versions = versionSpec.split('-').filter(item => !!item); + if (/^(pypy)(.+)/.test(versions[0])) { + let pythonVersion = versions[0].replace('pypy', ''); + versions.splice(0, 1, 'pypy', pythonVersion); + } if (versions.length < 2 || versions[0] != 'pypy') { - throw new Error("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-'. See README for examples and documentation."); + throw new Error("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy' or 'pypy-'. See README for examples and documentation."); } const pythonVersion = versions[1]; let pypyVersion; @@ -57033,8 +57043,11 @@ function useCpythonVersion(version, architecture) { core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); } } + const _binDir = binDir(installDir); + const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; + const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`); core.addPath(installDir); - core.addPath(binDir(installDir)); + core.addPath(_binDir); if (utils_1.IS_WINDOWS) { // Add --user directory // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/ @@ -57048,6 +57061,7 @@ function useCpythonVersion(version, architecture) { // On Linux and macOS, pip will create the --user directory and add it to PATH as needed. const installed = versionFromPath(installDir); core.setOutput('python-version', installed); + core.setOutput('python-path', pythonPath); return { impl: 'CPython', version: installed }; }); } diff --git a/src/find-pypy.ts b/src/find-pypy.ts index fd273825..1008ed6e 100644 --- a/src/find-pypy.ts +++ b/src/find-pypy.ts @@ -48,12 +48,18 @@ export async function findPyPyVersion( const pipDir = IS_WINDOWS ? 'Scripts' : 'bin'; const _binDir = path.join(installDir, pipDir); + const binaryExtension = IS_WINDOWS ? '.exe' : ''; + const pythonPath = path.join( + IS_WINDOWS ? installDir : _binDir, + `python${binaryExtension}` + ); const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); core.exportVariable('pythonLocation', pythonLocation); core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig'); core.addPath(pythonLocation); core.addPath(_binDir); core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim()); + core.setOutput('python-path', pythonPath); return {resolvedPyPyVersion, resolvedPythonVersion}; } @@ -98,9 +104,14 @@ export function findPyPyToolCache( export function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec { const versions = versionSpec.split('-').filter(item => !!item); + if (/^(pypy)(.+)/.test(versions[0])) { + let pythonVersion = versions[0].replace('pypy', ''); + versions.splice(0, 1, 'pypy', pythonVersion); + } + if (versions.length < 2 || versions[0] != 'pypy') { throw new Error( - "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-'. See README for examples and documentation." + "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy' or 'pypy-'. See README for examples and documentation." ); } diff --git a/src/find-python.ts b/src/find-python.ts index 3959ecf1..0a3dde13 100644 --- a/src/find-python.ts +++ b/src/find-python.ts @@ -83,8 +83,14 @@ export async function useCpythonVersion( } } + const _binDir = binDir(installDir); + const binaryExtension = IS_WINDOWS ? '.exe' : ''; + const pythonPath = path.join( + IS_WINDOWS ? installDir : _binDir, + `python${binaryExtension}` + ); core.addPath(installDir); - core.addPath(binDir(installDir)); + core.addPath(_binDir); if (IS_WINDOWS) { // Add --user directory @@ -106,6 +112,7 @@ export async function useCpythonVersion( const installed = versionFromPath(installDir); core.setOutput('python-version', installed); + core.setOutput('python-path', pythonPath); return {impl: 'CPython', version: installed}; } diff --git a/src/setup-python.ts b/src/setup-python.ts index 37229c59..e8789de6 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -7,7 +7,7 @@ import {getCacheDistributor} from './cache-distributions/cache-factory'; import {isCacheFeatureAvailable} from './utils'; function isPyPyVersion(versionSpec: string) { - return versionSpec.startsWith('pypy-'); + return versionSpec.startsWith('pypy'); } async function cacheDependencies(cache: string, pythonVersion: string) { @@ -53,6 +53,10 @@ async function run() { if (cache && isCacheFeatureAvailable()) { await cacheDependencies(cache, pythonVersion); } + } else { + core.warning( + 'The `python-version` input is not set. The version of Python currently in `PATH` will be used.' + ); } const matchersPath = path.join(__dirname, '../..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`);