Sync with Main branch

This commit is contained in:
IvanZosimov 2022-07-26 11:05:09 +02:00
commit 1f0a39a525
25 changed files with 758 additions and 102 deletions

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3
@ -45,7 +45,7 @@ jobs:
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist

View file

@ -18,11 +18,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@ -30,7 +30,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -44,4 +44,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View file

@ -72,15 +72,15 @@ jobs:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
- name: Init pyproject.toml
run: mv ./__tests__/data/pyproject.toml .
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Init pyproject.toml
run: poetry init -n
- name: Install dependencies
run: poetry add flake8
run: poetry install
python-pip-dependencies-caching-path:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})

View file

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
name: Check licenses
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3
with:

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Update the ${{ env.TAG_NAME }} tag
uses: actions/publish-action@v0.1.0
uses: actions/publish-action@v0.2.0
with:
source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

View file

@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: setup-python ${{ matrix.pypy }}
id: setup-python
@ -91,3 +91,36 @@ jobs:
- name: Run simple code
run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))'
check-latest:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Setup PyPy and check latest
uses: ./
with:
python-version: 'pypy-3.7-v7.3.x'
check-latest: true
- name: PyPy and Python version
run: python --version
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'
- name: Assert PyPy is running
run: |
import platform
assert platform.python_implementation().lower() == "pypy"
shell: python
- name: Assert expected binaries (or symlinks) are present
run: |
EXECUTABLE="pypy-3.7-v7.3.x"
EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
${EXECUTABLE} --version
shell: bash

View file

@ -172,3 +172,27 @@ jobs:
- name: Run simple code
run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))'
check-latest:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Setup Python and check latest
uses: ./
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Validate version
run: |
$pythonVersion = (python --version)
if ("$pythonVersion" -NotMatch "${{ matrix.python }}"){
Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}"
exit 1
}
$pythonVersion
shell: pwsh

View file

@ -17,7 +17,7 @@ jobs:
operating-system: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3

Binary file not shown.

144
README.md
View file

