Adding support for more PyPy versions and installing them on-flight (#168)

* add support to install pypy

* resolved comments, update readme, add e2e tests.

* resolve throw error

* Add pypy unit tests to cover code

* add tests

* Update test-pypy.yml

* Update test-python.yml

* Update test-python.yml

* Update README.md

* fixing tests

* change order

Co-authored-by: Maxim Lobanov <v-malob@microsoft.com>

* add pypy tests and fix issue with pypy-3-nightly

Co-authored-by: Maxim Lobanov <v-malob@microsoft.com>
This commit is contained in:
Dmitry Shibanov 2020-12-17 18:03:54 +03:00 committed by GitHub
parent 2831efe49a
commit 8c5ea631b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1896 additions and 34 deletions

47
.github/workflows/test-pypy.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Validate PyPy e2e
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
schedule:
- cron: 30 3 * * *
jobs:
setup-pypy:
name: Setup PyPy ${{ matrix.pypy }} ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04]
pypy:
- 'pypy-2.7'
- 'pypy-3.6'
- 'pypy-3.7'
- 'pypy-2.7-v7.3.2'
- 'pypy-3.6-v7.3.2'
- 'pypy-3.7-v7.3.2'
- 'pypy-3.6-v7.3.x'
- 'pypy-3.7-v7.x'
- 'pypy-3.6-v7.3.3rc1'
- 'pypy-3.7-nightly'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: setup-python ${{ matrix.pypy }}
uses: ./
with:
python-version: ${{ matrix.pypy }}
- name: PyPy and Python version
run: python --version
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'

View file

@ -1,4 +1,4 @@
name: Validate 'setup-python' name: Validate Python e2e
on: on:
push: push:
branches: branches:
@ -9,7 +9,7 @@ on:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
schedule: schedule:
- cron: 0 0 * * * - cron: 30 3 * * *
jobs: jobs:
default-version: default-version:
@ -18,7 +18,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -38,7 +38,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
python: [3.5.4, 3.6.7, 3.7.5, 3.8.1] python: [3.5.4, 3.6.7, 3.7.5, 3.8.1]
steps: steps:
- name: Checkout - name: Checkout
@ -68,7 +68,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -91,13 +91,13 @@ jobs:
- name: Run simple code - name: Run simple code
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-pypy: setup-pypy-legacy:
name: Setup PyPy ${{ matrix.os }} name: Setup PyPy ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2

View file

