mirror of
				https://git.mirrors.martin98.com/https://github.com/actions/setup-python
				synced 2025-10-31 08:51:06 +08:00 
			
		
		
		
	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:
		
							parent
							
								
									2831efe49a
								
							
						
					
					
						commit
						8c5ea631b2
					
				
							
								
								
									
										47
									
								
								.github/workflows/test-pypy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								.github/workflows/test-pypy.yml
									
									
									
									
										vendored
									
									
										Normal 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))' | ||||
| @ -1,4 +1,4 @@ | ||||
| name: Validate 'setup-python' | ||||
| name: Validate Python e2e | ||||
| on:  | ||||
|   push: | ||||
|     branches: | ||||
| @ -9,7 +9,7 @@ on: | ||||
|     paths-ignore: | ||||
|       - '**.md' | ||||
|   schedule: | ||||
|     - cron: 0 0 * * * | ||||
|     - cron: 30 3 * * * | ||||
| 
 | ||||
| jobs: | ||||
|   default-version: | ||||
| @ -18,7 +18,7 @@ jobs: | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       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: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v2 | ||||
| @ -38,7 +38,7 @@ jobs: | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       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] | ||||
|     steps: | ||||
|     - name: Checkout | ||||
| @ -68,7 +68,7 @@ jobs: | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       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: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v2 | ||||
| @ -91,13 +91,13 @@ jobs: | ||||
|     - name: Run simple code | ||||
|       run: python -c 'import math; print(math.factorial(5))' | ||||
| 
 | ||||
