diff --git a/.github/workflows/e2e-cache-freethreaded.yml b/.github/workflows/e2e-cache-freethreaded.yml index 096892cb3..99a35b9d7 100644 --- a/.github/workflows/e2e-cache-freethreaded.yml +++ b/.github/workflows/e2e-cache-freethreaded.yml @@ -219,3 +219,56 @@ jobs: pip-version: '25.0.1' - name: Install dependencies run: pip install numpy pandas requests + + python-pip-dependencies-caching-with-pip-install: + name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + ubuntu-latest, + ubuntu-22.04, + ubuntu-24.04-arm, + ubuntu-22.04-arm, + windows-latest, + macos-latest, + macos-13 + ] + python-version: [3.13.0t, 3.13.1t, 3.13.2t] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: ./ + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + pip-install: numpy pandas requests + + python-pip-dependencies-caching-path-with-pip-install: + name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + ubuntu-latest, + ubuntu-22.04, + ubuntu-24.04-arm, + ubuntu-22.04-arm, + windows-latest, + macos-latest, + macos-13 + ] + python-version: [3.13.0t, 3.13.1t, 3.13.2t] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: ./ + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: __tests__/data/requirements.txt + pip-install: numpy pandas requests diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index 76a8f8c51..2a6eb50fe 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -306,3 +306,56 @@ jobs: pip-version: '25.0.1' - name: Install dependencies run: pip install numpy pandas requests + + python-pip-dependencies-caching-with-pip-install: + name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + ubuntu-latest, + ubuntu-24.04-arm, + ubuntu-22.04, + ubuntu-22.04-arm, + windows-latest, + macos-latest, + macos-13 + ] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: ./ + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + pip-install: numpy pandas requests + + python-pip-dependencies-caching-path-with-pip-install: + name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + [ + ubuntu-latest, + ubuntu-24.04-arm, + ubuntu-22.04, + ubuntu-22.04-arm, + windows-latest, + macos-latest, + macos-13 + ] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: ./ + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: __tests__/data/requirements.txt + pip-install: numpy pandas requests diff --git a/README.md b/README.md index c3f16cf62..e6c24623e 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ See examples of using `cache` and `cache-dependency-path` for `pipenv` and `poet - [Using `setup-python` on GHES](docs/advanced-usage.md#using-setup-python-on-ghes) - [Allow pre-releases](docs/advanced-usage.md#allow-pre-releases) - [Using the pip-version input](docs/advanced-usage.md#using-the-pip-version-input) +- [Using the pip-install input](docs/advanced-usage.md#using-the-pip-install-input) ## Recommended permissions diff --git a/action.yml b/action.yml index e469b7b2b..2f4c11d9e 100644 --- a/action.yml +++ b/action.yml @@ -31,6 +31,8 @@ inputs: default: false pip-version: description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]." + pip-install: + description: "Used to specify the packages to install with pip after setting up Python. Can be a requirements file or package names." outputs: python-version: description: "The installed Python or PyPy version. Useful when given a version range as input." @@ -38,6 +40,7 @@ outputs: description: "A boolean value to indicate a cache entry was found" python-path: description: "The absolute path to the Python or PyPy executable." + runs: using: 'node20' main: 'dist/setup/index.js' diff --git a/dist/setup/index.js b/dist/setup/index.js index 2f06fb35d..3b2d87188 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -97945,12 +97945,30 @@ const os = __importStar(__nccwpck_require__(857)); const fs_1 = __importDefault(__nccwpck_require__(9896)); const cache_factory_1 = __nccwpck_require__(665); const utils_1 = __nccwpck_require__(1798); +const exec_1 = __nccwpck_require__(5236); function isPyPyVersion(versionSpec) { return versionSpec.startsWith('pypy'); } function isGraalPyVersion(versionSpec) { return versionSpec.startsWith('graalpy'); } +function installPipPackages() { + return __awaiter(this, void 0, void 0, function* () { + const pipInstall = core.getInput('pip-install'); + if (!pipInstall) { + return; + } + core.info(`Installing pip packages: ${pipInstall}`); + try { + const installArgs = pipInstall.trim().split(/\s+/); + yield (0, exec_1.exec)('python', ['-m', 'pip', 'install', ...installArgs]); + core.info('Successfully installed pip packages'); + } + catch (error) { + core.setFailed(`Failed to install pip packages from "${pipInstall}". Please verify that the package names and versions in the requirements file are correct, that the specified packages and versions can be resolved from PyPI or the configured package index, and that your network connection is stable and allows access to the package index.`); + } + }); +} function cacheDependencies(cache, pythonVersion) { return __awaiter(this, void 0, void 0, function* () { const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; @@ -98079,6 +98097,7 @@ function run() { if (cache && (0, utils_1.isCacheFeatureAvailable)()) { yield cacheDependencies(cache, pythonVersion); } + yield installPipPackages(); } else { core.warning('The `python-version` input is not set. The version of Python currently in `PATH` will be used.'); diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 188fa9d65..62f659a07 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -23,6 +23,7 @@ - [Using `setup-python` on GHES](advanced-usage.md#using-setup-python-on-ghes) - [Allow pre-releases](advanced-usage.md#allow-pre-releases) - [Using the pip-version input](advanced-usage.md#using-the-pip-version-input) +- [Using the pip-install input](advanced-usage.md#using-the-pip-install-input) ## Using the `python-version` input @@ -671,4 +672,23 @@ The version of Pip should be specified in the format `major`, `major.minor`, or ``` > The `pip-version` input is supported only with standard Python versions. It is not available when using PyPy or GraalPy. -> Using a specific or outdated version of pip may result in compatibility or security issues and can cause job failures. For best practices and guidance, refer to the official [pip documentation](https://pip.pypa.io/en/stable/). \ No newline at end of file +> Using a specific or outdated version of pip may result in compatibility or security issues and can cause job failures. For best practices and guidance, refer to the official [pip documentation](https://pip.pypa.io/en/stable/). + +## Using the pip-install input + +The `pip-install` input allows you to install dependencies as part of the Python setup step. + + +```yaml + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + pip-install: -r requirements.txt +``` +> Note: This feature is intended for standard pip-based dependency installations. +For complex workflows, or alternative package managers (e.g., poetry, pipenv), we recommend using separate steps to maintain clarity and flexibility. + + diff --git a/src/setup-python.ts b/src/setup-python.ts index 106b415a6..1465f121a 100644 --- a/src/setup-python.ts +++ b/src/setup-python.ts @@ -13,6 +13,7 @@ import { getVersionInputFromFile, getVersionsInputFromPlainFile } from './utils'; +import {exec} from '@actions/exec'; function isPyPyVersion(versionSpec: string) { return versionSpec.startsWith('pypy'); @@ -22,6 +23,25 @@ function isGraalPyVersion(versionSpec: string) { return versionSpec.startsWith('graalpy'); } +async function installPipPackages() { + const pipInstall = core.getInput('pip-install'); + + if (!pipInstall) { + return; + } + core.info(`Installing pip packages: ${pipInstall}`); + + try { + const installArgs = pipInstall.trim().split(/\s+/); + await exec('python', ['-m', 'pip', 'install', ...installArgs]); + core.info('Successfully installed pip packages'); + } catch (error) { + core.setFailed( + `Failed to install pip packages from "${pipInstall}". Please verify that the package names and versions in the requirements file are correct, that the specified packages and versions can be resolved from PyPI or the configured package index, and that your network connection is stable and allows access to the package index.` + ); + } +} + export async function cacheDependencies(cache: string, pythonVersion: string) { const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined; @@ -194,6 +214,7 @@ async function run() { if (cache && isCacheFeatureAvailable()) { await cacheDependencies(cache, pythonVersion); } + await installPipPackages(); } else { core.warning( 'The `python-version` input is not set. The version of Python currently in `PATH` will be used.'