@ -17,6 +17,7 @@ This action sets up a Python environment for use in actions by:
- Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed. - Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed.
- Automatic setup and download of Python packages if using a self-hosted runner. - Automatic setup and download of Python packages if using a self-hosted runner.
- Support for pre-release versions of Python. - Support for pre-release versions of Python.
- Support for installing any version of PyPy on-flight
# Usage # Usage
@ -40,7 +41,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ '2.x', '3.x', 'pypy2', 'pypy3' ] python-version: [ '2.x', '3.x', 'pypy-2.7', 'pypy-3.6', 'pypy-3.7' ]
name: Python ${{ matrix.python-version }} sample name: Python ${{ matrix.python-version }} sample
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -60,7 +61,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [2.7, 3.6, 3.7, 3.8, pypy2, pypy3] python-version: [2.7, 3.6, 3.7, 3.8, pypy-2.7, pypy-3.6]
exclude: exclude:
- os: macos-latest - os: macos-latest
python-version: 3.8 python-version: 3.8
@ -91,7 +92,6 @@ jobs:
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- run: python my_script.py - run: python my_script.py
``` ```
Download and set up an accurate pre-release version of Python: Download and set up an accurate pre-release version of Python:
@ -114,6 +114,27 @@ steps:
- run: python my_script.py - run: python my_script.py
``` ```
Download and set up PyPy:
```yaml
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- pypy-3.6 # the latest available version of PyPy that supports Python 3.6
- 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
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- run: python my_script.py
```
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.
# Getting started with Python + Actions # 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). 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).
@ -129,7 +150,21 @@ Check out our detailed guide on using [Python with GitHub Actions](https://help.
- 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. - 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.
- Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)). - 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. - 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. - If there is a specific version of Python that is not available, you can open an issue here
# Available versions of PyPy
`setup-python` is able to configure PyPy from two sources:
- 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.6`, 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.6-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.
- PyPy < 7.3.3 are not available to install on-flight.
- If some versions are not available, you can open an issue in https://foss.heptapod.net/pypy/pypy/
# Hosted Tool Cache # Hosted Tool Cache
@ -156,6 +191,20 @@ You should specify only a major and minor version if you are okay with the most
- The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced. - The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced.
- 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. - 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-<python_version>[-v<pypy_version>]`.
The `<pypy_version>` parameter is optional and can be skipped. The latest version will be used in this case.
```
pypy-3.6 # the latest available version of PyPy that supports Python 3.6
pypy-3.7 # the latest available version of PyPy that supports Python 3.7
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
```
# Using `setup-python` with a self hosted runner # Using `setup-python` with a self hosted runner
Python distributions are only available for the same [environments](https://github.com/actions/virtual-environments#available-environments) that GitHub Actions hosted environments are available for. If you are using an unsupported version of Ubuntu such as `19.04` or another Linux distribution such as Fedora, `setup-python` will not work. If you have a supported self-hosted runner and you would like to use `setup-python`, there are a few extra things you need to make sure are set up so that new versions of Python can be downloaded and configured on your runner. Python distributions are only available for the same [environments](https://github.com/actions/virtual-environments#available-environments) that GitHub Actions hosted environments are available for. If you are using an unsupported version of Ubuntu such as `19.04` or another Linux distribution such as Fedora, `setup-python` will not work. If you have a supported self-hosted runner and you would like to use `setup-python`, there are a few extra things you need to make sure are set up so that new versions of Python can be downloaded and configured on your runner.

494
__tests__/data/pypy.json Normal file
View file

@ -0,0 +1,494 @@
[
{
"pypy_version": "7.3.3",
"python_version": "3.6.12",
"stable": true,
"latest_pypy": true,
"date": "2020-11-21",
"files": [
{
"filename": "pypy3.6-v7.3.3-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-aarch64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-linux32.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-linux64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-darwin64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-darwin64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-win32.zip"
},
{
"filename": "pypy3.6-v7.3.3-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.3rc1",
"python_version": "3.6.12",
"stable": false,
"latest_pypy": false,
"date": "2020-11-11",
"files": [
{
"filename": "pypy3.6-v7.3.3rc1-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-aarch64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-linux32rc1.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-linux32.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3rc1-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-linux64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3rc1-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-osx64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.3-win32rc1.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-win32.zip"
},
{
"filename": "pypy3.6-v7.3.3rc1-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.3rc1-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.3rc2",
"python_version": "3.7.7",
"stable": false,
"latest_pypy": false,
"date": "2020-11-11",
"files": [
{
"filename": "test.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "test.tar.bz2"
},
{
"filename": "test.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "test.tar.bz2"
},
{
"filename": "test.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "test.tar.bz2"
},
{
"filename": "test.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "test.tar.bz2"
},
{
"filename": "test.zip",
"arch": "x86",
"platform": "win32",
"download_url": "test.zip"
},
{
"filename": "test.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "test.tar.bz2"
}
]
},
{
"pypy_version": "7.3.3",
"python_version": "3.7.9",
"stable": true,
"latest_pypy": true,
"date": "2020-11-21",
"files": [
{
"filename": "pypy3.7-v7.3.3-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-aarch64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.3-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-linux32.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.3-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-linux64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.3-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.3-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-win32.zip"
},
{
"filename": "pypy3.7-v7.3.3-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.3-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.3",
"python_version": "2.7.18",
"stable": true,
"latest_pypy": true,
"date": "2020-11-21",
"files": [
{
"filename": "pypy2.7-v7.3.3-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-aarch64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.3-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-linux32.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.3-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-linux64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.3-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-osx64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.3-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-win32.zip"
},
{
"filename": "pypy2.7-v7.3.3-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.3-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.2",
"python_version": "3.6.9",
"stable": true,
"latest_pypy": true,
"date": "2020-09-25",
"files": [
{
"filename": "pypy3.6-v7.3.2-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-aarch64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.2-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-linux32.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.2-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-linux64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.2-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-osx64.tar.bz2"
},
{
"filename": "pypy3.6-v7.3.2-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-win32.zip"
},
{
"filename": "pypy3.6-v7.3.2-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.6-v7.3.2-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.2",
"python_version": "3.7.9",
"stable": true,
"latest_pypy": false,
"date": "2020-09-25",
"files": [
{
"filename": "pypy3.7-v7.3.2-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-aarch64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.2-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-linux32.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.2-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-linux64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.2-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-osx64.tar.bz2"
},
{
"filename": "pypy3.7-v7.3.2-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-win32.zip"
},
{
"filename": "pypy3.7-v7.3.2-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy3.7-v7.3.2-s390x.tar.bz2"
}
]
},
{
"pypy_version": "7.3.2",
"python_version": "2.7.13",
"stable": true,
"latest_pypy": true,
"date": "2020-09-25",
"files": [
{
"filename": "pypy2.7-v7.3.2-aarch64.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-aarch64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.2-linux32.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-linux32.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.2-linux64.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-linux64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.2-osx64.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-osx64.tar.bz2"
},
{
"filename": "pypy2.7-v7.3.2-win32.zip",
"arch": "x86",
"platform": "win32",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-win32.zip"
},
{
"filename": "pypy2.7-v7.3.2-s390x.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "https://test.download.python.org/pypy/pypy2.7-v7.3.2-s390x.tar.bz2"
}
]
},
{
"pypy_version": "nightly",
"python_version": "2.7",
"stable": false,
"latest_pypy": false,
"files": [
{
"filename": "filename.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.zip",
"arch": "x86",
"platform": "win32",
"download_url": "http://nightlyBuilds.org/filename.zip"
},
{
"filename": "filename.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
}
]
},
{
"pypy_version": "nightly",
"python_version": "3.7",
"stable": false,
"latest_pypy": false,
"files": [
{
"filename": "filename.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.zip",
"arch": "x86",
"platform": "win32",
"download_url": "http://nightlyBuilds.org/filename.zip"
},
{
"filename": "filename.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
}
]
},
{
"pypy_version": "nightly",
"python_version": "3.6",
"stable": false,
"latest_pypy": false,
"files": [
{
"filename": "filename.tar.bz2",
"arch": "aarch64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "i686",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.tar.bz2",
"arch": "x64",
"platform": "darwin",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
},
{
"filename": "filename.zip",
"arch": "x86",
"platform": "win32",
"download_url": "http://nightlyBuilds.org/filename.zip"
},
{
"filename": "filename.tar.bz2",
"arch": "s390x",
"platform": "linux",
"download_url": "http://nightlyBuilds.org/filename.tar.bz2"
}
]
}
]

237
__tests__/find-pypy.test.ts Normal file
View file

@ -0,0 +1,237 @@
import fs from 'fs';
import * as utils from '../src/utils';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as path from 'path';
import * as semver from 'semver';
import * as finder from '../src/find-pypy';
import {
IPyPyManifestRelease,
IS_WINDOWS,
validateVersion,
getPyPyVersionFromPath
} from '../src/utils';
const manifestData = require('./data/pypy.json');
let architecture: string;
if (IS_WINDOWS) {
architecture = 'x86';
} else {
architecture = 'x64';
}
const toolDir = path.join(__dirname, 'runner', 'tools');
const tempDir = path.join(__dirname, 'runner', 'temp');
describe('parsePyPyVersion', () => {
it.each([
['pypy-3.6-v7.3.3', {pythonVersion: '3.6', pypyVersion: 'v7.3.3'}],
['pypy-3.6-v7.3.x', {pythonVersion: '3.6', pypyVersion: 'v7.3.x'}],
['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'}]
])('%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-<python-version>'. See README for examples and documentation."
);
});
});
describe('getPyPyVersionFromPath', () => {
it('/fake/toolcache/PyPy/3.6.5/x64 -> 3.6.5', () => {
expect(getPyPyVersionFromPath('/fake/toolcache/PyPy/3.6.5/x64')).toEqual(
'3.6.5'
);
});
});
describe('findPyPyToolCache', () => {
const actualPythonVersion = '3.6.17';
const actualPyPyVersion = '7.5.4';
const pypyPath = path.join('PyPy', actualPythonVersion, architecture);
let tcFind: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.SpyInstance;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualPythonVersion, semverVersion)
? pypyPath
: '';
});
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
it('PyPy exists on the path and versions are satisfied', () => {
expect(finder.findPyPyToolCache('3.6.17', 'v7.5.4', architecture)).toEqual({
installDir: pypyPath,
resolvedPythonVersion: actualPythonVersion,
resolvedPyPyVersion: actualPyPyVersion
});
});
it('PyPy exists on the path and versions are satisfied with semver', () => {
expect(finder.findPyPyToolCache('3.6', 'v7.5.x', architecture)).toEqual({
installDir: pypyPath,
resolvedPythonVersion: actualPythonVersion,
resolvedPyPyVersion: actualPyPyVersion
});
});
it("PyPy exists on the path, but Python version doesn't match", () => {
expect(finder.findPyPyToolCache('3.7', 'v7.5.4', architecture)).toEqual({
installDir: '',
resolvedPythonVersion: '',
resolvedPyPyVersion: ''
});
});
it("PyPy exists on the path, but PyPy version doesn't match", () => {
expect(finder.findPyPyToolCache('3.6', 'v7.5.1', architecture)).toEqual({
installDir: null,
resolvedPythonVersion: '',
resolvedPyPyVersion: ''
});
});
});
describe('findPyPyVersion', () => {
let tcFind: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
let spyWriteExactPyPyVersionFile: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((tool: string, version: string) => {
const semverRange = new semver.Range(version);
let pypyPath = '';
if (semver.satisfies('3.6.12', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
}
return pypyPath;
});
spyWriteExactPyPyVersionFile = jest.spyOn(
utils,
'writeExactPyPyVersionFile'
);
spyWriteExactPyPyVersionFile.mockImplementation(() => null);
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => '7.3.3');
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation((directory: string) => ['PyPyTest']);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation(
async (): Promise<ifm.ITypedResponse<IPyPyManifestRelease[]>> => {
const result = JSON.stringify(manifestData);
return {
statusCode: 200,
headers: {},
result: JSON.parse(result) as IPyPyManifestRelease[]
};
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
spySymlinkSync.mockImplementation(() => undefined);
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
it('found PyPy in toolcache', async () => {
await expect(
finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture)
).resolves.toEqual({
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
});
it('throw on invalid input format', async () => {
await expect(
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture)
).rejects.toThrow();
});
it('throw on invalid input format pypy3.7-7.3.x', async () => {
await expect(
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture)
).rejects.toThrow();
});
it('found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture)
).resolves.toEqual({
resolvedPythonVersion: '3.7.9',
resolvedPyPyVersion: '7.3.3'
});
});
it('throw if release is not found', async () => {
await expect(
finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture)
).rejects.toThrowError(
`PyPy version 3.7 (v7.5.x) with arch ${architecture} not found`
);
});
});

View file

@ -0,0 +1,230 @@
import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as path from 'path';
import * as installer from '../src/install-pypy';
import {
IPyPyManifestRelease,
IPyPyManifestAsset,
IS_WINDOWS
} from '../src/utils';
const manifestData = require('./data/pypy.json');
let architecture: string;
if (IS_WINDOWS) {
architecture = 'x86';
} else {
architecture = 'x64';
}
const toolDir = path.join(__dirname, 'runner', 'tools');
const tempDir = path.join(__dirname, 'runner', 'temp');
describe('pypyVersionToSemantic', () => {
it.each([
['7.3.3rc1', '7.3.3-rc.1'],
['7.3.3', '7.3.3'],
['7.3.x', '7.3.x'],
['7.x', '7.x'],
['nightly', 'nightly']
])('%s -> %s', (input, expected) => {
expect(installer.pypyVersionToSemantic(input)).toEqual(expected);
});
});
describe('findRelease', () => {
const result = JSON.stringify(manifestData);
const releases = JSON.parse(result) as IPyPyManifestRelease[];
const extension = IS_WINDOWS ? '.zip' : '.tar.bz2';
const extensionName = IS_WINDOWS
? `${process.platform}${extension}`
: `${process.platform}64${extension}`;
const files: IPyPyManifestAsset = {
filename: `pypy3.6-v7.3.3-${extensionName}`,
arch: architecture,
platform: process.platform,
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.3.3-${extensionName}`
};
it("Python version is found, but PyPy version doesn't match", () => {
const pythonVersion = '3.6';
const pypyVersion = '7.3.7';
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual(null);
});
it('Python version is found and PyPy version matches', () => {
const pythonVersion = '3.6';
const pypyVersion = '7.3.3';
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual({
foundAsset: files,
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: pypyVersion
});
});
it('Python version is found in toolcache and PyPy version matches semver', () => {
const pythonVersion = '3.6';
const pypyVersion = '7.x';
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual({
foundAsset: files,
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
});
it('Python and preview version of PyPy are found', () => {
const pythonVersion = '3.7';
const pypyVersion = installer.pypyVersionToSemantic('7.3.3rc2');
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual({
foundAsset: {
filename: `test${extension}`,
arch: architecture,
platform: process.platform,
download_url: `test${extension}`
},
resolvedPythonVersion: '3.7.7',
resolvedPyPyVersion: '7.3.3rc2'
});
});
it('Python version with latest PyPy is found', () => {
const pythonVersion = '3.6';
const pypyVersion = 'x';
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual({
foundAsset: files,
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
});
it('Nightly release is found', () => {
const pythonVersion = '3.6';
const pypyVersion = 'nightly';
const filename = IS_WINDOWS ? 'filename.zip' : 'filename.tar.bz2';
expect(
installer.findRelease(releases, pythonVersion, pypyVersion, architecture)
).toEqual({
foundAsset: {
filename: filename,
arch: architecture,
platform: process.platform,
download_url: `http://nightlyBuilds.org/${filename}`
},
resolvedPythonVersion: '3.6',
resolvedPyPyVersion: pypyVersion
});
});
});
describe('installPyPy', () => {
let tcFind: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
let spyFsWriteFile: jest.SpyInstance;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation(() => path.join('PyPy', '3.6.12', architecture));
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest']);
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
spyFsWriteFile.mockImplementation(() => undefined);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation(
async (): Promise<ifm.ITypedResponse<IPyPyManifestRelease[]>> => {
const result = JSON.stringify(manifestData);
return {
statusCode: 200,
headers: {},
result: JSON.parse(result) as IPyPyManifestRelease[]
};
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
spySymlinkSync.mockImplementation(() => undefined);
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockImplementation(() => false);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
it('throw if release is not found', async () => {
await expect(
installer.installPyPy('7.3.3', '3.6.17', architecture)
).rejects.toThrowError(
`PyPy version 3.6.17 (7.3.3) with arch ${architecture} not found`
);
expect(spyHttpClient).toHaveBeenCalled();
expect(spyDownloadTool).not.toHaveBeenCalled();
expect(spyExec).not.toHaveBeenCalled();
});
it('found and install PyPy', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.6.12', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
installer.installPyPy('7.3.x', '3.6.12', architecture)
).resolves.toEqual({
installDir: path.join(toolDir, 'PyPy', '3.6.12', architecture),
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
expect(spyHttpClient).toHaveBeenCalled();
expect(spyDownloadTool).toHaveBeenCalled();
expect(spyExistsSync).toHaveBeenCalled();
expect(spyCacheDir).toHaveBeenCalled();
expect(spyExec).toHaveBeenCalled();
});
});

34
__tests__/utils.test.ts Normal file
View file

@ -0,0 +1,34 @@
import {
validateVersion,
validatePythonVersionFormatForPyPy
} from '../src/utils';
describe('validatePythonVersionFormatForPyPy', () => {
it.each([
['3.6', true],
['3.7', true],
['3.6.x', false],
['3.7.x', false],
['3.x', false],
['3', false]
])('%s -> %s', (input, expected) => {
expect(validatePythonVersionFormatForPyPy(input)).toEqual(expected);
});
});
describe('validateVersion', () => {
it.each([
['v7.3.3', true],
['v7.3.x', true],
['v7.x', true],
['x', true],
['v7.3.3-rc.1', true],
['nightly', true],
['v7.3.b', false],
['3.6', true],
['3.b', false],
['3', true]
])('%s -> %s', (input, expected) => {
expect(validateVersion(input)).toEqual(expected);
});
});

370
dist/index.js vendored
View file

@ -1072,6 +1072,113 @@ function _readLinuxVersionFile() {
exports._readLinuxVersionFile = _readLinuxVersionFile; exports._readLinuxVersionFile = _readLinuxVersionFile;
//# sourceMappingURL=manifest.js.map //# sourceMappingURL=manifest.js.map
/***/ }),
/***/ 50:
/***/ (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 path = __importStar(__webpack_require__(622));
const pypyInstall = __importStar(__webpack_require__(369));
const utils_1 = __webpack_require__(163);
const semver = __importStar(__webpack_require__(876));
const core = __importStar(__webpack_require__(470));
const tc = __importStar(__webpack_require__(533));
function findPyPyVersion(versionSpec, architecture) {
return __awaiter(this, void 0, void 0, function* () {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
// PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64.
if (utils_1.IS_WINDOWS && architecture === 'x64') {
architecture = 'x86';
}
({ installDir, resolvedPythonVersion, resolvedPyPyVersion } = findPyPyToolCache(pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture));
if (!installDir) {
({
installDir,
resolvedPythonVersion,
resolvedPyPyVersion
} = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture));
}
const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin';
const _binDir = path.join(installDir, pipDir);
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
core.exportVariable('pythonLocation', pythonLocation);
core.addPath(pythonLocation);
core.addPath(_binDir);
return { resolvedPyPyVersion, resolvedPythonVersion };
});
}
exports.findPyPyVersion = findPyPyVersion;
function findPyPyToolCache(pythonVersion, pypyVersion, architecture) {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir = tc.find('PyPy', pythonVersion, architecture);
if (installDir) {
// 'tc.find' finds tool based on Python version but we also need to check
// whether PyPy version satisfies requested version.
resolvedPythonVersion = utils_1.getPyPyVersionFromPath(installDir);
resolvedPyPyVersion = utils_1.readExactPyPyVersionFile(installDir);
const isPyPyVersionSatisfies = semver.satisfies(resolvedPyPyVersion, pypyVersion);
if (!isPyPyVersionSatisfies) {
installDir = null;
resolvedPyPyVersion = '';
resolvedPythonVersion = '';
}
}
if (!installDir) {
core.info(`PyPy version ${pythonVersion} (${pypyVersion}) was not found in the local cache`);
}
return { installDir, resolvedPythonVersion, resolvedPyPyVersion };
}
exports.findPyPyToolCache = findPyPyToolCache;
function parsePyPyVersion(versionSpec) {
const versions = versionSpec.split('-').filter(item => !!item);
if (versions.length < 2 || versions[0] != 'pypy') {
throw new Error("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See README for examples and documentation.");
}
const pythonVersion = versions[1];
let pypyVersion;
if (versions.length > 2) {
pypyVersion = pypyInstall.pypyVersionToSemantic(versions[2]);
}
else {
pypyVersion = 'x';
}
if (!utils_1.validateVersion(pythonVersion) || !utils_1.validateVersion(pypyVersion)) {
throw new Error("Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation.");
}
if (!utils_1.validatePythonVersionFormatForPyPy(pythonVersion)) {
throw new Error("Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation.");
}
return {
pypyVersion: pypyVersion,
pythonVersion: pythonVersion
};
}
exports.parsePyPyVersion = parsePyPyVersion;
/***/ }), /***/ }),
/***/ 65: /***/ 65:
@ -2197,6 +2304,92 @@ if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
exports.debug = debug; // for test exports.debug = debug; // for test
/***/ }),
/***/ 163:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
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 fs_1 = __importDefault(__webpack_require__(747));
const path = __importStar(__webpack_require__(622));
const semver = __importStar(__webpack_require__(876));
exports.IS_WINDOWS = process.platform === 'win32';
exports.IS_LINUX = process.platform === 'linux';
const PYPY_VERSION_FILE = 'PYPY_VERSION';
/** create Symlinks for downloaded PyPy
* It should be executed only for downloaded versions in runtime, because
* toolcache versions have this setup.
*/
function createSymlinkInFolder(folderPath, sourceName, targetName, setExecutable = false) {
const sourcePath = path.join(folderPath, sourceName);
const targetPath = path.join(folderPath, targetName);
if (fs_1.default.existsSync(targetPath)) {
return;
}
fs_1.default.symlinkSync(sourcePath, targetPath);
if (!exports.IS_WINDOWS && setExecutable) {
fs_1.default.chmodSync(targetPath, '755');
}
}
exports.createSymlinkInFolder = createSymlinkInFolder;
function validateVersion(version) {
return isNightlyKeyword(version) || Boolean(semver.validRange(version));
}
exports.validateVersion = validateVersion;
function isNightlyKeyword(pypyVersion) {
return pypyVersion === 'nightly';
}
exports.isNightlyKeyword = isNightlyKeyword;
function getPyPyVersionFromPath(installDir) {
return path.basename(path.dirname(installDir));
}
exports.getPyPyVersionFromPath = getPyPyVersionFromPath;
/**
* In tool-cache, we put PyPy to '<toolcache_root>/PyPy/<python_version>/x64'
* There is no easy way to determine what PyPy version is located in specific folder
* 'pypy --version' is not reliable enough since it is not set properly for preview versions
* "7.3.3rc1" is marked as '7.3.3' in 'pypy --version'
* so we put PYPY_VERSION file to PyPy directory when install it to VM and read it when we need to know version
* PYPY_VERSION contains exact version from 'versions.json'
*/
function readExactPyPyVersionFile(installDir) {
let pypyVersion = '';
let fileVersion = path.join(installDir, PYPY_VERSION_FILE);
if (fs_1.default.existsSync(fileVersion)) {
pypyVersion = fs_1.default.readFileSync(fileVersion).toString();
}
return pypyVersion;
}
exports.readExactPyPyVersionFile = readExactPyPyVersionFile;
function writeExactPyPyVersionFile(installDir, resolvedPyPyVersion) {
const pypyFilePath = path.join(installDir, PYPY_VERSION_FILE);
fs_1.default.writeFileSync(pypyFilePath, resolvedPyPyVersion);
}
exports.writeExactPyPyVersionFile = writeExactPyPyVersionFile;
/**
* Python version should be specified explicitly like "x.y" (2.7, 3.6, 3.7)
* "3.x" or "3" are not supported
* because it could cause ambiguity when both PyPy version and Python version are not precise
*/
function validatePythonVersionFormatForPyPy(version) {
const re = /^\d+\.\d+$/;
return re.test(version);
}
exports.validatePythonVersionFormatForPyPy = validatePythonVersionFormatForPyPy;
/***/ }), /***/ }),
/***/ 164: /***/ 164:
@ -2443,17 +2636,27 @@ var __importStar = (this && this.__importStar) || function (mod) {
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 finderPyPy = __importStar(__webpack_require__(50));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
function isPyPyVersion(versionSpec) {
return versionSpec.startsWith('pypy-');
}
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') || os.arch(); const arch = core.getInput('architecture') || os.arch();
if (isPyPyVersion(version)) {
const installed = yield finderPyPy.findPyPyVersion(version, arch);
core.info(`Successfully setup PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`);
}
else {
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')}`);
} }
@ -2580,6 +2783,151 @@ module.exports = ltr
module.exports = require("assert"); module.exports = require("assert");
/***/ }),
/***/ 369:
/***/ (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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(__webpack_require__(622));
const core = __importStar(__webpack_require__(470));
const tc = __importStar(__webpack_require__(533));
const semver = __importStar(__webpack_require__(876));
const httpm = __importStar(__webpack_require__(539));
const exec = __importStar(__webpack_require__(986));
const fs_1 = __importDefault(__webpack_require__(747));
const utils_1 = __webpack_require__(163);
function installPyPy(pypyVersion, pythonVersion, architecture) {
return __awaiter(this, void 0, void 0, function* () {
let downloadDir;
const releases = yield getAvailablePyPyVersions();
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
const releaseData = findRelease(releases, pythonVersion, pypyVersion, architecture);
if (!releaseData || !releaseData.foundAsset) {
throw new Error(`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`);
}
const { foundAsset, resolvedPythonVersion, resolvedPyPyVersion } = releaseData;
let downloadUrl = `${foundAsset.download_url}`;
core.info(`Downloading PyPy from "${downloadUrl}" ...`);
const pypyPath = yield tc.downloadTool(downloadUrl);
core.info('Extracting downloaded archive...');
if (utils_1.IS_WINDOWS) {
downloadDir = yield tc.extractZip(pypyPath);
}
else {
downloadDir = yield tc.extractTar(pypyPath, undefined, 'x');
}
// root folder in archive can have unpredictable name so just take the first folder
// downloadDir is unique folder under TEMP and can't contain any other folders
const archiveName = fs_1.default.readdirSync(downloadDir)[0];
const toolDir = path.join(downloadDir, archiveName);
let installDir = toolDir;
if (!utils_1.isNightlyKeyword(resolvedPyPyVersion)) {
installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture);
}
utils_1.writeExactPyPyVersionFile(installDir, resolvedPyPyVersion);
const binaryPath = getPyPyBinaryPath(installDir);
yield createPyPySymlink(binaryPath, resolvedPythonVersion);
yield installPip(binaryPath);
return { installDir, resolvedPythonVersion, resolvedPyPyVersion };
});
}
exports.installPyPy = installPyPy;
function getAvailablePyPyVersions() {
return __awaiter(this, void 0, void 0, function* () {
const url = 'https://downloads.python.org/pypy/versions.json';
const http = new httpm.HttpClient('tool-cache');
const response = yield http.getJson(url);
if (!response.result) {
throw new Error(`Unable to retrieve the list of available PyPy versions from '${url}'`);
}
return response.result;
});
}
function createPyPySymlink(pypyBinaryPath, pythonVersion) {
return __awaiter(this, void 0, void 0, function* () {
const version = semver.coerce(pythonVersion);
const pythonBinaryPostfix = semver.major(version);
const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3';
let binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
core.info('Creating symlinks...');
utils_1.createSymlinkInFolder(pypyBinaryPath, `pypy${pypyBinaryPostfix}${binaryExtension}`, `python${pythonBinaryPostfix}${binaryExtension}`, true);
utils_1.createSymlinkInFolder(pypyBinaryPath, `pypy${pypyBinaryPostfix}${binaryExtension}`, `python${binaryExtension}`, true);
});
}
function installPip(pythonLocation) {
return __awaiter(this, void 0, void 0, function* () {
core.info('Installing and updating pip');
const pythonBinary = path.join(pythonLocation, 'python');
yield exec.exec(`${pythonBinary} -m ensurepip`);
yield exec.exec(`${pythonLocation}/python -m pip install --ignore-installed pip`);
});
}
function findRelease(releases, pythonVersion, pypyVersion, architecture) {
const filterReleases = releases.filter(item => {
const isPythonVersionSatisfied = semver.satisfies(semver.coerce(item.python_version), pythonVersion);
const isPyPyNightly = utils_1.isNightlyKeyword(pypyVersion) && utils_1.isNightlyKeyword(item.pypy_version);
const isPyPyVersionSatisfied = isPyPyNightly ||
semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion);
const isArchPresent = item.files &&
item.files.some(file => file.arch === architecture && file.platform === process.platform);
return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent;
});
if (filterReleases.length === 0) {
return null;
}
const sortedReleases = filterReleases.sort((previous, current) => {
return (semver.compare(semver.coerce(pypyVersionToSemantic(current.pypy_version)), semver.coerce(pypyVersionToSemantic(previous.pypy_version))) ||
semver.compare(semver.coerce(current.python_version), semver.coerce(previous.python_version)));
});
const foundRelease = sortedReleases[0];
const foundAsset = foundRelease.files.find(item => item.arch === architecture && item.platform === process.platform);
return {
foundAsset,
resolvedPythonVersion: foundRelease.python_version,
resolvedPyPyVersion: foundRelease.pypy_version
};
}
exports.findRelease = findRelease;
/** Get PyPy binary location from the tool of installation directory
* - On Linux and macOS, the Python interpreter is in 'bin'.
* - On Windows, it is in the installation root.
*/
function getPyPyBinaryPath(installDir) {
const _binDir = path.join(installDir, 'bin');
return utils_1.IS_WINDOWS ? installDir : _binDir;
}
exports.getPyPyBinaryPath = getPyPyBinaryPath;
function pypyVersionToSemantic(versionSpec) {
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
return versionSpec.replace(prereleaseVersion, '$1-$2.$3');
}
exports.pypyVersionToSemantic = pypyVersionToSemantic;
/***/ }), /***/ }),
/***/ 413: /***/ 413:
@ -6426,14 +6774,13 @@ const path = __importStar(__webpack_require__(622));
const core = __importStar(__webpack_require__(470)); const core = __importStar(__webpack_require__(470));
const tc = __importStar(__webpack_require__(533)); const tc = __importStar(__webpack_require__(533));
const exec = __importStar(__webpack_require__(986)); const exec = __importStar(__webpack_require__(986));
const utils_1 = __webpack_require__(163);
const TOKEN = core.getInput('token'); const TOKEN = core.getInput('token');
const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`;
const MANIFEST_REPO_OWNER = 'actions'; const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions'; const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main'; const MANIFEST_REPO_BRANCH = 'main';
exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const IS_WINDOWS = process.platform === 'win32';
const IS_LINUX = process.platform === 'linux';
function findReleaseFromManifest(semanticVersionSpec, architecture) { function findReleaseFromManifest(semanticVersionSpec, architecture) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH); const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
@ -6445,7 +6792,7 @@ function installPython(workingDirectory) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const options = { const options = {
cwd: workingDirectory, cwd: workingDirectory,
env: Object.assign(Object.assign({}, process.env), (IS_LINUX && { LD_LIBRARY_PATH: path.join(workingDirectory, 'lib') })), env: Object.assign(Object.assign({}, process.env), (utils_1.IS_LINUX && { LD_LIBRARY_PATH: path.join(workingDirectory, 'lib') })),
silent: true, silent: true,
listeners: { listeners: {
stdout: (data) => { stdout: (data) => {
@ -6456,7 +6803,7 @@ function installPython(workingDirectory) {
} }
} }
}; };
if (IS_WINDOWS) { if (utils_1.IS_WINDOWS) {
yield exec.exec('powershell', ['./setup.ps1'], options); yield exec.exec('powershell', ['./setup.ps1'], options);
} }
else { else {
@ -6471,7 +6818,7 @@ function installCpythonFromRelease(release) {
const pythonPath = yield tc.downloadTool(downloadUrl, undefined, AUTH); const pythonPath = yield tc.downloadTool(downloadUrl, undefined, AUTH);
core.info('Extract downloaded archive'); core.info('Extract downloaded archive');
let pythonExtractedFolder; let pythonExtractedFolder;
if (IS_WINDOWS) { if (utils_1.IS_WINDOWS) {
pythonExtractedFolder = yield tc.extractZip(pythonPath); pythonExtractedFolder = yield tc.extractZip(pythonPath);
} }
else { else {
@ -6686,12 +7033,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
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 utils_1 = __webpack_require__(163);
const semver = __importStar(__webpack_require__(876)); const semver = __importStar(__webpack_require__(876));
const installer = __importStar(__webpack_require__(824)); const installer = __importStar(__webpack_require__(824));
const core = __importStar(__webpack_require__(470)); const core = __importStar(__webpack_require__(470));
const tc = __importStar(__webpack_require__(533)); const tc = __importStar(__webpack_require__(533));
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.
// There is a seperate directory for `pip install --user`. // There is a seperate directory for `pip install --user`.
@ -6705,7 +7051,7 @@ const IS_LINUX = process.platform === 'linux';
// (--user) %APPDATA%\Python\PythonXY\Scripts // (--user) %APPDATA%\Python\PythonXY\Scripts
// See https://docs.python.org/3/library/sysconfig.html // See https://docs.python.org/3/library/sysconfig.html
function binDir(installDir) { function binDir(installDir) {
if (IS_WINDOWS) { if (utils_1.IS_WINDOWS) {
return path.join(installDir, 'Scripts'); return path.join(installDir, 'Scripts');
} }
else { else {
@ -6720,7 +7066,7 @@ function binDir(installDir) {
function usePyPy(majorVersion, architecture) { function usePyPy(majorVersion, architecture) {
const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion); const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion);
let installDir = findPyPy(architecture); let installDir = findPyPy(architecture);
if (!installDir && IS_WINDOWS) { if (!installDir && utils_1.IS_WINDOWS) {
// PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64.
// On our Windows virtual environments, we only install an x86 version. // On our Windows virtual environments, we only install an x86 version.
// Fall back to x86. // Fall back to x86.
@ -6734,7 +7080,7 @@ function usePyPy(majorVersion, architecture) {
const _binDir = path.join(installDir, 'bin'); const _binDir = path.join(installDir, 'bin');
// On Linux and macOS, the Python interpreter is in 'bin'. // On Linux and macOS, the Python interpreter is in 'bin'.
// On Windows, it is in the installation root. // On Windows, it is in the installation root.
const pythonLocation = IS_WINDOWS ? installDir : _binDir; const pythonLocation = utils_1.IS_WINDOWS ? installDir : _binDir;
core.exportVariable('pythonLocation', pythonLocation); core.exportVariable('pythonLocation', pythonLocation);
core.addPath(installDir); core.addPath(installDir);
core.addPath(_binDir); core.addPath(_binDir);
@ -6768,7 +7114,7 @@ function useCpythonVersion(version, architecture) {
].join(os.EOL)); ].join(os.EOL));
} }
core.exportVariable('pythonLocation', installDir); core.exportVariable('pythonLocation', installDir);
if (IS_LINUX) { if (utils_1.IS_LINUX) {
const libPath = process.env.LD_LIBRARY_PATH const libPath = process.env.LD_LIBRARY_PATH
? `:${process.env.LD_LIBRARY_PATH}` ? `:${process.env.LD_LIBRARY_PATH}`
: ''; : '';
@ -6779,7 +7125,7 @@ function useCpythonVersion(version, architecture) {
} }
core.addPath(installDir); core.addPath(installDir);
core.addPath(binDir(installDir)); core.addPath(binDir(installDir));
if (IS_WINDOWS) { if (utils_1.IS_WINDOWS) {
// Add --user directory // Add --user directory
// `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/ // `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` // So if `findLocalTool` succeeded above, we must have a conformant `installDir`