@ -47,7 +47,151 @@ The `python-version` input supports the [Semantic Versioning Specification](http
Using `architecture` input it is possible to specify required Python/PyPy interpreter architecture: `x86` or `x64`. If input is not specified the architecture defaults to `x64`.
<<<<<<< HEAD
## Caching packages dependencies
=======
Download and set up the latest stable version of Python (for specified major version):
```yaml
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- run: python my_script.py
```
Download and set up PyPy:
```yaml
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- 'pypy3.7' # the latest available version of PyPy that supports Python 3.7
- 'pypy3.7-v7.3.3' # Python 3.7 and PyPy 7.3.3
- 'pypy3.8' # the latest available version of PyPy that supports Python 3.8
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
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.
An output is available with the absolute path of the python interpreter executable if you need it:
```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
id: cp310
with:
python-version: "3.10"
- run: pipx run --python '${{ steps.cp310.outputs.python-path }}' nox --version
```
>The environment variable `pythonLocation` also becomes available after Python or PyPy installation. It contains the absolute path to the folder where the desired version of Python or PyPy is installed.
# 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).
# Available versions of Python
`setup-python` is able to configure Python from two sources:
- 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 every minor version of Python, expect only the latest patch to be preinstalled.
- If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tools cache.
- If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache.
- Use `-dev` instead of a patch number (e.g., `3.11-dev`) to install the latest patch version release for a given minor version, *alpha and beta releases included*.
- Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)).
- All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file.
- If there is a specific version of Python that is not available, you can open an issue here
**Note:** Python versions used in this action are generated in the [python-versions](https://github.com/actions/python-versions) repository. For macOS and Ubuntu images python versions are built from the source code. For Windows the python-versions repository uses installation executable. For more information please refer to the [python-versions](https://github.com/actions/python-versions) repository.
# 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 `pypy3.7` or `pypy-3.7`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy3.7-v7.3.3` or `pypy-3.7-v7.3.3`.
- Downloadable PyPy versions from the [official PyPy site](https://downloads.python.org/pypy/).
- All available versions that we can download are listed in [versions.json](https://downloads.python.org/pypy/versions.json) file.
- 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
GitHub hosted runners have a tools cache that comes with a few versions of Python + PyPy already installed. This tools cache helps speed up runs and tool setup by not requiring any new downloads. There is an environment variable called `RUNNER_TOOL_CACHE` on each runner that describes the location of this tools cache and there is where you will find Python and PyPy installed. `setup-python` works by taking a specific version of Python or PyPy in this tools cache and adding it to PATH.
|| Location |
|------|-------|
|**Tool Cache Directory** |`RUNNER_TOOL_CACHE`|
|**Python Tool Cache**|`RUNNER_TOOL_CACHE/Python/*`|
|**PyPy Tool Cache**|`RUNNER_TOOL_CACHE/PyPy/*`|
GitHub virtual environments are setup in [actions/virtual-environments](https://github.com/actions/virtual-environments). During the setup, the available versions of Python and PyPy are automatically downloaded, setup and documented.
- Tools cache setup for Ubuntu: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Configure-Toolset.ps1)
- Tools cache setup for Windows: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Configure-Toolset.ps1)
# Specifying a Python version
If there is a specific version of Python that you need and you don't want to worry about any potential breaking changes due to patch updates (going from `3.7.5` to `3.7.6` for example), you should specify the exact major, minor, and patch version (such as `3.7.5`)
- The only downside to this is that set up will take a little longer since the exact version will have to be downloaded if the exact version is not already installed on the runner due to more recent versions.
- MSI installers are used on Windows for this, so runs will take a little longer to set up vs Mac and Linux.
You should specify only a major and minor version if you are okay with the most recent patch version being used.
- There will be a single patch version already installed on each runner for every minor version of Python that is supported.
- The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced.
- Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version Python on the runner will be used.
# Specifying a PyPy version
The version of PyPy should be specified in the format `pypy<python_version>[-v<pypy_version>]` or `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.
```
pypy3.7 or pypy-3.7 # the latest available version of PyPy that supports Python 3.7
pypy3.8 or pypy-3.8 # the latest available version of PyPy that supports Python 3.8
pypy2.7 or pypy-2.7 # the latest available version of PyPy that supports Python 2.7
pypy3.7-v7.3.3 or pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3
pypy3.7-v7.x or pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x
pypy3.7-v7.3.3rc1 or pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy
pypy3.7-nightly or pypy-3.7-nightly # Python 3.7 and nightly PyPy
```
Note: `pypy2` and `pypy3` have been removed in v3. Use the format above instead.
# Check latest version
The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability and if you want to ensure a specific `Python/PyPy` version is always used.
If `check-latest` is set to `true`, the action first checks if the cached version is the latest one. If the locally cached version is not the most up-to-date, a `Python/PyPy` version will then be downloaded. Set `check-latest` to `true` if you want the most up-to-date `Python/PyPy` version to always be used.
> Setting `check-latest` to `true` has performance implications as downloading `Python/PyPy` versions is slower than using cached versions.
```yaml
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: '3.7'
check-latest: true
- run: python my_script.py
```
# Caching packages dependencies
>>>>>>> main
The action has built-in functionality for caching and restoring dependencies. It uses [actions/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching dependencies but requires less configuration settings. Supported package managers are `pip`, `pipenv` and `poetry`. The `cache` input is optional, and caching is turned off by default.

View file

@ -1,7 +1,9 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import {getCacheDistributor} from '../src/cache-distributions/cache-factory';
import * as utils from './../src/utils';
describe('restore-cache', () => {
const pipFileLockHash =
@ -28,6 +30,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
let saveSatetSpy: jest.SpyInstance;
let getStateSpy: jest.SpyInstance;
let setOutputSpy: jest.SpyInstance;
let getLinuxOSReleaseInfoSpy: jest.SpyInstance;
// cache spy
let restoreCacheSpy: jest.SpyInstance;
@ -35,6 +38,9 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
// exec spy
let getExecOutputSpy: jest.SpyInstance;
// io spy
let whichSpy: jest.SpyInstance;
beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
@ -74,6 +80,10 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
return primaryKey;
}
);
whichSpy = jest.spyOn(io, 'which');
whichSpy.mockImplementation(() => '/path/to/python');
getLinuxOSReleaseInfoSpy = jest.spyOn(utils, 'getLinuxOSReleaseInfo');
});
describe('Validate provided package manager', () => {
@ -109,11 +119,24 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
pythonVersion,
dependencyFile
);
if (process.platform === 'linux') {
getLinuxOSReleaseInfoSpy.mockImplementation(() =>
Promise.resolve('Ubuntu-20.4')
);
}
await cacheDistributor.restoreCache();
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-${fileHash}`
);
if (process.platform === 'linux' && packageManager === 'pip') {
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-Ubuntu-20.4-python-${pythonVersion}-${packageManager}-${fileHash}`
);
} else {
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-${fileHash}`
);
}
},
30000
);

View file

