resolved comments, update readme, add e2e tests.

This commit is contained in:
Dmitry Shibanov 2020-12-15 16:27:56 +03:00 committed by GitHub
parent 739154f76b
commit 3d613a97df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 180 additions and 38 deletions

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

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

View File

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

View File

@ -17,6 +17,7 @@ This action sets up a Python environment for use in actions by:
- Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed. - Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed.
- Automatic setup and download of Python packages if using a self-hosted runner. - Automatic setup and download of Python packages if using a self-hosted runner.
- Support for pre-release versions of Python. - Support for pre-release versions of Python.
- Support for installation 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
- pypy-3.7 # the latest available version of PyPy
- 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 [Available versions of PyPy](#available-versions-of-pypy) section.
# Getting started with Python + Actions # Getting started with Python + Actions
Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions). Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions).
@ -129,7 +150,21 @@ Check out our detailed guide on using [Python with GitHub Actions](https://help.
- If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache. - If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache.
- Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)). - Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)).
- All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file. - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file.
- If there is a specific version of Python that is not available, you can open an issue here. - If there is a specific version of Python that is not available, you can open an issue here
# Available versions of PyPy
`setup-python` is able to configure PyPy from two sources:
- Preinstalled versions of PyPy in the tools cache on GitHub-hosted runners
- For detailed information regarding the available versions of PyPy that are installed see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software).
- For the latest PyPy release, all version of Python are cached.
- Cache is updated with 1-2 weeks delay. if you specify PyPy as `pypy-3.6`, the version from cache will be used although a new version is available. If you need to start using the recently released version right after release, you should specify exact PyPy version `pypy-3.6-v7.3.3`.
- Downloadable PyPy versions from [official PyPy site](https://downloads.python.org/pypy/).
- All available versions are listed in the [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>]`.
Parameter `<pypy_version>` is optional and can be skipped. The latest version will be used in this case.
```
pypy-3.6 # the latest available version of PyPy
pypy-3.7 # the latest available version of PyPy
pypy-2.7 # the latest available version of PyPy
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

42
dist/index.js vendored
View File

@ -1154,7 +1154,8 @@ function findPyPyToolCache(pythonVersion, pypyVersion, architecture) {
function parsePyPyVersion(versionSpec) { function parsePyPyVersion(versionSpec) {
const versions = versionSpec.split('-').filter(item => !!item); const versions = versionSpec.split('-').filter(item => !!item);
if (versions.length < 2) { if (versions.length < 2) {
throw new Error("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See readme for more examples."); core.setFailed("Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See README for examples and documentation.");
process.exit();
} }
const pythonVersion = versions[1]; const pythonVersion = versions[1];
let pypyVersion; let pypyVersion;
@ -1164,6 +1165,10 @@ function parsePyPyVersion(versionSpec) {
else { else {
pypyVersion = 'x'; pypyVersion = 'x';
} }
if (!utils_1.validateVersion(pythonVersion) || !utils_1.validateVersion(pypyVersion)) {
core.setFailed("Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation.");
process.exit();
}
return { return {
pypyVersion: pypyVersion, pypyVersion: pypyVersion,
pythonVersion: pythonVersion pythonVersion: pythonVersion
@ -2316,6 +2321,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(__webpack_require__(747)); const fs = __importStar(__webpack_require__(747));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
const semver = __importStar(__webpack_require__(876));
exports.IS_WINDOWS = process.platform === 'win32'; exports.IS_WINDOWS = process.platform === 'win32';
exports.IS_LINUX = process.platform === 'linux'; exports.IS_LINUX = process.platform === 'linux';
/** create Symlinks for downloaded PyPy /** create Symlinks for downloaded PyPy
@ -2334,6 +2340,14 @@ function createSymlinkInFolder(folderPath, sourceName, targetName, setExecutable
} }
} }
exports.createSymlinkInFolder = createSymlinkInFolder; 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;
/***/ }), /***/ }),
@ -2766,9 +2780,14 @@ function installPyPy(pypyVersion, pythonVersion, architecture) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let downloadDir; let downloadDir;
const releases = yield getAvailablePyPyVersions(); const releases = yield getAvailablePyPyVersions();
if (!releases || releases.length === 0) {
core.setFailed('No release was found in PyPy version.json');
process.exit();
}
const releaseData = findRelease(releases, pythonVersion, pypyVersion, architecture); const releaseData = findRelease(releases, pythonVersion, pypyVersion, architecture);
if (!releaseData || !releaseData.foundAsset) { if (!releaseData || !releaseData.foundAsset) {
throw new Error(`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`); core.setFailed(`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`);
process.exit();
} }
const { foundAsset, resolvedPythonVersion, resolvedPyPyVersion } = releaseData; const { foundAsset, resolvedPythonVersion, resolvedPyPyVersion } = releaseData;
let downloadUrl = `${foundAsset.download_url}`; let downloadUrl = `${foundAsset.download_url}`;
@ -2786,7 +2805,7 @@ function installPyPy(pypyVersion, pythonVersion, architecture) {
const archiveName = fs.readdirSync(downloadDir)[0]; const archiveName = fs.readdirSync(downloadDir)[0];
const toolDir = path.join(downloadDir, archiveName); const toolDir = path.join(downloadDir, archiveName);
let installDir = toolDir; let installDir = toolDir;
if (!isNightlyKeyword(resolvedPyPyVersion)) { if (!utils_1.isNightlyKeyword(resolvedPyPyVersion)) {
installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture); installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture);
} }
writeExactPyPyVersionFile(installDir, resolvedPyPyVersion); writeExactPyPyVersionFile(installDir, resolvedPyPyVersion);
@ -2803,7 +2822,8 @@ function getAvailablePyPyVersions() {
const http = new httpm.HttpClient('tool-cache'); const http = new httpm.HttpClient('tool-cache');
const response = yield http.getJson(url); const response = yield http.getJson(url);
if (!response.result) { if (!response.result) {
throw new Error(`Unable to retrieve the list of available PyPy versions from '${url}'`); core.setFailed(`Unable to retrieve the list of available PyPy versions from '${url}'`);
process.exit();
} }
return response.result; return response.result;
}); });
@ -2830,12 +2850,13 @@ function installPip(pythonLocation) {
} }
function findRelease(releases, pythonVersion, pypyVersion, architecture) { function findRelease(releases, pythonVersion, pypyVersion, architecture) {
const filterReleases = releases.filter(item => { const filterReleases = releases.filter(item => {
const isPythonVersionSatisfies = semver.satisfies(semver.coerce(item.python_version), pythonVersion); const isPythonVersionSatisfied = semver.satisfies(semver.coerce(item.python_version), pythonVersion);
const isPyPyNightly = isNightlyKeyword(pypyVersion) && isNightlyKeyword(item.pypy_version); const isPyPyNightly = utils_1.isNightlyKeyword(pypyVersion) && utils_1.isNightlyKeyword(item.pypy_version);
const isPyPyVersionSatisfies = isPyPyNightly || const isPyPyVersionSatisfied = isPyPyNightly ||
semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion); semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion);
const isArchExists = item.files.some(file => file.arch === architecture && file.platform === process.platform); const isArchPresent = item.files &&
return isPythonVersionSatisfies && isPyPyVersionSatisfies && isArchExists; item.files.some(file => file.arch === architecture && file.platform === process.platform);
return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent;
}); });
if (filterReleases.length === 0) { if (filterReleases.length === 0) {
return null; return null;
@ -2884,9 +2905,6 @@ function getPyPyBinaryPath(installDir) {
return utils_1.IS_WINDOWS ? installDir : _binDir; return utils_1.IS_WINDOWS ? installDir : _binDir;
} }
exports.getPyPyBinaryPath = getPyPyBinaryPath; exports.getPyPyBinaryPath = getPyPyBinaryPath;
function isNightlyKeyword(pypyVersion) {
return pypyVersion === 'nightly';
}
function pypyVersionToSemantic(versionSpec) { function pypyVersionToSemantic(versionSpec) {
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g; const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
return versionSpec.replace(prereleaseVersion, '$1-$2.$3'); return versionSpec.replace(prereleaseVersion, '$1-$2.$3');

View File

@ -1,6 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import * as pypyInstall from './install-pypy'; import * as pypyInstall from './install-pypy';
import {IS_WINDOWS} from './utils'; import {IS_WINDOWS, validateVersion} from './utils';
import * as semver from 'semver'; import * as semver from 'semver';
import * as core from '@actions/core'; import * as core from '@actions/core';
@ -93,10 +93,12 @@ function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec {
const versions = versionSpec.split('-').filter(item => !!item); const versions = versionSpec.split('-').filter(item => !!item);
if (versions.length < 2) { if (versions.length < 2) {
throw new Error( core.setFailed(
"Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See readme for more examples." "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy-<python-version>'. See README for examples and documentation."
); );
process.exit();
} }
const pythonVersion = versions[1]; const pythonVersion = versions[1];
let pypyVersion: string; let pypyVersion: string;
if (versions.length > 2) { if (versions.length > 2) {
@ -105,6 +107,13 @@ function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec {
pypyVersion = 'x'; pypyVersion = 'x';
} }
if (!validateVersion(pythonVersion) || !validateVersion(pypyVersion)) {
core.setFailed(
"Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation."
);
process.exit();
}
return { return {
pypyVersion: pypyVersion, pypyVersion: pypyVersion,
pythonVersion: pythonVersion pythonVersion: pythonVersion

View File

@ -6,7 +6,12 @@ import * as httpm from '@actions/http-client';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as fs from 'fs'; import * as fs from 'fs';
import {IS_WINDOWS, IPyPyManifestRelease, createSymlinkInFolder} from './utils'; import {
IS_WINDOWS,
IPyPyManifestRelease,
createSymlinkInFolder,
isNightlyKeyword
} from './utils';
const PYPY_VERSION_FILE = 'PYPY_VERSION'; const PYPY_VERSION_FILE = 'PYPY_VERSION';
@ -18,6 +23,11 @@ export async function installPyPy(
let downloadDir; let downloadDir;
const releases = await getAvailablePyPyVersions(); const releases = await getAvailablePyPyVersions();
if (!releases || releases.length === 0) {
core.setFailed('No release was found in PyPy version.json');
process.exit();
}
const releaseData = findRelease( const releaseData = findRelease(
releases, releases,
pythonVersion, pythonVersion,
@ -26,9 +36,10 @@ export async function installPyPy(
); );
if (!releaseData || !releaseData.foundAsset) { if (!releaseData || !releaseData.foundAsset) {
throw new Error( core.setFailed(
`PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found` `PyPy version ${pythonVersion} (${pypyVersion}) with arch ${architecture} not found`
); );
process.exit();
} }
const {foundAsset, resolvedPythonVersion, resolvedPyPyVersion} = releaseData; const {foundAsset, resolvedPythonVersion, resolvedPyPyVersion} = releaseData;
@ -74,9 +85,10 @@ async function getAvailablePyPyVersions() {
const response = await http.getJson<IPyPyManifestRelease[]>(url); const response = await http.getJson<IPyPyManifestRelease[]>(url);
if (!response.result) { if (!response.result) {
throw new Error( core.setFailed(
`Unable to retrieve the list of available PyPy versions from '${url}'` `Unable to retrieve the list of available PyPy versions from '${url}'`
); );
process.exit();
} }
return response.result; return response.result;
@ -124,19 +136,21 @@ function findRelease(
architecture: string architecture: string
) { ) {
const filterReleases = releases.filter(item => { const filterReleases = releases.filter(item => {
const isPythonVersionSatisfies = semver.satisfies( const isPythonVersionSatisfied = semver.satisfies(
semver.coerce(item.python_version)!, semver.coerce(item.python_version)!,
pythonVersion pythonVersion
); );
const isPyPyNightly = const isPyPyNightly =
isNightlyKeyword(pypyVersion) && isNightlyKeyword(item.pypy_version); isNightlyKeyword(pypyVersion) && isNightlyKeyword(item.pypy_version);
const isPyPyVersionSatisfies = const isPyPyVersionSatisfied =
isPyPyNightly || isPyPyNightly ||
semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion); semver.satisfies(pypyVersionToSemantic(item.pypy_version), pypyVersion);
const isArchExists = item.files.some( const isArchPresent =
file => file.arch === architecture && file.platform === process.platform item.files &&
); item.files.some(
return isPythonVersionSatisfies && isPyPyVersionSatisfies && isArchExists; file => file.arch === architecture && file.platform === process.platform
);
return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent;
}); });
if (filterReleases.length === 0) { if (filterReleases.length === 0) {
@ -206,10 +220,6 @@ export function getPyPyBinaryPath(installDir: string) {
return IS_WINDOWS ? installDir : _binDir; return IS_WINDOWS ? installDir : _binDir;
} }
function isNightlyKeyword(pypyVersion: string) {
return pypyVersion === 'nightly';
}
export function pypyVersionToSemantic(versionSpec: string) { export function pypyVersionToSemantic(versionSpec: string) {
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g; const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
return versionSpec.replace(prereleaseVersion, '$1-$2.$3'); return versionSpec.replace(prereleaseVersion, '$1-$2.$3');

View File

@ -1,5 +1,6 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as semver from 'semver';
export const IS_WINDOWS = process.platform === 'win32'; export const IS_WINDOWS = process.platform === 'win32';
export const IS_LINUX = process.platform === 'linux'; export const IS_LINUX = process.platform === 'linux';
@ -40,3 +41,11 @@ export function createSymlinkInFolder(
fs.chmodSync(targetPath, '755'); fs.chmodSync(targetPath, '755');
} }
} }
export function validateVersion(version: string) {
return isNightlyKeyword(version) || Boolean(semver.validRange(version));
}
export function isNightlyKeyword(pypyVersion: string) {
return pypyVersion === 'nightly';
}