131
src/find-pypy.ts Normal file
View file

@ -0,0 +1,131 @@
import * as path from 'path';
import * as pypyInstall from './install-pypy';
import {
IS_WINDOWS,
validateVersion,
getPyPyVersionFromPath,
readExactPyPyVersionFile,
validatePythonVersionFormatForPyPy
} from './utils';
import * as semver from 'semver';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
interface IPyPyVersionSpec {
pypyVersion: string;
pythonVersion: string;
}
export async function findPyPyVersion(
versionSpec: string,
architecture: string
): Promise<{resolvedPyPyVersion: string; resolvedPythonVersion: string}> {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
// PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64.
if (IS_WINDOWS && architecture === 'x64') {
architecture = 'x86';
}
({installDir, resolvedPythonVersion, resolvedPyPyVersion} = findPyPyToolCache(
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
architecture
));
if (!installDir) {
({
installDir,
resolvedPythonVersion,
resolvedPyPyVersion
} = await pypyInstall.installPyPy(
pypyVersionSpec.pypyVersion,
pypyVersionSpec.pythonVersion,
architecture
));
}
const pipDir = IS_WINDOWS ? 'Scripts' : 'bin';
const _binDir = path.join(installDir, pipDir);
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
core.exportVariable('pythonLocation', pythonLocation);
core.addPath(pythonLocation);
core.addPath(_binDir);
return {resolvedPyPyVersion, resolvedPythonVersion};
}
export function findPyPyToolCache(
pythonVersion: string,
pypyVersion: string,
architecture: string
) {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null = tc.find('PyPy', pythonVersion, architecture);
if (installDir) {
// 'tc.find' finds tool based on Python version but we also need to check
// whether PyPy version satisfies requested version.
resolvedPythonVersion = getPyPyVersionFromPath(installDir);
resolvedPyPyVersion = readExactPyPyVersionFile(installDir);
const isPyPyVersionSatisfies = semver.satisfies(
resolvedPyPyVersion,
pypyVersion
);
if (!isPyPyVersionSatisfies) {
installDir = null;
resolvedPyPyVersion = '';
resolvedPythonVersion = '';
}
}
if (!installDir) {
core.info(
`PyPy version ${pythonVersion} (${pypyVersion}) was not found in the local cache`
);
}
return {installDir, resolvedPythonVersion, resolvedPyPyVersion};
}
export function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec {
const versions = versionSpec.split('-').filter(item => !!item);
if (versions.length < 2 || versions[0] != 'pypy') {
throw new Error(
"Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See README for examples and documentation."
);
}
const pythonVersion = versions[1];
let pypyVersion: string;
if (versions.length > 2) {
pypyVersion = pypyInstall.pypyVersionToSemantic(versions[2]);
} else {
pypyVersion = 'x';
}
if (!validateVersion(pythonVersion) || !validateVersion(pypyVersion)) {
throw new Error(
"Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation."
);
}
if (!validatePythonVersionFormatForPyPy(pythonVersion)) {
throw new Error(
"Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation."
);
}
return {
pypyVersion: pypyVersion,
pythonVersion: pythonVersion
};
}