|   setup-pypy: | ||||
|   setup-pypy-legacy: | ||||
|     name: Setup PyPy ${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       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: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v2 | ||||
							
								
								
									
										57
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								README.md
									
									
									
									
									
								
							| @ -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. | ||||
| - Automatic setup and download of Python packages if using a self-hosted runner. | ||||
| - Support for pre-release versions of Python. | ||||
| - Support for installing any version of PyPy on-flight | ||||
| 
 | ||||
| # Usage | ||||
| 
 | ||||
| @ -40,7 +41,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       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 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
| @ -60,7 +61,7 @@ jobs: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         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: | ||||
|           - os: macos-latest | ||||
|             python-version: 3.8 | ||||
| @ -91,7 +92,6 @@ jobs: | ||||
|       with: | ||||
|         python-version: ${{ matrix.python-version }} | ||||
|     - run: python my_script.py | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| Download and set up an accurate pre-release version of Python: | ||||
| @ -114,6 +114,27 @@ steps: | ||||
| - 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 | ||||
| 
 | ||||
| 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. | ||||
| - 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. | ||||
|     - 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 | ||||
| 
 | ||||
| @ -155,6 +190,20 @@ You should specify only a major and minor version if you are okay with the most | ||||
|   - There will be a single patch version already installed on each runner for every minor version of Python that is supported. | ||||
|   - 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. | ||||
|    | ||||
| # 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 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										494
									
								
								__tests__/data/pypy.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										494
									
								
								__tests__/data/pypy.json
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										237
									
								
								__tests__/find-pypy.test.ts
									
									
									
									
									
										Normal 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` | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										230
									
								
								__tests__/install-pypy.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								__tests__/install-pypy.test.ts
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										34
									
								
								__tests__/utils.test.ts
									
									
									
									
									
										Normal 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); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										374
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										374
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @ -1072,6 +1072,113 @@ function _readLinuxVersionFile() { | ||||
| exports._readLinuxVersionFile = _readLinuxVersionFile; | ||||
| //# 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: | ||||
| @ -2197,6 +2304,92 @@ if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { | ||||
| 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: | ||||
| @ -2443,16 +2636,26 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const finder = __importStar(__webpack_require__(927)); | ||||
| const finderPyPy = __importStar(__webpack_require__(50)); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const os = __importStar(__webpack_require__(87)); | ||||
| function isPyPyVersion(versionSpec) { | ||||
|     return versionSpec.startsWith('pypy-'); | ||||
| } | ||||
| function run() { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         try { | ||||
|             let version = core.getInput('python-version'); | ||||
|             if (version) { | ||||
|                 const arch = core.getInput('architecture') || os.arch(); | ||||
|                 const installed = yield finder.findPythonVersion(version, arch); | ||||
|                 core.info(`Successfully setup ${installed.impl} (${installed.version})`); | ||||
|                 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); | ||||
|                     core.info(`Successfully setup ${installed.impl} (${installed.version})`); | ||||
|                 } | ||||
|             } | ||||
|             const matchersPath = path.join(__dirname, '..', '.github'); | ||||
|             core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); | ||||
| @ -2580,6 +2783,151 @@ module.exports = ltr | ||||
| 
 | ||||
| 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: | ||||
| @ -6426,14 +6774,13 @@ const path = __importStar(__webpack_require__(622)); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| const tc = __importStar(__webpack_require__(533)); | ||||
| const exec = __importStar(__webpack_require__(986)); | ||||
| const utils_1 = __webpack_require__(163); | ||||
| const TOKEN = core.getInput('token'); | ||||
| const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | ||||
| const MANIFEST_REPO_OWNER = 'actions'; | ||||
| const MANIFEST_REPO_NAME = 'python-versions'; | ||||
| const MANIFEST_REPO_BRANCH = 'main'; | ||||
| 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) { | ||||
|     return __awaiter(this, void 0, void 0, function* () { | ||||
|         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* () { | ||||
|         const options = { | ||||
|             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, | ||||
|             listeners: { | ||||
|                 stdout: (data) => { | ||||
| @ -6456,7 +6803,7 @@ function installPython(workingDirectory) { | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         if (IS_WINDOWS) { | ||||
|         if (utils_1.IS_WINDOWS) { | ||||
|             yield exec.exec('powershell', ['./setup.ps1'], options); | ||||
|         } | ||||
|         else { | ||||
| @ -6471,7 +6818,7 @@ function installCpythonFromRelease(release) { | ||||
|         const pythonPath = yield tc.downloadTool(downloadUrl, undefined, AUTH); | ||||
|         core.info('Extract downloaded archive'); | ||||
|         let pythonExtractedFolder; | ||||
|         if (IS_WINDOWS) { | ||||
|         if (utils_1.IS_WINDOWS) { | ||||
|             pythonExtractedFolder = yield tc.extractZip(pythonPath); | ||||
|         } | ||||
|         else { | ||||
| @ -6686,12 +7033,11 @@ var __importStar = (this && this.__importStar) || function (mod) { | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const os = __importStar(__webpack_require__(87)); | ||||
| const path = __importStar(__webpack_require__(622)); | ||||
| const utils_1 = __webpack_require__(163); | ||||
| const semver = __importStar(__webpack_require__(876)); | ||||
| const installer = __importStar(__webpack_require__(824)); | ||||
| const core = __importStar(__webpack_require__(470)); | ||||
| 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.
 | ||||
| // This is where pip is, along with anything that pip installs.
 | ||||
| // There is a seperate directory for `pip install --user`.
 | ||||
| @ -6705,7 +7051,7 @@ const IS_LINUX = process.platform === 'linux'; | ||||
| //      (--user) %APPDATA%\Python\PythonXY\Scripts
 | ||||
| // See https://docs.python.org/3/library/sysconfig.html
 | ||||
| function binDir(installDir) { | ||||
|     if (IS_WINDOWS) { | ||||
|     if (utils_1.IS_WINDOWS) { | ||||
|         return path.join(installDir, 'Scripts'); | ||||
|     } | ||||
|     else { | ||||
| @ -6720,7 +7066,7 @@ function binDir(installDir) { | ||||
| function usePyPy(majorVersion, architecture) { | ||||
|     const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion); | ||||
|     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.
 | ||||
|         // On our Windows virtual environments, we only install an x86 version.
 | ||||
|         // Fall back to x86.
 | ||||
| @ -6734,7 +7080,7 @@ function usePyPy(majorVersion, architecture) { | ||||
|     const _binDir = path.join(installDir, 'bin'); | ||||
|     // On Linux and macOS, the Python interpreter is in 'bin'.
 | ||||
|     // 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.addPath(installDir); | ||||
|     core.addPath(_binDir); | ||||
| @ -6768,7 +7114,7 @@ function useCpythonVersion(version, architecture) { | ||||
|             ].join(os.EOL)); | ||||
|         } | ||||
|         core.exportVariable('pythonLocation', installDir); | ||||
|         if (IS_LINUX) { | ||||
|         if (utils_1.IS_LINUX) { | ||||
|             const libPath = process.env.LD_LIBRARY_PATH | ||||
|                 ? `:${process.env.LD_LIBRARY_PATH}` | ||||
|                 : ''; | ||||
| @ -6779,7 +7125,7 @@ function useCpythonVersion(version, architecture) { | ||||
|         } | ||||
|         core.addPath(installDir); | ||||
|         core.addPath(binDir(installDir)); | ||||
|         if (IS_WINDOWS) { | ||||
|         if (utils_1.IS_WINDOWS) { | ||||
|             // Add --user directory
 | ||||
|             // `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`
 | ||||
|  | ||||
							
								
								
									
										131
									
								
								src/find-pypy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/find-pypy.ts
									
									
									
									
									
										Normal 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 | ||||
|   }; | ||||
| } | ||||
| @ -1,5 +1,6 @@ | ||||
| import * as os from 'os'; | ||||
| import * as path from 'path'; | ||||
| import {IS_WINDOWS, IS_LINUX} from './utils'; | ||||
| 
 | ||||
| import * as semver from 'semver'; | ||||
| 
 | ||||
| @ -8,9 +9,6 @@ import * as installer from './install-python'; | ||||
| import * as core from '@actions/core'; | ||||
| 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.
 | ||||
| // This is where pip is, along with anything that pip installs.
 | ||||
| // There is a seperate directory for `pip install --user`.
 | ||||
|  | ||||
							
								
								
									
										193
									
								
								src/install-pypy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/install-pypy.ts
									
									
									
									
									
										Normal 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'); | ||||
| } | ||||
| @ -3,7 +3,7 @@ import * as core from '@actions/core'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import {ExecOptions} from '@actions/exec/lib/interfaces'; | ||||
| import {stderr} from 'process'; | ||||
| import {IS_WINDOWS, IS_LINUX} from './utils'; | ||||
| 
 | ||||
| const TOKEN = core.getInput('token'); | ||||
| const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | ||||
| @ -12,9 +12,6 @@ const MANIFEST_REPO_NAME = 'python-versions'; | ||||
| 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`; | ||||
| 
 | ||||
| const IS_WINDOWS = process.platform === 'win32'; | ||||
| const IS_LINUX = process.platform === 'linux'; | ||||
| 
 | ||||
| export async function findReleaseFromManifest( | ||||
|   semanticVersionSpec: string, | ||||
|   architecture: string | ||||
|  | ||||
| @ -1,15 +1,29 @@ | ||||
| import * as core from '@actions/core'; | ||||
| import * as finder from './find-python'; | ||||
| import * as finderPyPy from './find-pypy'; | ||||
| import * as path from 'path'; | ||||
| import * as os from 'os'; | ||||
| 
 | ||||
| function isPyPyVersion(versionSpec: string) { | ||||
|   return versionSpec.startsWith('pypy-'); | ||||
| } | ||||
| 
 | ||||
| async function run() { | ||||
|   try { | ||||
|     let version = core.getInput('python-version'); | ||||
|     if (version) { | ||||
|       const arch: string = core.getInput('architecture') || os.arch(); | ||||
|       const installed = await finder.findPythonVersion(version, arch); | ||||
|       core.info(`Successfully setup ${installed.impl} (${installed.version})`); | ||||
|       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); | ||||
|         core.info( | ||||
|           `Successfully setup ${installed.impl} (${installed.version})` | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|     const matchersPath = path.join(__dirname, '..', '.github'); | ||||
|     core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); | ||||
|  | ||||
							
								
								
									
										92
									
								
								src/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/utils.ts
									
									
									
									
									
										Normal 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); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dmitry Shibanov
						Dmitry Shibanov