mirror of
				https://gitea.com/actions/setup-python.git
				synced 2025-10-31 07:47:08 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8c5ea631b2 | ||
|   | 2831efe49a | ||
|   | 3b3f2de1b1 | ||
|   | 723e46dad7 | ||
|   | 195f5c388b | ||
|   | 41b7212b16 | ||
|   | 878156f1de | 
							
								
								
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | * @actions/actions-service | ||||||
							
								
								
									
										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:  | on:  | ||||||
|   push: |   push: | ||||||
|     branches: |     branches: | ||||||
| @@ -9,7 +9,7 @@ on: | |||||||
|     paths-ignore: |     paths-ignore: | ||||||
|       - '**.md' |       - '**.md' | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: 0 0 * * * |     - cron: 30 3 * * * | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   default-version: |   default-version: | ||||||
| @@ -18,7 +18,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] |         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout |     - name: Checkout | ||||||
|       uses: actions/checkout@v2 |       uses: actions/checkout@v2 | ||||||
| @@ -38,7 +38,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] |         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] | ||||||
|         python: [3.5.4, 3.6.7, 3.7.5, 3.8.1] |         python: [3.5.4, 3.6.7, 3.7.5, 3.8.1] | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout |     - name: Checkout | ||||||
| @@ -68,7 +68,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04] |         os: [macos-latest, windows-latest, ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout |     - name: Checkout | ||||||
|       uses: actions/checkout@v2 |       uses: actions/checkout@v2 | ||||||
| @@ -90,3 +90,24 @@ jobs: | |||||||
| 
 | 
 | ||||||
|     - name: Run simple code |     - name: Run simple code | ||||||
|       run: python -c 'import math; print(math.factorial(5))' |       run: python -c 'import math; print(math.factorial(5))' | ||||||
|  | 
 | ||||||
|  |   setup-pypy-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, ubuntu-20.04] | ||||||
|  |     steps: | ||||||
|  |     - name: Checkout | ||||||
|  |       uses: actions/checkout@v2 | ||||||
|  | 
 | ||||||
|  |     - name: setup-python pypy3 | ||||||
|  |       uses: ./ | ||||||
|  |       with: | ||||||
|  |         python-version: 'pypy3' | ||||||
|  | 
 | ||||||
|  |     - name: setup-python pypy2 | ||||||
|  |       uses: ./ | ||||||
|  |       with: | ||||||
|  |         python-version: 'pypy2' | ||||||
							
								
								
									
										93
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								README.md
									
									
									
									
									
								
							| @@ -6,17 +6,18 @@ | |||||||