View file

@ -1,5 +1,6 @@
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import {IS_WINDOWS, IS_LINUX} from './utils';
import * as semver from 'semver'; import * as semver from 'semver';
@ -8,9 +9,6 @@ import * as installer from './install-python';
import * as core from '@actions/core'; 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_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.
// There is a seperate directory for `pip install --user`. // There is a seperate directory for `pip install --user`.

193
src/install-pypy.ts Normal file
View file

@ -0,0 +1,193 @@
import * as path from 'path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as semver from 'semver';
import * as httpm from '@actions/http-client';
import * as exec from '@actions/exec';
import fs from 'fs';
import {
IS_WINDOWS,
IPyPyManifestRelease,
createSymlinkInFolder,
isNightlyKeyword,
writeExactPyPyVersionFile
} from './utils';
export async function installPyPy(
pypyVersion: string,
pythonVersion: string,
architecture: string
) {
let downloadDir;
const releases = await getAvailablePyPyVersions();
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
const releaseData = findRelease(
releases,
pythonVersion,
pypyVersion,
architecture
);
if (!releaseData || !releaseData.foundAsset) {
throw new Error(
`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`
);
}
const {foundAsset, resolvedPythonVersion, resolvedPyPyVersion} = releaseData;
let downloadUrl = `${foundAsset.download_url}`;
core.info(`Downloading PyPy from "${downloadUrl}" ...`);
const pypyPath = await tc.downloadTool(downloadUrl);
core.info('Extracting downloaded archive...');
if (IS_WINDOWS) {
downloadDir = await tc.extractZip(pypyPath);
} else {
downloadDir = await tc.extractTar(pypyPath, undefined, 'x');
}
// root folder in archive can have unpredictable name so just take the first folder
// downloadDir is unique folder under TEMP and can't contain any other folders
const archiveName = fs.readdirSync(downloadDir)[0];
const toolDir = path.join(downloadDir, archiveName);
let installDir = toolDir;
if (!isNightlyKeyword(resolvedPyPyVersion)) {
installDir = await tc.cacheDir(
toolDir,
'PyPy',
resolvedPythonVersion,
architecture
);
}
writeExactPyPyVersionFile(installDir, resolvedPyPyVersion);
const binaryPath = getPyPyBinaryPath(installDir);
await createPyPySymlink(binaryPath, resolvedPythonVersion);
await installPip(binaryPath);
return {installDir, resolvedPythonVersion, resolvedPyPyVersion};
}
async function getAvailablePyPyVersions() {
const url = 'https://downloads.python.org/pypy/versions.json';
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');
const response = await http.getJson<IPyPyManifestRelease[]>(url);
if (!response.result) {
throw new Error(
`Unable to retrieve the list of available PyPy versions from '${url}'`
);
}
return response.result;
}
async function createPyPySymlink(
pypyBinaryPath: string,
pythonVersion: string
) {
const version = semver.coerce(pythonVersion)!;
const pythonBinaryPostfix = semver.major(version);
const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3';
let binaryExtension = IS_WINDOWS ? '.exe' : '';
core.info('Creating symlinks...');
createSymlinkInFolder(
pypyBinaryPath,
`pypy${pypyBinaryPostfix}${binaryExtension}`,
`python${pythonBinaryPostfix}${binaryExtension}`,
true
);
createSymlinkInFolder(
pypyBinaryPath,
`pypy${pypyBinaryPostfix}${binaryExtension}`,
`python${binaryExtension}`,
true
);
}
async function installPip(pythonLocation: string) {
core.info('Installing and updating pip');
const pythonBinary = path.join(pythonLocation, 'python');
await exec.exec(`${pythonBinary} -m ensurepip`);
await exec.exec(
`${pythonLocation}/python -m pip install --ignore-installed pip`
);
}
export function findRelease(
releases: IPyPyManifestRelease[],
pythonVersion: string,
pypyVersion: string,
architecture: string
) {
const filterReleases = releases.filter(item => {
const isPythonVersionSatisfied = semver.satisfies(
semver.coerce(item.python_version)!,
pythonVersion
);
const isPyPyNightly =
isNightlyKeyword(pypyVersion) && isNightlyKeyword(item.pypy_version);
const isPyPyVersionSatisfied =
isPyPyNightly ||
semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion);
const isArchPresent =
item.files &&
item.files.some(
file => file.arch === architecture && file.platform === process.platform
);
return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent;
});
if (filterReleases.length === 0) {
return null;
}
const sortedReleases = filterReleases.sort((previous, current) => {
return (
semver.compare(
semver.coerce(pypyVersionToSemantic(current.pypy_version))!,
semver.coerce(pypyVersionToSemantic(previous.pypy_version))!
) ||
semver.compare(
semver.coerce(current.python_version)!,
semver.coerce(previous.python_version)!
)
);
});
const foundRelease = sortedReleases[0];
const foundAsset = foundRelease.files.find(
item => item.arch === architecture && item.platform === process.platform
);
return {
foundAsset,
resolvedPythonVersion: foundRelease.python_version,
resolvedPyPyVersion: foundRelease.pypy_version
};
}
/** Get PyPy binary location from the tool of installation directory
* - On Linux and macOS, the Python interpreter is in 'bin'.
* - On Windows, it is in the installation root.
*/
export function getPyPyBinaryPath(installDir: string) {
const _binDir = path.join(installDir, 'bin');
return IS_WINDOWS ? installDir : _binDir;
}
export function pypyVersionToSemantic(versionSpec: string) {
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
return versionSpec.replace(prereleaseVersion, '$1-$2.$3');
}