@ -0,0 +1,15 @@
[tool.poetry]
name = "testactiontasks"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
flake8 = "^4.0.1"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View file

@ -14,7 +14,6 @@ import * as finder from '../src/find-pypy';
import {
IPyPyManifestRelease,
IS_WINDOWS,
validateVersion,
getPyPyVersionFromPath
} from '../src/utils';
@ -82,6 +81,12 @@ describe('findPyPyToolCache', () => {
const pypyPath = path.join('PyPy', actualPythonVersion, architecture);
let tcFind: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.SpyInstance;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
@ -94,6 +99,24 @@ describe('findPyPyToolCache', () => {
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => null);
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null);
});
afterEach(() => {
@ -136,6 +159,13 @@ describe('findPyPyToolCache', () => {
});
describe('findPyPyVersion', () => {
let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.SpyInstance;
let tcFind: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
@ -154,6 +184,27 @@ describe('findPyPyVersion', () => {
const env = process.env;
beforeEach(() => {
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false);
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null);
jest.resetModules();
process.env = {...env};
tcFind = jest.spyOn(tc, 'find');
@ -222,7 +273,7 @@ describe('findPyPyVersion', () => {
it('found PyPy in toolcache', async () => {
await expect(
finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, true)
finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, true, false)
).resolves.toEqual({
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
@ -240,13 +291,13 @@ describe('findPyPyVersion', () => {
it('throw on invalid input format', async () => {
await expect(
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true)
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false)
).rejects.toThrow();
});
it('throw on invalid input format pypy3.7-7.3.x', async () => {
await expect(
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true)
finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false)
).rejects.toThrow();
});
@ -258,7 +309,7 @@ describe('findPyPyVersion', () => {
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, true)
finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, true, false)
).resolves.toEqual({
resolvedPythonVersion: '3.7.9',
resolvedPyPyVersion: '7.3.3'
@ -282,7 +333,7 @@ describe('findPyPyVersion', () => {
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false)
finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false, false)
).resolves.toEqual({
resolvedPythonVersion: '3.7.9',
resolvedPyPyVersion: '7.3.3'
@ -293,9 +344,61 @@ describe('findPyPyVersion', () => {
it('throw if release is not found', async () => {
await expect(
finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture, true)
finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture, true, false)
).rejects.toThrowError(
`PyPy version 3.7 (v7.5.x) with arch ${architecture} not found`
);
});
it('check-latest enabled version found and used from toolcache', async () => {
await expect(
finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, false, true)
).resolves.toEqual({
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
expect(infoSpy).toHaveBeenCalledWith(
'Resolved as PyPy 7.3.3 with Python (3.6.12)'
);
});
it('check-latest enabled version 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, false, true)
).resolves.toEqual({
resolvedPythonVersion: '3.7.9',
resolvedPyPyVersion: '7.3.3'
});
expect(infoSpy).toHaveBeenCalledWith(
'Resolved as PyPy 7.3.3 with Python (3.7.9)'
);
});
it('check-latest enabled version is not found and used from toolcache', async () => {
tcFind.mockImplementationOnce((tool: string, version: string) => {
const semverRange = new semver.Range(version);
let pypyPath = '';
if (semver.satisfies('3.8.8', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture);
}
return pypyPath;
});
await expect(
finder.findPyPyVersion('pypy-3.8-v7.3.x', architecture, false, true)
).resolves.toEqual({
resolvedPythonVersion: '3.8.8',
resolvedPyPyVersion: '7.3.3'
});
expect(infoSpy).toHaveBeenCalledWith(
'Failed to resolve PyPy v7.3.x with Python (3.8) from manifest'
);
});
});

View file

@ -1,6 +1,7 @@
import io = require('@actions/io');
import fs = require('fs');
import path = require('path');
import * as io from '@actions/io';
import os from 'os';
import fs from 'fs';
import path from 'path';
const toolDir = path.join(
__dirname,
@ -26,11 +27,14 @@ import * as installer from '../src/install-python';
const manifestData = require('./data/versions-manifest.json');
describe('Finder tests', () => {
let writeSpy: jest.SpyInstance;
let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.SpyInstance;
const env = process.env;
beforeEach(() => {
writeSpy = jest.spyOn(process.stdout, 'write');
writeSpy.mockImplementation(() => {});
jest.resetModules();
process.env = {...env};
spyCoreAddPath = jest.spyOn(core, 'addPath');
@ -45,11 +49,14 @@ describe('Finder tests', () => {
});
it('Finds Python if it is installed', async () => {
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
await io.mkdirP(pythonDir);
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('3.x', 'x64', true);
await finder.useCpythonVersion('3.x', 'x64', true, false);
expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation',
@ -66,7 +73,7 @@ describe('Finder tests', () => {
await io.mkdirP(pythonDir);
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('3.x', 'x64', false);
await finder.useCpythonVersion('3.x', 'x64', false, false);
expect(spyCoreAddPath).not.toHaveBeenCalled();
expect(spyCoreExportVariable).not.toHaveBeenCalled();
});
@ -75,6 +82,9 @@ describe('Finder tests', () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
@ -85,7 +95,7 @@ describe('Finder tests', () => {
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
});
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('1.2.3', 'x64', true);
await finder.useCpythonVersion('1.2.3', 'x64', true, false);
expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation',
@ -101,6 +111,9 @@ describe('Finder tests', () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
@ -116,7 +129,65 @@ describe('Finder tests', () => {
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
});
// This will throw if it doesn't find it in the manifest (because no such version exists)
await finder.useCpythonVersion('1.2.3-beta.2', 'x64', true);
await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, false);
});
it('Check-latest true, finds the latest version in the manifest', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => true);
const cnSpy: jest.SpyInstance = jest.spyOn(process.stdout, 'write');
cnSpy.mockImplementation(line => {
// uncomment to debug
// process.stderr.write('write:' + line + '\n');
});
const addPathSpy: jest.SpyInstance = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null);
const infoSpy: jest.SpyInstance = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {});
const debugSpy: jest.SpyInstance = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => {});
const pythonDir: string = path.join(toolDir, 'Python', '1.2.2', 'x64');
const expPath: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => {
await io.mkdirP(expPath);
fs.writeFileSync(`${expPath}.complete`, 'hello');
});
const tcFindSpy: jest.SpyInstance = jest.spyOn(tc, 'find');
tcFindSpy
.mockImplementationOnce(() => '')
.mockImplementationOnce(() => expPath);
await io.mkdirP(pythonDir);
await io.rmRF(path.join(toolDir, 'Python', '1.2.3'));
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await finder.useCpythonVersion('1.2', 'x64', true, true);
expect(infoSpy).toHaveBeenCalledWith("Resolved as '1.2.3'");
expect(infoSpy).toHaveBeenCalledWith(
'Version 1.2.3 was not found in the local cache'
);
expect(infoSpy).toBeCalledWith(
'Version 1.2.3 is available for downloading'
);
expect(installSpy).toHaveBeenCalled();
expect(addPathSpy).toHaveBeenCalledWith(expPath);
await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, true);
expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation',
@ -132,7 +203,7 @@ describe('Finder tests', () => {
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
let thrown = false;
try {
await finder.useCpythonVersion('3.300000', 'x64', true);
await finder.useCpythonVersion('3.300000', 'x64', true, false);
} catch {
thrown = true;
}

View file

@ -4,6 +4,7 @@ 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 core from '@actions/core';
import * as path from 'path';
import * as installer from '../src/install-pypy';
@ -51,6 +52,22 @@ describe('findRelease', () => {
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.3.3-${extensionName}`
};
let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
beforeEach(() => {
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
});
it("Python version is found, but PyPy version doesn't match", () => {
const pythonVersion = '3.6';
const pypyVersion = '7.3.7';
@ -133,6 +150,10 @@ describe('findRelease', () => {
describe('installPyPy', () => {
let tcFind: jest.SpyInstance;
let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
@ -158,6 +179,15 @@ describe('installPyPy', () => {
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir);
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest']);
@ -194,7 +224,7 @@ describe('installPyPy', () => {
it('throw if release is not found', async () => {
await expect(
installer.installPyPy('7.3.3', '3.6.17', architecture)
installer.installPyPy('7.3.3', '3.6.17', architecture, undefined)
).rejects.toThrowError(
`PyPy version 3.6.17 (7.3.3) with arch ${architecture} not found`
);
@ -214,7 +244,7 @@ describe('installPyPy', () => {
spyChmodSync.mockImplementation(() => undefined);
await expect(
installer.installPyPy('7.3.x', '3.6.12', architecture)
installer.installPyPy('7.3.x', '3.6.12', architecture, undefined)
).resolves.toEqual({
installDir: path.join(toolDir, 'PyPy', '3.6.12', architecture),
resolvedPythonVersion: '3.6.12',

View file

@ -11,7 +11,11 @@ inputs:
description: 'Used to specify a package manager for caching in the default directory. Supported values: pip, pipenv, poetry.'
required: false
architecture:
<<<<<<< HEAD
description: "The target architecture (x86, x64) of the Python/PyPy interpreter."
=======
description: 'The target architecture (x86, x64) of the Python interpreter.'
>>>>>>> main
check-latest:
description: 'Set this option if you want the action to check for the latest available version that satisfies the version spec.'
default: false

142
dist/setup/index.js vendored
View file

@ -64430,8 +64430,17 @@ class PipCache extends cache_distributor_1.default {
computeKeys() {
return __awaiter(this, void 0, void 0, function* () {
const hash = yield glob.hashFiles(this.cacheDependencyPath);
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
let primaryKey = '';
let restoreKey = '';
if (utils_1.IS_LINUX) {
const osRelease = yield utils_1.getLinuxOSReleaseInfo();
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}`;
}
else {
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
}
return {
primaryKey,
restoreKey: [restoreKey]
@ -64564,9 +64573,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const glob = __importStar(__nccwpck_require__(8090));
const io = __importStar(__nccwpck_require__(7436));
const path = __importStar(__nccwpck_require__(1017));
const exec = __importStar(__nccwpck_require__(1514));
const core = __importStar(__nccwpck_require__(2186));
const cache_distributor_1 = __importDefault(__nccwpck_require__(8953));
const utils_1 = __nccwpck_require__(1314);
class PoetryCache extends cache_distributor_1.default {
constructor(pythonVersion, patterns = '**/poetry.lock') {
super('poetry', patterns);
@ -64582,6 +64594,17 @@ class PoetryCache extends cache_distributor_1.default {
if (poetryConfig['virtualenvs.in-project'] === true) {
paths.push(path.join(process.cwd(), '.venv'));
}
const pythonLocation = yield io.which('python');
if (pythonLocation) {
core.debug(`pythonLocation is ${pythonLocation}`);
const { exitCode, stderr } = yield exec.getExecOutput(`poetry env use ${pythonLocation}`, undefined, { ignoreReturnCode: true });
if (exitCode) {
utils_1.logWarning(stderr);
}
}
else {
utils_1.logWarning('python binaries were not found in PATH');
}
return paths;
});
}
@ -64662,19 +64685,34 @@ const utils_1 = __nccwpck_require__(1314);
const semver = __importStar(__nccwpck_require__(1383));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
function findPyPyVersion(versionSpec, architecture, updateEnvironment) {
function findPyPyVersion(versionSpec, architecture, updateEnvironment, checkLatest) {
return __awaiter(this, void 0, void 0, function* () {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir;
let releases;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
if (checkLatest) {
releases = yield pypyInstall.getAvailablePyPyVersions();
if (releases && releases.length > 0) {
const releaseData = pypyInstall.findRelease(releases, pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture);
if (releaseData) {
core.info(`Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`);
pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion;
pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion;
}
else {
core.info(`Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`);
}
}
}
({ installDir, resolvedPythonVersion, resolvedPyPyVersion } = findPyPyToolCache(pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture));
if (!installDir) {
({
installDir,
resolvedPythonVersion,
resolvedPyPyVersion
} = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture));
} = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture, releases));
}
const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin';
const _binDir = path.join(installDir, pipDir);
@ -64824,15 +64862,28 @@ function binDir(installDir) {
return path.join(installDir, 'bin');
}
}
function useCpythonVersion(version, architecture, updateEnvironment) {
function useCpythonVersion(version, architecture, updateEnvironment, checkLatest) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let manifest = null;
const desugaredVersionSpec = desugarDevVersion(version);
const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
if (checkLatest) {
manifest = yield installer.getManifest();
const resolvedVersion = (_a = (yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest))) === null || _a === void 0 ? void 0 : _a.version;
if (resolvedVersion) {
semanticVersionSpec = resolvedVersion;
core.info(`Resolved as '${semanticVersionSpec}'`);
}
else {
core.info(`Failed to resolve version ${semanticVersionSpec} from manifest`);
}
}
let installDir = tc.find('Python', semanticVersionSpec, architecture);
if (!installDir) {
core.info(`Version ${semanticVersionSpec} was not found in the local cache`);
const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture);
const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest);
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
core.info(`Version ${semanticVersionSpec} is available for downloading`);
yield installer.installCpythonFromRelease(foundRelease);
@ -64951,7 +65002,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.installPyPy = void 0;
exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0;
const path = __importStar(__nccwpck_require__(1017));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
@ -64960,10 +65011,10 @@ const httpm = __importStar(__nccwpck_require__(9925));
const exec = __importStar(__nccwpck_require__(1514));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const utils_1 = __nccwpck_require__(1314);
function installPyPy(pypyVersion, pythonVersion, architecture) {
function installPyPy(pypyVersion, pythonVersion, architecture, releases) {
return __awaiter(this, void 0, void 0, function* () {
let downloadDir;
const releases = yield getAvailablePyPyVersions();
releases = releases !== null && releases !== void 0 ? releases : (yield getAvailablePyPyVersions());
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
@ -65009,6 +65060,7 @@ function getAvailablePyPyVersions() {
return response.result;
});
}
exports.getAvailablePyPyVersions = getAvailablePyPyVersions;
function createPyPySymlink(pypyBinaryPath, pythonVersion) {
return __awaiter(this, void 0, void 0, function* () {
const version = semver.coerce(pythonVersion);
@ -65131,7 +65183,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.installCpythonFromRelease = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
exports.installCpythonFromRelease = exports.getManifest = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
const path = __importStar(__nccwpck_require__(1017));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
@ -65143,13 +65195,21 @@ const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
function findReleaseFromManifest(semanticVersionSpec, architecture) {
function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) {
return __awaiter(this, void 0, void 0, function* () {
const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
return yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture);
if (!manifest) {
manifest = yield getManifest();
}
const foundRelease = yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture);
return foundRelease;
});
}
exports.findReleaseFromManifest = findReleaseFromManifest;
function getManifest() {
core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
return tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
}
exports.getManifest = getManifest;
function installPython(workingDirectory) {
return __awaiter(this, void 0, void 0, function* () {
const options = {
@ -65232,7 +65292,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.logWarning = void 0;
const core = __importStar(__nccwpck_require__(2186));
const finder = __importStar(__nccwpck_require__(9996));
const finderPyPy = __importStar(__nccwpck_require__(4003));
@ -65262,17 +65321,20 @@ function resolveVersionInput() {
}
if (versionFile) {
if (!fs_1.default.existsSync(versionFile)) {
logWarning(`The specified python version file at: ${versionFile} doesn't exist. Attempting to find .python-version file.`);
versionFile = '.python-version';
if (!fs_1.default.existsSync(versionFile)) {
throw new Error(`The ${versionFile} doesn't exist.`);
}
throw new Error(`The specified python version file at: ${versionFile} doesn't exist.`);
}
version = fs_1.default.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return version;
}
core.warning("Neither 'python-version' nor 'python-version-file' inputs were supplied.");
utils_1.logWarning("Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file.");
versionFile = '.python-version';
if (fs_1.default.existsSync(versionFile)) {
version = fs_1.default.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return version;
}
utils_1.logWarning(`${versionFile} doesn't exist.`);
return version;
}
function run() {
@ -65290,17 +65352,18 @@ function run() {
core.debug(`Python is expected to be installed into RUNNER_TOOL_CACHE=${process.env['RUNNER_TOOL_CACHE']}`);
try {
const version = resolveVersionInput();
const checkLatest = core.getBooleanInput('check-latest');
if (version) {
let pythonVersion;
const arch = core.getInput('architecture') || os.arch();
const updateEnvironment = core.getBooleanInput('update-environment');
if (isPyPyVersion(version)) {
const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment);
const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment, checkLatest);
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`);
}
else {
const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment);
const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest);
pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
}
@ -65320,11 +65383,6 @@ function run() {
}
});
}
function logWarning(message) {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
}
exports.logWarning = logWarning;
run();
@ -65354,16 +65412,26 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
exports.logWarning = exports.getLinuxOSReleaseInfo = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
const cache = __importStar(__nccwpck_require__(7799));
const core = __importStar(__nccwpck_require__(2186));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const path = __importStar(__nccwpck_require__(1017));
const semver = __importStar(__nccwpck_require__(1383));
const exec = __importStar(__nccwpck_require__(1514));
exports.IS_WINDOWS = process.platform === 'win32';
exports.IS_LINUX = process.platform === 'linux';
exports.WINDOWS_ARCHS = ['x86', 'x64'];
@ -65447,6 +65515,22 @@ function isCacheFeatureAvailable() {
return true;
}
exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
function getLinuxOSReleaseInfo() {
return __awaiter(this, void 0, void 0, function* () {
const { stdout, stderr, exitCode } = yield exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], {
silent: true
});
const [osRelease, osVersion] = stdout.trim().split('\n');
core.debug(`OS Release: ${osRelease}, Version: ${osVersion}`);
return `${osVersion}-${osRelease}`;
});
}
exports.getLinuxOSReleaseInfo = getLinuxOSReleaseInfo;
function logWarning(message) {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
}
exports.logWarning = logWarning;
/***/ }),

View file

@ -7,7 +7,7 @@ import * as path from 'path';
import os from 'os';
import CacheDistributor from './cache-distributor';
import {IS_WINDOWS} from '../utils';
import {getLinuxOSReleaseInfo, IS_LINUX, IS_WINDOWS} from '../utils';
class PipCache extends CacheDistributor {
constructor(
@ -57,8 +57,17 @@ class PipCache extends CacheDistributor {
protected async computeKeys() {
const hash = await glob.hashFiles(this.cacheDependencyPath);
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
let primaryKey = '';
let restoreKey = '';
if (IS_LINUX) {
const osRelease = await getLinuxOSReleaseInfo();
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}`;
} else {
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
}
return {
primaryKey,

View file

@ -1,9 +1,11 @@
import * as glob from '@actions/glob';
import * as os from 'os';
import * as io from '@actions/io';
import * as path from 'path';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import CacheDistributor from './cache-distributor';
import {logWarning} from '../utils';
class PoetryCache extends CacheDistributor {
constructor(
@ -28,6 +30,26 @@ class PoetryCache extends CacheDistributor {
paths.push(path.join(process.cwd(), '.venv'));
}
const pythonLocation = await io.which('python');
if (pythonLocation) {
core.debug(`pythonLocation is ${pythonLocation}`);
const {
exitCode,
stderr
} = await exec.getExecOutput(
`poetry env use ${pythonLocation}`,
undefined,
{ignoreReturnCode: true}
);
if (exitCode) {
logWarning(stderr);
}
} else {
logWarning('python binaries were not found in PATH');
}
return paths;
}

View file

@ -6,7 +6,8 @@ import {
validateVersion,
getPyPyVersionFromPath,
readExactPyPyVersionFile,
validatePythonVersionFormatForPyPy
validatePythonVersionFormatForPyPy,
IPyPyManifestRelease
} from './utils';
import * as semver from 'semver';
@ -21,14 +22,40 @@ interface IPyPyVersionSpec {
export async function findPyPyVersion(
versionSpec: string,
architecture: string,
updateEnvironment: boolean
updateEnvironment: boolean,
checkLatest: boolean
): Promise<{resolvedPyPyVersion: string; resolvedPythonVersion: string}> {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null;
let releases: IPyPyManifestRelease[] | undefined;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
if (checkLatest) {
releases = await pypyInstall.getAvailablePyPyVersions();
if (releases && releases.length > 0) {
const releaseData = pypyInstall.findRelease(
releases,
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
architecture
);
if (releaseData) {
core.info(
`Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`
);
pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion;
pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion;
} else {
core.info(
`Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`
);
}
}
}
({installDir, resolvedPythonVersion, resolvedPyPyVersion} = findPyPyToolCache(
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
@ -43,7 +70,8 @@ export async function findPyPyVersion(
} = await pypyInstall.installPyPy(
pypyVersionSpec.pypyVersion,
pypyVersionSpec.pythonVersion,
architecture
architecture,
releases
));
}

View file

@ -33,12 +33,34 @@ function binDir(installDir: string): string {
export async function useCpythonVersion(
version: string,
architecture: string,
updateEnvironment: boolean
updateEnvironment: boolean,
checkLatest: boolean
): Promise<InstalledVersion> {
let manifest: tc.IToolRelease[] | null = null;
const desugaredVersionSpec = desugarDevVersion(version);
const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
if (checkLatest) {
manifest = await installer.getManifest();
const resolvedVersion = (
await installer.findReleaseFromManifest(
semanticVersionSpec,
architecture,
manifest
)
)?.version;
if (resolvedVersion) {
semanticVersionSpec = resolvedVersion;
core.info(`Resolved as '${semanticVersionSpec}'`);
} else {
core.info(
`Failed to resolve version ${semanticVersionSpec} from manifest`
);
}
}
let installDir: string | null = tc.find(
'Python',
semanticVersionSpec,
@ -50,7 +72,8 @@ export async function useCpythonVersion(
);
const foundRelease = await installer.findReleaseFromManifest(
semanticVersionSpec,
architecture
architecture,
manifest
);
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {

View file

@ -19,11 +19,13 @@ import {
export async function installPyPy(
pypyVersion: string,
pythonVersion: string,
architecture: string
architecture: string,
releases: IPyPyManifestRelease[] | undefined
) {
let downloadDir;
const releases = await getAvailablePyPyVersions();
releases = releases ?? (await getAvailablePyPyVersions());
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
@ -78,7 +80,7 @@ export async function installPyPy(
return {installDir, resolvedPythonVersion, resolvedPyPyVersion};
}
async function getAvailablePyPyVersions() {
export async function getAvailablePyPyVersions() {
const url = 'https://downloads.python.org/pypy/versions.json';
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');

View file

@ -14,20 +14,33 @@ export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_O
export async function findReleaseFromManifest(
semanticVersionSpec: string,
architecture: string
architecture: string,
manifest: tc.IToolRelease[] | null
): Promise<tc.IToolRelease | undefined> {
const manifest: tc.IToolRelease[] = await tc.getManifestFromRepo(
MANIFEST_REPO_OWNER,
MANIFEST_REPO_NAME,
AUTH,
MANIFEST_REPO_BRANCH
);
return await tc.findFromManifest(
if (!manifest) {
manifest = await getManifest();
}
const foundRelease = await tc.findFromManifest(
semanticVersionSpec,
false,
manifest,
architecture
);
return foundRelease;
}
export function getManifest(): Promise<tc.IToolRelease[]> {
core.debug(
`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`
);
return tc.getManifestFromRepo(
MANIFEST_REPO_OWNER,
MANIFEST_REPO_NAME,
AUTH,
MANIFEST_REPO_BRANCH
);
}
async function installPython(workingDirectory: string) {

View file

@ -5,7 +5,12 @@ import * as path from 'path';
import * as os from 'os';
import fs from 'fs';
import {getCacheDistributor} from './cache-distributions/cache-factory';
import {isCacheFeatureAvailable, IS_LINUX, IS_WINDOWS} from './utils';
import {
isCacheFeatureAvailable,
logWarning,
IS_LINUX,
IS_WINDOWS
} from './utils';
function isPyPyVersion(versionSpec: string) {
return versionSpec.startsWith('pypy');
@ -38,24 +43,26 @@ function resolveVersionInput(): string {
if (versionFile) {
if (!fs.existsSync(versionFile)) {
logWarning(
`The specified python version file at: ${versionFile} doesn't exist. Attempting to find .python-version file.`
throw new Error(
`The specified python version file at: ${versionFile} doesn't exist.`
);
versionFile = '.python-version';
if (!fs.existsSync(versionFile)) {
throw new Error(`The ${versionFile} doesn't exist.`);
}
}
version = fs.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return version;
}
core.warning(
"Neither 'python-version' nor 'python-version-file' inputs were supplied."
logWarning(
"Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file."
);
versionFile = '.python-version';
if (fs.existsSync(versionFile)) {
version = fs.readFileSync(versionFile, 'utf8');
core.info(`Resolved ${versionFile} as ${version}`);
return version;
}
logWarning(`${versionFile} doesn't exist.`);
return version;
}
@ -73,6 +80,8 @@ async function run() {
);
try {
const version = resolveVersionInput();
const checkLatest = core.getBooleanInput('check-latest');
if (version) {
let pythonVersion: string;
const arch: string = core.getInput('architecture') || os.arch();
@ -81,7 +90,8 @@ async function run() {
const installed = await finderPyPy.findPyPyVersion(
version,
arch,
updateEnvironment
updateEnvironment,
checkLatest
);
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
core.info(
@ -91,7 +101,8 @@ async function run() {
const installed = await finder.useCpythonVersion(
version,
arch,
updateEnvironment
updateEnvironment,
checkLatest
);
pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
@ -113,9 +124,4 @@ async function run() {
}
}
export function logWarning(message: string): void {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
}
run();

View file

@ -3,6 +3,7 @@ import * as core from '@actions/core';
import fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import * as exec from '@actions/exec';
export const IS_WINDOWS = process.platform === 'win32';
export const IS_LINUX = process.platform === 'linux';
@ -119,3 +120,24 @@ export function isCacheFeatureAvailable(): boolean {
return true;
}
export async function getLinuxOSReleaseInfo() {
const {stdout, stderr, exitCode} = await exec.getExecOutput(
'lsb_release',
['-i', '-r', '-s'],
{
silent: true
}
);
const [osRelease, osVersion] = stdout.trim().split('\n');
core.debug(`OS Release: ${osRelease}, Version: ${osVersion}`);
return `${osVersion}-${osRelease}`;
}
export function logWarning(message: string): void {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
}