|  |  | ||||||
| This action sets up a Python environment for use in actions by: | This action sets up a Python environment for use in actions by: | ||||||
|  |  | ||||||
| - optionally installing and adding to PATH a version of Python that is already installed in the tools cache | - optionally installing and adding to PATH a version of Python that is already installed in the tools cache. | ||||||
| - downloading, installing and adding to PATH an available version of Python from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)) if a specific version is not available in the tools cache | - downloading, installing and adding to PATH an available version of Python from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)) if a specific version is not available in the tools cache. | ||||||
| - failing if a specific version of Python is not preinstalled or available for download | - failing if a specific version of Python is not preinstalled or available for download. | ||||||
| - registering problem matchers for error output | - registering problem matchers for error output. | ||||||
|  |  | ||||||
| # What's new | # What's new | ||||||
|  |  | ||||||
| - Ability to download, install and set up Python packages from `actions/python-versions` that do not come preinstalled on runners | - Ability to download, install and set up Python packages from `actions/python-versions` that do not come preinstalled on runners. | ||||||
|   - Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed |   - Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed. | ||||||
| - Automatic setup and download of Python packages if using a self-hosted runner | - Automatic setup and download of Python packages if using a self-hosted runner. | ||||||
| - Support for pre-release versions of Python | - Support for pre-release versions of Python. | ||||||
|  | - Support for installing any version of PyPy on-flight | ||||||
|  |  | ||||||
| # Usage | # Usage | ||||||
|  |  | ||||||
| @@ -40,7 +41,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         python-version: [ '2.x', '3.x', 'pypy2', 'pypy3' ] |         python-version: [ '2.x', '3.x', 'pypy-2.7', 'pypy-3.6', 'pypy-3.7' ] | ||||||
|     name: Python ${{ matrix.python-version }} sample |     name: Python ${{ matrix.python-version }} sample | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
| @@ -60,7 +61,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         os: [ubuntu-latest, macos-latest, windows-latest] |         os: [ubuntu-latest, macos-latest, windows-latest] | ||||||
|         python-version: [2.7, 3.6, 3.7, 3.8, pypy2, pypy3] |         python-version: [2.7, 3.6, 3.7, 3.8, pypy-2.7, pypy-3.6] | ||||||
|         exclude: |         exclude: | ||||||
|           - os: macos-latest |           - os: macos-latest | ||||||
|             python-version: 3.8 |             python-version: 3.8 | ||||||
| @@ -91,7 +92,6 @@ jobs: | |||||||
|       with: |       with: | ||||||
|         python-version: ${{ matrix.python-version }} |         python-version: ${{ matrix.python-version }} | ||||||
|     - run: python my_script.py |     - run: python my_script.py | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Download and set up an accurate pre-release version of Python: | Download and set up an accurate pre-release version of Python: | ||||||
| @@ -114,6 +114,27 @@ steps: | |||||||
| - run: python my_script.py | - run: python my_script.py | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | Download and set up PyPy:   | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         python-version: | ||||||
|  |         - pypy-3.6 # the latest available version of PyPy that supports Python 3.6 | ||||||
|  |         - pypy-3.7 # the latest available version of PyPy that supports Python 3.7 | ||||||
|  |         - pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3 | ||||||
|  |     steps: | ||||||
|  |     - uses: actions/checkout@v2 | ||||||
|  |     - uses: actions/setup-python@v2 | ||||||
|  |       with: | ||||||
|  |         python-version: ${{ matrix.python-version }} | ||||||
|  |     - run: python my_script.py | ||||||
|  | ``` | ||||||
|  | More details on PyPy syntax and examples of using preview / nightly versions of PyPy can be found in the [Available versions of PyPy](#available-versions-of-pypy) section. | ||||||
|  |  | ||||||
| # Getting started with Python + Actions | # Getting started with Python + Actions | ||||||
|  |  | ||||||
| Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions). | Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions). | ||||||
| @@ -122,14 +143,28 @@ Check out our detailed guide on using [Python with GitHub Actions](https://help. | |||||||
|  |  | ||||||
| `setup-python` is able to configure Python from two sources: | `setup-python` is able to configure Python from two sources: | ||||||
|  |  | ||||||
| - Preinstalled versions of Python in the tools cache on GitHub-hosted runners | - Preinstalled versions of Python in the tools cache on GitHub-hosted runners. | ||||||
|     - For detailed information regarding the available versions of Python that are installed see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software). |     - For detailed information regarding the available versions of Python that are installed see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software). | ||||||
|     - For every minor version of Python, expect only the latest patch to be preinstalled. |     - For every minor version of Python, expect only the latest patch to be preinstalled. | ||||||
|     - If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tools cache. |     - If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tools cache. | ||||||
|     - If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache. |     - If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache. | ||||||
| - Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)) | - Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)). | ||||||
|     - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file. |     - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file. | ||||||
|     - If there is a specific version of Python that is not available, you can open an issue here |     - If there is a specific version of Python that is not available, you can open an issue here | ||||||
|  |      | ||||||
|  |  # Available versions of PyPy | ||||||
|  |   | ||||||
|  |  `setup-python` is able to configure PyPy from two sources: | ||||||
|  |   | ||||||
|  | - Preinstalled versions of PyPy in the tools cache on GitHub-hosted runners | ||||||
|  |   - For detailed information regarding the available versions of PyPy that are installed see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software). | ||||||
|  |   - For the latest PyPy release, all versions of Python are cached. | ||||||
|  |   - Cache is updated with a 1-2 week delay. If you specify the PyPy version as `pypy-3.6`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy-3.6-v7.3.3`. | ||||||
|  |  | ||||||
|  | - Downloadable PyPy versions from the [official PyPy site](https://downloads.python.org/pypy/). | ||||||
|  |   - All available versions that we can download are listed in [versions.json](https://downloads.python.org/pypy/versions.json) file. | ||||||
|  |   - PyPy < 7.3.3 are not available to install on-flight. | ||||||
|  |   - If some versions are not available, you can open an issue in https://foss.heptapod.net/pypy/pypy/ | ||||||
|  |  | ||||||
| # Hosted Tool Cache | # Hosted Tool Cache | ||||||
|  |  | ||||||
| @@ -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. |   - 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. |   - The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced. | ||||||
|   - Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version Python on the runner will be used. |   - Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version Python on the runner will be used. | ||||||
|  |    | ||||||
|  | # Specifying a PyPy version | ||||||
|  | The version of PyPy should be specified in the format `pypy-<python_version>[-v<pypy_version>]`.   | ||||||
|  | The `<pypy_version>` parameter is optional and can be skipped. The latest version will be used in this case. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | pypy-3.6 # the latest available version of PyPy that supports Python 3.6 | ||||||
|  | pypy-3.7 # the latest available version of PyPy that supports Python 3.7 | ||||||
|  | pypy-2.7 # the latest available version of PyPy that supports Python 2.7 | ||||||
|  | pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3 | ||||||
|  | pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x | ||||||
|  | pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy | ||||||
|  | pypy-3.7-nightly # Python 3.7 and nightly PyPy | ||||||
|  | ``` | ||||||
|  |  | ||||||
| # Using `setup-python` with a self hosted runner | # Using `setup-python` with a self hosted runner | ||||||
|  |  | ||||||
| @@ -174,22 +223,22 @@ If you are experiencing problems while configuring Python on your self-hosted ru | |||||||
|  |  | ||||||
| - The Python packages that are downloaded from `actions/python-versions` are originally compiled from source in `/opt/hostedtoolcache/` with the [--enable-shared](https://github.com/actions/python-versions/blob/94f04ae6806c6633c82db94c6406a16e17decd5c/builders/ubuntu-python-builder.psm1#L35) flag, which makes them non-relocatable. | - The Python packages that are downloaded from `actions/python-versions` are originally compiled from source in `/opt/hostedtoolcache/` with the [--enable-shared](https://github.com/actions/python-versions/blob/94f04ae6806c6633c82db94c6406a16e17decd5c/builders/ubuntu-python-builder.psm1#L35) flag, which makes them non-relocatable. | ||||||
| - Create an environment variable called `AGENT_TOOLSDIRECTORY` and set it to `/opt/hostedtoolcache`. This controls where the runner downloads and installs tools. | - Create an environment variable called `AGENT_TOOLSDIRECTORY` and set it to `/opt/hostedtoolcache`. This controls where the runner downloads and installs tools. | ||||||
|   - In the same shell that your runner is using, type `export AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache` |   - In the same shell that your runner is using, type `export AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. | ||||||
|   - A more permanent way of setting the environment variable is to create a `.env` file in the same directory as your runner and to add `AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. This ensures the variable is always set if your runner is configured as a service. |   - A more permanent way of setting the environment variable is to create a `.env` file in the same directory as your runner and to add `AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. This ensures the variable is always set if your runner is configured as a service. | ||||||
| - Create a directory called `hostedtoolcache` inside `/opt`. | - Create a directory called `hostedtoolcache` inside `/opt`. | ||||||
| - The user starting the runner must have write permission to the `/opt/hostedtoolcache` directory. It is not possible to start the Linux runner with `sudo` and the `/opt` directory usually requires root privileges to write to. Check the current user and group that the runner belongs to by typing `ls -l` inside the runners root directory. | - The user starting the runner must have write permission to the `/opt/hostedtoolcache` directory. It is not possible to start the Linux runner with `sudo` and the `/opt` directory usually requires root privileges to write to. Check the current user and group that the runner belongs to by typing `ls -l` inside the runners root directory. | ||||||
| - The runner can be granted write access to the `/opt/hostedtoolcache` directory using a few techniques: | - The runner can be granted write access to the `/opt/hostedtoolcache` directory using a few techniques: | ||||||
|   - The user starting the runner is the owner, and the owner has write permission |   - The user starting the runner is the owner, and the owner has write permission. | ||||||
|   - The user starting the runner is in the owning group, and the owning group has write permission |   - The user starting the runner is in the owning group, and the owning group has write permission. | ||||||
|   - All users have write permission |   - All users have write permission. | ||||||
| - One quick way to grant access is to change the user and group of `/opt/hostedtoolcache` to be the same as the runners using `chown` | - One quick way to grant access is to change the user and group of `/opt/hostedtoolcache` to be the same as the runners using `chown`. | ||||||
|     - `sudo chown runner-user:runner-group opt/hostedtoolcache/` |     - `sudo chown runner-user:runner-group opt/hostedtoolcache/`. | ||||||
| - If your runner is configured as a service and you run into problems, make sure the user that the service is running as is correct. For more information, you can [check the status of your self-hosted runner](https://help.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service#checking-the-status-of-the-service). | - If your runner is configured as a service and you run into problems, make sure the user that the service is running as is correct. For more information, you can [check the status of your self-hosted runner](https://help.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service#checking-the-status-of-the-service). | ||||||
|  |  | ||||||
| ### Mac | ### Mac | ||||||
|  |  | ||||||
| - The same setup that applies to `Linux` also applies to `Mac`, just with a different tools cache directory. | - The same setup that applies to `Linux` also applies to `Mac`, just with a different tools cache directory. | ||||||
| - Create a directory called `/Users/runner/hostedtoolcache` | - Create a directory called `/Users/runner/hostedtoolcache`. | ||||||
| - Set the `AGENT_TOOLSDIRECTORY` environment variable to `/Users/runner/hostedtoolcache`. | - Set the `AGENT_TOOLSDIRECTORY` environment variable to `/Users/runner/hostedtoolcache`. | ||||||
| - Change the permissions of `/Users/runner/hostedtoolcache` so that the runner has write access. | - Change the permissions of `/Users/runner/hostedtoolcache` so that the runner has write access. | ||||||
|  |  | ||||||
| @@ -200,8 +249,8 @@ If you are experiencing problems while configuring Python on your self-hosted ru | |||||||
|  |  | ||||||
| # License | # License | ||||||
|  |  | ||||||
| The scripts and documentation in this project are released under the [MIT License](LICENSE) | The scripts and documentation in this project are released under the [MIT License](LICENSE). | ||||||
|  |  | ||||||
| # Contributions | # Contributions | ||||||
|  |  | ||||||
| Contributions are welcome! See our [Contributor's Guide](docs/contributors.md) | Contributions are welcome! See our [Contributor's Guide](docs/contributors.md). | ||||||
|   | |||||||
							
								
								
									
										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); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										390
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										390
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @@ -1072,6 +1072,113 @@ function _readLinuxVersionFile() { | |||||||
| exports._readLinuxVersionFile = _readLinuxVersionFile; | exports._readLinuxVersionFile = _readLinuxVersionFile; | ||||||
| //# sourceMappingURL=manifest.js.map | //# sourceMappingURL=manifest.js.map | ||||||
|  |  | ||||||
|  | /***/ }), | ||||||
|  |  | ||||||
|  | /***/ 50: | ||||||
|  | /***/ (function(__unusedmodule, exports, __webpack_require__) { | ||||||
|  |  | ||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||||||
|  |     function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||||||
|  |     return new (P || (P = Promise))(function (resolve, reject) { | ||||||
|  |         function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||||||
|  |         function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||||||
|  |         function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||||||
|  |         step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | var __importStar = (this && this.__importStar) || function (mod) { | ||||||
|  |     if (mod && mod.__esModule) return mod; | ||||||
|  |     var result = {}; | ||||||
|  |     if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||||||
|  |     result["default"] = mod; | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const pypyInstall = __importStar(__webpack_require__(369)); | ||||||
|  | const utils_1 = __webpack_require__(163); | ||||||
|  | const semver = __importStar(__webpack_require__(876)); | ||||||
|  | const core = __importStar(__webpack_require__(470)); | ||||||
|  | const tc = __importStar(__webpack_require__(533)); | ||||||
|  | function findPyPyVersion(versionSpec, architecture) { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         let resolvedPyPyVersion = ''; | ||||||
|  |         let resolvedPythonVersion = ''; | ||||||
|  |         let installDir; | ||||||
|  |         const pypyVersionSpec = parsePyPyVersion(versionSpec); | ||||||
|  |         // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. | ||||||
|  |         if (utils_1.IS_WINDOWS && architecture === 'x64') { | ||||||
|  |             architecture = 'x86'; | ||||||
|  |         } | ||||||
|  |         ({ installDir, resolvedPythonVersion, resolvedPyPyVersion } = findPyPyToolCache(pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture)); | ||||||
|  |         if (!installDir) { | ||||||
|  |             ({ | ||||||
|  |                 installDir, | ||||||
|  |                 resolvedPythonVersion, | ||||||
|  |                 resolvedPyPyVersion | ||||||
|  |             } = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture)); | ||||||
|  |         } | ||||||
|  |         const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin'; | ||||||
|  |         const _binDir = path.join(installDir, pipDir); | ||||||
|  |         const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir); | ||||||
|  |         core.exportVariable('pythonLocation', pythonLocation); | ||||||
|  |         core.addPath(pythonLocation); | ||||||
|  |         core.addPath(_binDir); | ||||||
|  |         return { resolvedPyPyVersion, resolvedPythonVersion }; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | exports.findPyPyVersion = findPyPyVersion; | ||||||
|  | function findPyPyToolCache(pythonVersion, pypyVersion, architecture) { | ||||||
|  |     let resolvedPyPyVersion = ''; | ||||||
|  |     let resolvedPythonVersion = ''; | ||||||
|  |     let installDir = tc.find('PyPy', pythonVersion, architecture); | ||||||
|  |     if (installDir) { | ||||||
|  |         // 'tc.find' finds tool based on Python version but we also need to check | ||||||
|  |         // whether PyPy version satisfies requested version. | ||||||
|  |         resolvedPythonVersion = utils_1.getPyPyVersionFromPath(installDir); | ||||||
|  |         resolvedPyPyVersion = utils_1.readExactPyPyVersionFile(installDir); | ||||||
|  |         const isPyPyVersionSatisfies = semver.satisfies(resolvedPyPyVersion, pypyVersion); | ||||||
|  |         if (!isPyPyVersionSatisfies) { | ||||||
|  |             installDir = null; | ||||||
|  |             resolvedPyPyVersion = ''; | ||||||
|  |             resolvedPythonVersion = ''; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if (!installDir) { | ||||||
|  |         core.info(`PyPy version ${pythonVersion} (${pypyVersion}) was not found in the local cache`); | ||||||
|  |     } | ||||||
|  |     return { installDir, resolvedPythonVersion, resolvedPyPyVersion }; | ||||||
|  | } | ||||||
|  | exports.findPyPyToolCache = findPyPyToolCache; | ||||||
|  | function parsePyPyVersion(versionSpec) { | ||||||
|  |     const versions = versionSpec.split('-').filter(item => !!item); | ||||||
|  |     if (versions.length < 2 || versions[0] != 'pypy') { | ||||||
|  |         throw new Error("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See README for examples and documentation."); | ||||||
|  |     } | ||||||
|  |     const pythonVersion = versions[1]; | ||||||
|  |     let pypyVersion; | ||||||
|  |     if (versions.length > 2) { | ||||||
|  |         pypyVersion = pypyInstall.pypyVersionToSemantic(versions[2]); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         pypyVersion = 'x'; | ||||||
|  |     } | ||||||
|  |     if (!utils_1.validateVersion(pythonVersion) || !utils_1.validateVersion(pypyVersion)) { | ||||||
|  |         throw new Error("Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation."); | ||||||
|  |     } | ||||||
|  |     if (!utils_1.validatePythonVersionFormatForPyPy(pythonVersion)) { | ||||||
|  |         throw new Error("Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation."); | ||||||
|  |     } | ||||||
|  |     return { | ||||||
|  |         pypyVersion: pypyVersion, | ||||||
|  |         pythonVersion: pythonVersion | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | exports.parsePyPyVersion = parsePyPyVersion; | ||||||
|  |  | ||||||
|  |  | ||||||
| /***/ }), | /***/ }), | ||||||
|  |  | ||||||
| /***/ 65: | /***/ 65: | ||||||
| @@ -2197,6 +2304,92 @@ if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { | |||||||
| exports.debug = debug; // for test | exports.debug = debug; // for test | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /***/ }), | ||||||
|  |  | ||||||
|  | /***/ 163: | ||||||
|  | /***/ (function(__unusedmodule, exports, __webpack_require__) { | ||||||
|  |  | ||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | var __importDefault = (this && this.__importDefault) || function (mod) { | ||||||
|  |     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||||
|  | }; | ||||||
|  | var __importStar = (this && this.__importStar) || function (mod) { | ||||||
|  |     if (mod && mod.__esModule) return mod; | ||||||
|  |     var result = {}; | ||||||
|  |     if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||||||
|  |     result["default"] = mod; | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const fs_1 = __importDefault(__webpack_require__(747)); | ||||||
|  | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const semver = __importStar(__webpack_require__(876)); | ||||||
|  | exports.IS_WINDOWS = process.platform === 'win32'; | ||||||
|  | exports.IS_LINUX = process.platform === 'linux'; | ||||||
|  | const PYPY_VERSION_FILE = 'PYPY_VERSION'; | ||||||
|  | /** create Symlinks for downloaded PyPy | ||||||
|  |  *  It should be executed only for downloaded versions in runtime, because | ||||||
|  |  *  toolcache versions have this setup. | ||||||
|  |  */ | ||||||
|  | function createSymlinkInFolder(folderPath, sourceName, targetName, setExecutable = false) { | ||||||
|  |     const sourcePath = path.join(folderPath, sourceName); | ||||||
|  |     const targetPath = path.join(folderPath, targetName); | ||||||
|  |     if (fs_1.default.existsSync(targetPath)) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     fs_1.default.symlinkSync(sourcePath, targetPath); | ||||||
|  |     if (!exports.IS_WINDOWS && setExecutable) { | ||||||
|  |         fs_1.default.chmodSync(targetPath, '755'); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | exports.createSymlinkInFolder = createSymlinkInFolder; | ||||||
|  | function validateVersion(version) { | ||||||
|  |     return isNightlyKeyword(version) || Boolean(semver.validRange(version)); | ||||||
|  | } | ||||||
|  | exports.validateVersion = validateVersion; | ||||||
|  | function isNightlyKeyword(pypyVersion) { | ||||||
|  |     return pypyVersion === 'nightly'; | ||||||
|  | } | ||||||
|  | exports.isNightlyKeyword = isNightlyKeyword; | ||||||
|  | function getPyPyVersionFromPath(installDir) { | ||||||
|  |     return path.basename(path.dirname(installDir)); | ||||||
|  | } | ||||||
|  | exports.getPyPyVersionFromPath = getPyPyVersionFromPath; | ||||||
|  | /** | ||||||
|  |  * In tool-cache, we put PyPy to '<toolcache_root>/PyPy/<python_version>/x64' | ||||||
|  |  * There is no easy way to determine what PyPy version is located in specific folder | ||||||
|  |  * 'pypy --version' is not reliable enough since it is not set properly for preview versions | ||||||
|  |  * "7.3.3rc1" is marked as '7.3.3' in 'pypy --version' | ||||||
|  |  * so we put PYPY_VERSION file to PyPy directory when install it to VM and read it when we need to know version | ||||||
|  |  * PYPY_VERSION contains exact version from 'versions.json' | ||||||
|  |  */ | ||||||
|  | function readExactPyPyVersionFile(installDir) { | ||||||
|  |     let pypyVersion = ''; | ||||||
|  |     let fileVersion = path.join(installDir, PYPY_VERSION_FILE); | ||||||
|  |     if (fs_1.default.existsSync(fileVersion)) { | ||||||
|  |         pypyVersion = fs_1.default.readFileSync(fileVersion).toString(); | ||||||
|  |     } | ||||||
|  |     return pypyVersion; | ||||||
|  | } | ||||||
|  | exports.readExactPyPyVersionFile = readExactPyPyVersionFile; | ||||||
|  | function writeExactPyPyVersionFile(installDir, resolvedPyPyVersion) { | ||||||
|  |     const pypyFilePath = path.join(installDir, PYPY_VERSION_FILE); | ||||||
|  |     fs_1.default.writeFileSync(pypyFilePath, resolvedPyPyVersion); | ||||||
|  | } | ||||||
|  | exports.writeExactPyPyVersionFile = writeExactPyPyVersionFile; | ||||||
|  | /** | ||||||
|  |  * Python version should be specified explicitly like "x.y" (2.7, 3.6, 3.7) | ||||||
|  |  * "3.x" or "3" are not supported | ||||||
|  |  * because it could cause ambiguity when both PyPy version and Python version are not precise | ||||||
|  |  */ | ||||||
|  | function validatePythonVersionFormatForPyPy(version) { | ||||||
|  |     const re = /^\d+\.\d+$/; | ||||||
|  |     return re.test(version); | ||||||
|  | } | ||||||
|  | exports.validatePythonVersionFormatForPyPy = validatePythonVersionFormatForPyPy; | ||||||
|  |  | ||||||
|  |  | ||||||
| /***/ }), | /***/ }), | ||||||
|  |  | ||||||
| /***/ 164: | /***/ 164: | ||||||
| @@ -2443,16 +2636,26 @@ var __importStar = (this && this.__importStar) || function (mod) { | |||||||
| Object.defineProperty(exports, "__esModule", { value: true }); | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
| const core = __importStar(__webpack_require__(470)); | const core = __importStar(__webpack_require__(470)); | ||||||
| const finder = __importStar(__webpack_require__(927)); | const finder = __importStar(__webpack_require__(927)); | ||||||
|  | const finderPyPy = __importStar(__webpack_require__(50)); | ||||||
| const path = __importStar(__webpack_require__(622)); | const path = __importStar(__webpack_require__(622)); | ||||||
| const os = __importStar(__webpack_require__(87)); | const os = __importStar(__webpack_require__(87)); | ||||||
|  | function isPyPyVersion(versionSpec) { | ||||||
|  |     return versionSpec.startsWith('pypy-'); | ||||||
|  | } | ||||||
| function run() { | function run() { | ||||||
|     return __awaiter(this, void 0, void 0, function* () { |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|         try { |         try { | ||||||
|             let version = core.getInput('python-version'); |             let version = core.getInput('python-version'); | ||||||
|             if (version) { |             if (version) { | ||||||
|                 const arch = core.getInput('architecture') || os.arch(); |                 const arch = core.getInput('architecture') || os.arch(); | ||||||
|                 const installed = yield finder.findPythonVersion(version, arch); |                 if (isPyPyVersion(version)) { | ||||||
|                 core.info(`Successfully setup ${installed.impl} (${installed.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'); |             const matchersPath = path.join(__dirname, '..', '.github'); | ||||||
|             core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); |             core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); | ||||||
| @@ -2580,6 +2783,151 @@ module.exports = ltr | |||||||
|  |  | ||||||
| module.exports = require("assert"); | module.exports = require("assert"); | ||||||
|  |  | ||||||
|  | /***/ }), | ||||||
|  |  | ||||||
|  | /***/ 369: | ||||||
|  | /***/ (function(__unusedmodule, exports, __webpack_require__) { | ||||||
|  |  | ||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||||||
|  |     function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||||||
|  |     return new (P || (P = Promise))(function (resolve, reject) { | ||||||
|  |         function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||||||
|  |         function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||||||
|  |         function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||||||
|  |         step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | var __importStar = (this && this.__importStar) || function (mod) { | ||||||
|  |     if (mod && mod.__esModule) return mod; | ||||||
|  |     var result = {}; | ||||||
|  |     if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||||||
|  |     result["default"] = mod; | ||||||
|  |     return result; | ||||||
|  | }; | ||||||
|  | var __importDefault = (this && this.__importDefault) || function (mod) { | ||||||
|  |     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||||
|  | }; | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const core = __importStar(__webpack_require__(470)); | ||||||
|  | const tc = __importStar(__webpack_require__(533)); | ||||||
|  | const semver = __importStar(__webpack_require__(876)); | ||||||
|  | const httpm = __importStar(__webpack_require__(539)); | ||||||
|  | const exec = __importStar(__webpack_require__(986)); | ||||||
|  | const fs_1 = __importDefault(__webpack_require__(747)); | ||||||
|  | const utils_1 = __webpack_require__(163); | ||||||
|  | function installPyPy(pypyVersion, pythonVersion, architecture) { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         let downloadDir; | ||||||
|  |         const releases = yield getAvailablePyPyVersions(); | ||||||
|  |         if (!releases || releases.length === 0) { | ||||||
|  |             throw new Error('No release was found in PyPy version.json'); | ||||||
|  |         } | ||||||
|  |         const releaseData = findRelease(releases, pythonVersion, pypyVersion, architecture); | ||||||
|  |         if (!releaseData || !releaseData.foundAsset) { | ||||||
|  |             throw new Error(`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`); | ||||||
|  |         } | ||||||
|  |         const { foundAsset, resolvedPythonVersion, resolvedPyPyVersion } = releaseData; | ||||||
|  |         let downloadUrl = `${foundAsset.download_url}`; | ||||||
|  |         core.info(`Downloading PyPy from "${downloadUrl}" ...`); | ||||||
|  |         const pypyPath = yield tc.downloadTool(downloadUrl); | ||||||
|  |         core.info('Extracting downloaded archive...'); | ||||||
|  |         if (utils_1.IS_WINDOWS) { | ||||||
|  |             downloadDir = yield tc.extractZip(pypyPath); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             downloadDir = yield tc.extractTar(pypyPath, undefined, 'x'); | ||||||
|  |         } | ||||||
|  |         // root folder in archive can have unpredictable name so just take the first folder | ||||||
|  |         // downloadDir is unique folder under TEMP and can't contain any other folders | ||||||
|  |         const archiveName = fs_1.default.readdirSync(downloadDir)[0]; | ||||||
|  |         const toolDir = path.join(downloadDir, archiveName); | ||||||
|  |         let installDir = toolDir; | ||||||
|  |         if (!utils_1.isNightlyKeyword(resolvedPyPyVersion)) { | ||||||
|  |             installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture); | ||||||
|  |         } | ||||||
|  |         utils_1.writeExactPyPyVersionFile(installDir, resolvedPyPyVersion); | ||||||
|  |         const binaryPath = getPyPyBinaryPath(installDir); | ||||||
|  |         yield createPyPySymlink(binaryPath, resolvedPythonVersion); | ||||||
|  |         yield installPip(binaryPath); | ||||||
|  |         return { installDir, resolvedPythonVersion, resolvedPyPyVersion }; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | exports.installPyPy = installPyPy; | ||||||
|  | function getAvailablePyPyVersions() { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         const url = 'https://downloads.python.org/pypy/versions.json'; | ||||||
|  |         const http = new httpm.HttpClient('tool-cache'); | ||||||
|  |         const response = yield http.getJson(url); | ||||||
|  |         if (!response.result) { | ||||||
|  |             throw new Error(`Unable to retrieve the list of available PyPy versions from '${url}'`); | ||||||
|  |         } | ||||||
|  |         return response.result; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | function createPyPySymlink(pypyBinaryPath, pythonVersion) { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         const version = semver.coerce(pythonVersion); | ||||||
|  |         const pythonBinaryPostfix = semver.major(version); | ||||||
|  |         const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3'; | ||||||
|  |         let binaryExtension = utils_1.IS_WINDOWS ? '.exe' : ''; | ||||||
|  |         core.info('Creating symlinks...'); | ||||||
|  |         utils_1.createSymlinkInFolder(pypyBinaryPath, `pypy${pypyBinaryPostfix}${binaryExtension}`, `python${pythonBinaryPostfix}${binaryExtension}`, true); | ||||||
|  |         utils_1.createSymlinkInFolder(pypyBinaryPath, `pypy${pypyBinaryPostfix}${binaryExtension}`, `python${binaryExtension}`, true); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | function installPip(pythonLocation) { | ||||||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |         core.info('Installing and updating pip'); | ||||||
|  |         const pythonBinary = path.join(pythonLocation, 'python'); | ||||||
|  |         yield exec.exec(`${pythonBinary} -m ensurepip`); | ||||||
|  |         yield exec.exec(`${pythonLocation}/python -m pip install --ignore-installed pip`); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | function findRelease(releases, pythonVersion, pypyVersion, architecture) { | ||||||
|  |     const filterReleases = releases.filter(item => { | ||||||
|  |         const isPythonVersionSatisfied = semver.satisfies(semver.coerce(item.python_version), pythonVersion); | ||||||
|  |         const isPyPyNightly = utils_1.isNightlyKeyword(pypyVersion) && utils_1.isNightlyKeyword(item.pypy_version); | ||||||
|  |         const isPyPyVersionSatisfied = isPyPyNightly || | ||||||
|  |             semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion); | ||||||
|  |         const isArchPresent = item.files && | ||||||
|  |             item.files.some(file => file.arch === architecture && file.platform === process.platform); | ||||||
|  |         return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent; | ||||||
|  |     }); | ||||||
|  |     if (filterReleases.length === 0) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |     const sortedReleases = filterReleases.sort((previous, current) => { | ||||||
|  |         return (semver.compare(semver.coerce(pypyVersionToSemantic(current.pypy_version)), semver.coerce(pypyVersionToSemantic(previous.pypy_version))) || | ||||||
|  |             semver.compare(semver.coerce(current.python_version), semver.coerce(previous.python_version))); | ||||||
|  |     }); | ||||||
|  |     const foundRelease = sortedReleases[0]; | ||||||
|  |     const foundAsset = foundRelease.files.find(item => item.arch === architecture && item.platform === process.platform); | ||||||
|  |     return { | ||||||
|  |         foundAsset, | ||||||
|  |         resolvedPythonVersion: foundRelease.python_version, | ||||||
|  |         resolvedPyPyVersion: foundRelease.pypy_version | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | exports.findRelease = findRelease; | ||||||
|  | /** Get PyPy binary location from the tool of installation directory | ||||||
|  |  *  - On Linux and macOS, the Python interpreter is in 'bin'. | ||||||
|  |  *  - On Windows, it is in the installation root. | ||||||
|  |  */ | ||||||
|  | function getPyPyBinaryPath(installDir) { | ||||||
|  |     const _binDir = path.join(installDir, 'bin'); | ||||||
|  |     return utils_1.IS_WINDOWS ? installDir : _binDir; | ||||||
|  | } | ||||||
|  | exports.getPyPyBinaryPath = getPyPyBinaryPath; | ||||||
|  | function pypyVersionToSemantic(versionSpec) { | ||||||
|  |     const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g; | ||||||
|  |     return versionSpec.replace(prereleaseVersion, '$1-$2.$3'); | ||||||
|  | } | ||||||
|  | exports.pypyVersionToSemantic = pypyVersionToSemantic; | ||||||
|  |  | ||||||
|  |  | ||||||
| /***/ }), | /***/ }), | ||||||
|  |  | ||||||
| /***/ 413: | /***/ 413: | ||||||
| @@ -6422,16 +6770,17 @@ var __importStar = (this && this.__importStar) || function (mod) { | |||||||
|     return result; |     return result; | ||||||
| }; | }; | ||||||
| Object.defineProperty(exports, "__esModule", { value: true }); | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const path = __importStar(__webpack_require__(622)); | ||||||
| const core = __importStar(__webpack_require__(470)); | const core = __importStar(__webpack_require__(470)); | ||||||
| const tc = __importStar(__webpack_require__(533)); | const tc = __importStar(__webpack_require__(533)); | ||||||
| const exec = __importStar(__webpack_require__(986)); | const exec = __importStar(__webpack_require__(986)); | ||||||
|  | const utils_1 = __webpack_require__(163); | ||||||
| const TOKEN = core.getInput('token'); | const TOKEN = core.getInput('token'); | ||||||
| const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | ||||||
| const MANIFEST_REPO_OWNER = 'actions'; | const MANIFEST_REPO_OWNER = 'actions'; | ||||||
| const MANIFEST_REPO_NAME = 'python-versions'; | const MANIFEST_REPO_NAME = 'python-versions'; | ||||||
| const MANIFEST_REPO_BRANCH = 'main'; | const MANIFEST_REPO_BRANCH = 'main'; | ||||||
| exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; | exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; | ||||||
| const IS_WINDOWS = process.platform === 'win32'; |  | ||||||
| function findReleaseFromManifest(semanticVersionSpec, architecture) { | function findReleaseFromManifest(semanticVersionSpec, architecture) { | ||||||
|     return __awaiter(this, void 0, void 0, function* () { |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|         const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH); |         const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH); | ||||||
| @@ -6443,6 +6792,7 @@ function installPython(workingDirectory) { | |||||||
|     return __awaiter(this, void 0, void 0, function* () { |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|         const options = { |         const options = { | ||||||
|             cwd: workingDirectory, |             cwd: workingDirectory, | ||||||
|  |             env: Object.assign(Object.assign({}, process.env), (utils_1.IS_LINUX && { LD_LIBRARY_PATH: path.join(workingDirectory, 'lib') })), | ||||||
|             silent: true, |             silent: true, | ||||||
|             listeners: { |             listeners: { | ||||||
|                 stdout: (data) => { |                 stdout: (data) => { | ||||||
| @@ -6453,7 +6803,7 @@ function installPython(workingDirectory) { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         if (IS_WINDOWS) { |         if (utils_1.IS_WINDOWS) { | ||||||
|             yield exec.exec('powershell', ['./setup.ps1'], options); |             yield exec.exec('powershell', ['./setup.ps1'], options); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
| @@ -6468,7 +6818,7 @@ function installCpythonFromRelease(release) { | |||||||
|         const pythonPath = yield tc.downloadTool(downloadUrl, undefined, AUTH); |         const pythonPath = yield tc.downloadTool(downloadUrl, undefined, AUTH); | ||||||
|         core.info('Extract downloaded archive'); |         core.info('Extract downloaded archive'); | ||||||
|         let pythonExtractedFolder; |         let pythonExtractedFolder; | ||||||
|         if (IS_WINDOWS) { |         if (utils_1.IS_WINDOWS) { | ||||||
|             pythonExtractedFolder = yield tc.extractZip(pythonPath); |             pythonExtractedFolder = yield tc.extractZip(pythonPath); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
| @@ -6683,11 +7033,11 @@ var __importStar = (this && this.__importStar) || function (mod) { | |||||||
| Object.defineProperty(exports, "__esModule", { value: true }); | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
| const os = __importStar(__webpack_require__(87)); | const os = __importStar(__webpack_require__(87)); | ||||||
| const path = __importStar(__webpack_require__(622)); | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const utils_1 = __webpack_require__(163); | ||||||
| const semver = __importStar(__webpack_require__(876)); | const semver = __importStar(__webpack_require__(876)); | ||||||
| const installer = __importStar(__webpack_require__(824)); | const installer = __importStar(__webpack_require__(824)); | ||||||
| const core = __importStar(__webpack_require__(470)); | const core = __importStar(__webpack_require__(470)); | ||||||
| const tc = __importStar(__webpack_require__(533)); | const tc = __importStar(__webpack_require__(533)); | ||||||
| const IS_WINDOWS = process.platform === 'win32'; |  | ||||||
| // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. | // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. | ||||||
| // This is where pip is, along with anything that pip installs. | // This is where pip is, along with anything that pip installs. | ||||||
| // There is a seperate directory for `pip install --user`. | // There is a seperate directory for `pip install --user`. | ||||||
| @@ -6701,7 +7051,7 @@ const IS_WINDOWS = process.platform === 'win32'; | |||||||
| //      (--user) %APPDATA%\Python\PythonXY\Scripts | //      (--user) %APPDATA%\Python\PythonXY\Scripts | ||||||
| // See https://docs.python.org/3/library/sysconfig.html | // See https://docs.python.org/3/library/sysconfig.html | ||||||
| function binDir(installDir) { | function binDir(installDir) { | ||||||
|     if (IS_WINDOWS) { |     if (utils_1.IS_WINDOWS) { | ||||||
|         return path.join(installDir, 'Scripts'); |         return path.join(installDir, 'Scripts'); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
| @@ -6714,9 +7064,9 @@ function binDir(installDir) { | |||||||
| // For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. | // For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. | ||||||
| // We only care about the Python version, so we don't use the PyPy version for the tool cache. | // We only care about the Python version, so we don't use the PyPy version for the tool cache. | ||||||
| function usePyPy(majorVersion, architecture) { | function usePyPy(majorVersion, architecture) { | ||||||
|     const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion.toString()); |     const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion); | ||||||
|     let installDir = findPyPy(architecture); |     let installDir = findPyPy(architecture); | ||||||
|     if (!installDir && IS_WINDOWS) { |     if (!installDir && utils_1.IS_WINDOWS) { | ||||||
|         // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. |         // PyPy only precompiles binaries for x86, but the architecture parameter defaults to x64. | ||||||
|         // On our Windows virtual environments, we only install an x86 version. |         // On our Windows virtual environments, we only install an x86 version. | ||||||
|         // Fall back to x86. |         // Fall back to x86. | ||||||
| @@ -6730,10 +7080,14 @@ function usePyPy(majorVersion, architecture) { | |||||||
|     const _binDir = path.join(installDir, 'bin'); |     const _binDir = path.join(installDir, 'bin'); | ||||||
|     // On Linux and macOS, the Python interpreter is in 'bin'. |     // On Linux and macOS, the Python interpreter is in 'bin'. | ||||||
|     // On Windows, it is in the installation root. |     // On Windows, it is in the installation root. | ||||||
|     const pythonLocation = IS_WINDOWS ? installDir : _binDir; |     const pythonLocation = utils_1.IS_WINDOWS ? installDir : _binDir; | ||||||
|     core.exportVariable('pythonLocation', pythonLocation); |     core.exportVariable('pythonLocation', pythonLocation); | ||||||
|     core.addPath(installDir); |     core.addPath(installDir); | ||||||
|     core.addPath(_binDir); |     core.addPath(_binDir); | ||||||
|  |     // Starting from PyPy 7.3.1, the folder that is used for pip and anything that pip installs should be "Scripts" on Windows. | ||||||
|  |     if (IS_WINDOWS) { | ||||||
|  |         core.addPath(path.join(installDir, 'Scripts')); | ||||||
|  |     } | ||||||
|     const impl = 'pypy' + majorVersion.toString(); |     const impl = 'pypy' + majorVersion.toString(); | ||||||
|     core.setOutput('python-version', impl); |     core.setOutput('python-version', impl); | ||||||
|     return { impl: impl, version: versionFromPath(installDir) }; |     return { impl: impl, version: versionFromPath(installDir) }; | ||||||
| @@ -6760,9 +7114,18 @@ function useCpythonVersion(version, architecture) { | |||||||
|             ].join(os.EOL)); |             ].join(os.EOL)); | ||||||
|         } |         } | ||||||
|         core.exportVariable('pythonLocation', installDir); |         core.exportVariable('pythonLocation', installDir); | ||||||
|  |         if (utils_1.IS_LINUX) { | ||||||
|  |             const libPath = process.env.LD_LIBRARY_PATH | ||||||
|  |                 ? `:${process.env.LD_LIBRARY_PATH}` | ||||||
|  |                 : ''; | ||||||
|  |             const pyLibPath = path.join(installDir, 'lib'); | ||||||
|  |             if (!libPath.split(':').includes(pyLibPath)) { | ||||||
|  |                 core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         core.addPath(installDir); |         core.addPath(installDir); | ||||||
|         core.addPath(binDir(installDir)); |         core.addPath(binDir(installDir)); | ||||||
|         if (IS_WINDOWS) { |         if (utils_1.IS_WINDOWS) { | ||||||
|             // Add --user directory |             // Add --user directory | ||||||
|             // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/ |             // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/ | ||||||
|             // So if `findLocalTool` succeeded above, we must have a conformant `installDir` |             // So if `findLocalTool` succeeded above, we must have a conformant `installDir` | ||||||
| @@ -6808,9 +7171,10 @@ function findPythonVersion(version, architecture) { | |||||||
|     return __awaiter(this, void 0, void 0, function* () { |     return __awaiter(this, void 0, void 0, function* () { | ||||||
|         switch (version.toUpperCase()) { |         switch (version.toUpperCase()) { | ||||||
|             case 'PYPY2': |             case 'PYPY2': | ||||||
|                 return usePyPy(2, architecture); |                 return usePyPy('2', architecture); | ||||||
|             case 'PYPY3': |             case 'PYPY3': | ||||||
|                 return usePyPy(3, architecture); |                 // keep pypy3 pointing to 3.6 for backward compatibility | ||||||
|  |                 return usePyPy('3.6', architecture); | ||||||
|             default: |             default: | ||||||
|                 return yield useCpythonVersion(version, architecture); |                 return yield useCpythonVersion(version, architecture); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -13,12 +13,14 @@ In order to avoid uploading `node_modules/` to the repository, we use [vercel/nc | |||||||
| ### Developing | ### Developing | ||||||
|  |  | ||||||
| If you're developing locally, you can run | If you're developing locally, you can run | ||||||
| ``` |  | ||||||
|  | ```sh | ||||||
| npm install | npm install | ||||||
| tsc | tsc | ||||||
| ncc build src/setup-python.ts | ncc build src/setup-python.ts | ||||||
| ``` | ``` | ||||||
| Any files generated using `tsc` will be added to `lib/`, however those files also are not uploaded to the repository and are exluded using `.gitignore`. |  | ||||||
|  | Any files generated using `tsc` will be added to `lib/`, however those files also are not uploaded to the repository and are excluded using `.gitignore`. | ||||||
|  |  | ||||||
| During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier) (to run manually, use `npm run format`). | During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier) (to run manually, use `npm run format`). | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										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 os from 'os'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
|  | import {IS_WINDOWS, IS_LINUX} from './utils'; | ||||||
|  |  | ||||||
| import * as semver from 'semver'; | import * as semver from 'semver'; | ||||||
|  |  | ||||||
| @@ -8,8 +9,6 @@ import * as installer from './install-python'; | |||||||
| import * as core from '@actions/core'; | import * as core from '@actions/core'; | ||||||
| import * as tc from '@actions/tool-cache'; | import * as tc from '@actions/tool-cache'; | ||||||
|  |  | ||||||
| const IS_WINDOWS = process.platform === 'win32'; |  | ||||||
|  |  | ||||||
| // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. | // Python has "scripts" or "bin" directories where command-line tools that come with packages are installed. | ||||||
| // This is where pip is, along with anything that pip installs. | // This is where pip is, along with anything that pip installs. | ||||||
| // There is a seperate directory for `pip install --user`. | // There is a seperate directory for `pip install --user`. | ||||||
| @@ -36,8 +35,11 @@ function binDir(installDir: string): string { | |||||||
| // A particular version of PyPy may contain one or more versions of the Python interpreter. | // A particular version of PyPy may contain one or more versions of the Python interpreter. | ||||||
| // For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. | // For example, PyPy 7.0 contains Python 2.7, 3.5, and 3.6-alpha. | ||||||
| // We only care about the Python version, so we don't use the PyPy version for the tool cache. | // We only care about the Python version, so we don't use the PyPy version for the tool cache. | ||||||
| function usePyPy(majorVersion: 2 | 3, architecture: string): InstalledVersion { | function usePyPy( | ||||||
|   const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion.toString()); |   majorVersion: '2' | '3.6', | ||||||
|  |   architecture: string | ||||||
|  | ): InstalledVersion { | ||||||
|  |   const findPyPy = tc.find.bind(undefined, 'PyPy', majorVersion); | ||||||
|   let installDir: string | null = findPyPy(architecture); |   let installDir: string | null = findPyPy(architecture); | ||||||
|  |  | ||||||
|   if (!installDir && IS_WINDOWS) { |   if (!installDir && IS_WINDOWS) { | ||||||
| @@ -62,6 +64,10 @@ function usePyPy(majorVersion: 2 | 3, architecture: string): InstalledVersion { | |||||||
|  |  | ||||||
|   core.addPath(installDir); |   core.addPath(installDir); | ||||||
|   core.addPath(_binDir); |   core.addPath(_binDir); | ||||||
|  |   // Starting from PyPy 7.3.1, the folder that is used for pip and anything that pip installs should be "Scripts" on Windows. | ||||||
|  |   if (IS_WINDOWS) { | ||||||
|  |     core.addPath(path.join(installDir, 'Scripts')); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   const impl = 'pypy' + majorVersion.toString(); |   const impl = 'pypy' + majorVersion.toString(); | ||||||
|   core.setOutput('python-version', impl); |   core.setOutput('python-version', impl); | ||||||
| @@ -109,6 +115,18 @@ async function useCpythonVersion( | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   core.exportVariable('pythonLocation', installDir); |   core.exportVariable('pythonLocation', installDir); | ||||||
|  |  | ||||||
|  |   if (IS_LINUX) { | ||||||
|  |     const libPath = process.env.LD_LIBRARY_PATH | ||||||
|  |       ? `:${process.env.LD_LIBRARY_PATH}` | ||||||
|  |       : ''; | ||||||
|  |     const pyLibPath = path.join(installDir, 'lib'); | ||||||
|  |  | ||||||
|  |     if (!libPath.split(':').includes(pyLibPath)) { | ||||||
|  |       core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   core.addPath(installDir); |   core.addPath(installDir); | ||||||
|   core.addPath(binDir(installDir)); |   core.addPath(binDir(installDir)); | ||||||
|  |  | ||||||
| @@ -175,9 +193,10 @@ export async function findPythonVersion( | |||||||
| ): Promise<InstalledVersion> { | ): Promise<InstalledVersion> { | ||||||
|   switch (version.toUpperCase()) { |   switch (version.toUpperCase()) { | ||||||
|     case 'PYPY2': |     case 'PYPY2': | ||||||
|       return usePyPy(2, architecture); |       return usePyPy('2', architecture); | ||||||
|     case 'PYPY3': |     case 'PYPY3': | ||||||
|       return usePyPy(3, architecture); |       // keep pypy3 pointing to 3.6 for backward compatibility | ||||||
|  |       return usePyPy('3.6', architecture); | ||||||
|     default: |     default: | ||||||
|       return await useCpythonVersion(version, architecture); |       return await useCpythonVersion(version, architecture); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										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 tc from '@actions/tool-cache'; | ||||||
| import * as exec from '@actions/exec'; | import * as exec from '@actions/exec'; | ||||||
| import {ExecOptions} from '@actions/exec/lib/interfaces'; | import {ExecOptions} from '@actions/exec/lib/interfaces'; | ||||||
| import {stderr} from 'process'; | import {IS_WINDOWS, IS_LINUX} from './utils'; | ||||||
|  |  | ||||||
| const TOKEN = core.getInput('token'); | const TOKEN = core.getInput('token'); | ||||||
| const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | const AUTH = !TOKEN || isGhes() ? undefined : `token ${TOKEN}`; | ||||||
| @@ -12,8 +12,6 @@ const MANIFEST_REPO_NAME = 'python-versions'; | |||||||
| const MANIFEST_REPO_BRANCH = 'main'; | const MANIFEST_REPO_BRANCH = 'main'; | ||||||
| export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; | export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; | ||||||
|  |  | ||||||
| const IS_WINDOWS = process.platform === 'win32'; |  | ||||||
|  |  | ||||||
| export async function findReleaseFromManifest( | export async function findReleaseFromManifest( | ||||||
|   semanticVersionSpec: string, |   semanticVersionSpec: string, | ||||||
|   architecture: string |   architecture: string | ||||||
| @@ -35,6 +33,10 @@ export async function findReleaseFromManifest( | |||||||
| async function installPython(workingDirectory: string) { | async function installPython(workingDirectory: string) { | ||||||
|   const options: ExecOptions = { |   const options: ExecOptions = { | ||||||
|     cwd: workingDirectory, |     cwd: workingDirectory, | ||||||
|  |     env: { | ||||||
|  |       ...process.env, | ||||||
|  |       ...(IS_LINUX && {LD_LIBRARY_PATH: path.join(workingDirectory, 'lib')}) | ||||||
|  |     }, | ||||||
|     silent: true, |     silent: true, | ||||||
|     listeners: { |     listeners: { | ||||||
|       stdout: (data: Buffer) => { |       stdout: (data: Buffer) => { | ||||||
|   | |||||||
| @@ -1,15 +1,29 @@ | |||||||
| import * as core from '@actions/core'; | import * as core from '@actions/core'; | ||||||
| import * as finder from './find-python'; | import * as finder from './find-python'; | ||||||
|  | import * as finderPyPy from './find-pypy'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| import * as os from 'os'; | import * as os from 'os'; | ||||||
|  |  | ||||||
|  | function isPyPyVersion(versionSpec: string) { | ||||||
|  |   return versionSpec.startsWith('pypy-'); | ||||||
|  | } | ||||||
|  |  | ||||||
| async function run() { | async function run() { | ||||||
|   try { |   try { | ||||||
|     let version = core.getInput('python-version'); |     let version = core.getInput('python-version'); | ||||||
|     if (version) { |     if (version) { | ||||||
|       const arch: string = core.getInput('architecture') || os.arch(); |       const arch: string = core.getInput('architecture') || os.arch(); | ||||||
|       const installed = await finder.findPythonVersion(version, arch); |       if (isPyPyVersion(version)) { | ||||||
|       core.info(`Successfully setup ${installed.impl} (${installed.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'); |     const matchersPath = path.join(__dirname, '..', '.github'); | ||||||
|     core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); |     core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); | ||||||
|   | |||||||
							
								
								
									
										92
									
								
								src/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user