View file

@ -3,7 +3,7 @@ import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import {ExecOptions} from '@actions/exec/lib/interfaces'; import {ExecOptions} from '@actions/exec/lib/interfaces';
import {stderr} from 'process'; import {IS_WINDOWS, IS_LINUX} from './utils';
const TOKEN = core.getInput('token'); const TOKEN = core.getInput('token');
const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`;
@ -12,9 +12,6 @@ const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main'; const MANIFEST_REPO_BRANCH = 'main';
export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const IS_WINDOWS = process.platform === 'win32';
const IS_LINUX = process.platform === 'linux';
export async function findReleaseFromManifest( export async function findReleaseFromManifest(
semanticVersionSpec: string, semanticVersionSpec: string,
architecture: string architecture: string

View file

@ -1,15 +1,29 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as finder from './find-python'; import * as finder from './find-python';
import * as finderPyPy from './find-pypy';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
function isPyPyVersion(versionSpec: string) {
return versionSpec.startsWith('pypy-');
}
async function run() { async function run() {
try { try {
let version = core.getInput('python-version'); let version = core.getInput('python-version');
if (version) { if (version) {
const arch: string = core.getInput('architecture') || os.arch(); const arch: string = core.getInput('architecture') || os.arch();
if (isPyPyVersion(version)) {
const installed = await finderPyPy.findPyPyVersion(version, arch);
core.info(
`Successfully setup PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`
);
} else {
const installed = await finder.findPythonVersion(version, arch); const installed = await 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')}`);

92
src/utils.ts Normal file
View file

@ -0,0 +1,92 @@
import fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
export const IS_WINDOWS = process.platform === 'win32';
export const IS_LINUX = process.platform === 'linux';
const PYPY_VERSION_FILE = 'PYPY_VERSION';
export interface IPyPyManifestAsset {
filename: string;
arch: string;
platform: string;
download_url: string;
}
export interface IPyPyManifestRelease {
pypy_version: string;
python_version: string;
stable: boolean;
latest_pypy: boolean;
files: IPyPyManifestAsset[];
}
/** create Symlinks for downloaded PyPy
* It should be executed only for downloaded versions in runtime, because
* toolcache versions have this setup.
*/
export function createSymlinkInFolder(
folderPath: string,
sourceName: string,
targetName: string,
setExecutable = false
) {
const sourcePath = path.join(folderPath, sourceName);
const targetPath = path.join(folderPath, targetName);
if (fs.existsSync(targetPath)) {
return;
}
fs.symlinkSync(sourcePath, targetPath);
if (!IS_WINDOWS && setExecutable) {
fs.chmodSync(targetPath, '755');
}
}
export function validateVersion(version: string) {
return isNightlyKeyword(version) || Boolean(semver.validRange(version));
}
export function isNightlyKeyword(pypyVersion: string) {
return pypyVersion === 'nightly';
}
export function getPyPyVersionFromPath(installDir: string) {
return path.basename(path.dirname(installDir));
}
/**
* In tool-cache, we put PyPy to '<toolcache_root>/PyPy/<python_version>/x64'
* There is no easy way to determine what PyPy version is located in specific folder
* 'pypy --version' is not reliable enough since it is not set properly for preview versions
* "7.3.3rc1" is marked as '7.3.3' in 'pypy --version'
* so we put PYPY_VERSION file to PyPy directory when install it to VM and read it when we need to know version
* PYPY_VERSION contains exact version from 'versions.json'
*/
export function readExactPyPyVersionFile(installDir: string) {
let pypyVersion = '';
let fileVersion = path.join(installDir, PYPY_VERSION_FILE);
if (fs.existsSync(fileVersion)) {
pypyVersion = fs.readFileSync(fileVersion).toString();
}
return pypyVersion;
}
export function writeExactPyPyVersionFile(
installDir: string,
resolvedPyPyVersion: string
) {
const pypyFilePath = path.join(installDir, PYPY_VERSION_FILE);
fs.writeFileSync(pypyFilePath, resolvedPyPyVersion);
}
/**
* Python version should be specified explicitly like "x.y" (2.7, 3.6, 3.7)
* "3.x" or "3" are not supported
* because it could cause ambiguity when both PyPy version and Python version are not precise
*/
export function validatePythonVersionFormatForPyPy(version: string) {
const re = /^\d+\.\d+$/;
return re.test(version);
}