Skip to content

Commit ad2a595

Browse files
priyagupta108aparnajyothi-y
authored andcommitted
Add support for pip-version (#1129)
* Add pip-version input * Update workflow files * Add documentation * Update workflow files
1 parent 34f8914 commit ad2a595

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed

.github/workflows/e2e-cache-freethreaded.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,60 @@ jobs:
162162
run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
163163
- name: Install dependencies
164164
run: pipenv install requests
165+
166+
python-pip-dependencies-caching-with-pip-version:
167+
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})
168+
runs-on: ${{ matrix.os }}
169+
strategy:
170+
fail-fast: false
171+
matrix:
172+
os:
173+
[
174+
ubuntu-latest,
175+
ubuntu-22.04,
176+
ubuntu-24.04-arm,
177+
ubuntu-22.04-arm,
178+
windows-latest,
179+
macos-latest,
180+
macos-13
181+
]
182+
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
183+
steps:
184+
- uses: actions/checkout@v4
185+
- name: Setup Python
186+
uses: ./
187+
with:
188+
python-version: ${{ matrix.python-version }}
189+
cache: 'pip'
190+
pip-version: '25.0.1'
191+
- name: Install dependencies
192+
run: pip install numpy pandas requests
193+
194+
python-pip-dependencies-caching-path-with-pip-version:
195+
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path)
196+
runs-on: ${{ matrix.os }}
197+
strategy:
198+
fail-fast: false
199+
matrix:
200+
os:
201+
[
202+
ubuntu-latest,
203+
ubuntu-22.04,
204+
ubuntu-24.04-arm,
205+
ubuntu-22.04-arm,
206+
windows-latest,
207+
macos-latest,
208+
macos-13
209+
]
210+
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
211+
steps:
212+
- uses: actions/checkout@v4
213+
- name: Setup Python
214+
uses: ./
215+
with:
216+
python-version: ${{ matrix.python-version }}
217+
cache: 'pip'
218+
cache-dependency-path: __tests__/data/requirements.txt
219+
pip-version: '25.0.1'
220+
- name: Install dependencies
221+
run: pip install numpy pandas requests

.github/workflows/e2e-cache.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,60 @@ jobs:
249249
}
250250
- name: Run Python Script
251251
run: pipenv run python test-pipenv.py
252+
253+
python-pip-dependencies-caching-with-pip-version:
254+
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})
255+
runs-on: ${{ matrix.os }}
256+
strategy:
257+
fail-fast: false
258+
matrix:
259+
os:
260+
[
261+
ubuntu-latest,
262+
ubuntu-24.04-arm,
263+
ubuntu-22.04,
264+
ubuntu-22.04-arm,
265+
windows-latest,
266+
macos-latest,
267+
macos-13
268+
]
269+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
270+
steps:
271+
- uses: actions/checkout@v4
272+
- name: Setup Python
273+
uses: ./
274+
with:
275+
python-version: ${{ matrix.python-version }}
276+
cache: 'pip'
277+
pip-version: '25.0.1'
278+
- name: Install dependencies
279+
run: pip install numpy pandas requests
280+
281+
python-pip-dependencies-caching-path-with-pip-version:
282+
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path)
283+
runs-on: ${{ matrix.os }}
284+
strategy:
285+
fail-fast: false
286+
matrix:
287+
os:
288+
[
289+
ubuntu-latest,
290+
ubuntu-24.04-arm,
291+
ubuntu-22.04,
292+
ubuntu-22.04-arm,
293+
windows-latest,
294+
macos-latest,
295+
macos-13
296+
]
297+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
298+
steps:
299+
- uses: actions/checkout@v4
300+
- name: Setup Python
301+
uses: ./
302+
with:
303+
python-version: ${{ matrix.python-version }}
304+
cache: 'pip'
305+
cache-dependency-path: __tests__/data/requirements.txt
306+
pip-version: '25.0.1'
307+
- name: Install dependencies
308+
run: pip install numpy pandas requests

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ See examples of using `cache` and `cache-dependency-path` for `pipenv` and `poet
108108
- [Using `setup-python` with a self-hosted runner](docs/advanced-usage.md#using-setup-python-with-a-self-hosted-runner)
109109
- [Using `setup-python` on GHES](docs/advanced-usage.md#using-setup-python-on-ghes)
110110
- [Allow pre-releases](docs/advanced-usage.md#allow-pre-releases)
111+
- [Using the pip-version input](docs/advanced-usage.md#using-the-pip-version-input)
111112

112113
## Recommended permissions
113114

action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ inputs:
2929
freethreaded:
3030
description: "When 'true', use the freethreaded version of Python."
3131
default: false
32+
pip-version:
33+
description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]."
3234
outputs:
3335
python-version:
3436
description: "The installed Python or PyPy version. Useful when given a version range as input."

dist/setup/index.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95950,6 +95950,7 @@ const semver = __importStar(__nccwpck_require__(2088));
9595095950
const installer = __importStar(__nccwpck_require__(1919));
9595195951
const core = __importStar(__nccwpck_require__(7484));
9595295952
const tc = __importStar(__nccwpck_require__(3472));
95953+
const exec = __importStar(__nccwpck_require__(5236));
9595395954
// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
9595495955
// This is where pip is, along with anything that pip installs.
9595595956
// There is a separate directory for `pip install --user`.
@@ -95970,6 +95971,7 @@ function binDir(installDir) {
9597095971
return path.join(installDir, 'bin');
9597195972
}
9597295973
}
95974+
<<<<<<< HEAD
9597395975
async function useCpythonVersion(version, architecture, updateEnvironment, checkLatest, allowPreReleases, freethreaded) {
9597495976
let manifest = null;
9597595977
const { version: desugaredVersionSpec, freethreaded: versionFreethreaded } = desugarVersion(version);
@@ -95990,6 +95992,31 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
9599095992
if (resolvedVersion) {
9599195993
semanticVersionSpec = resolvedVersion;
9599295994
core.info(`Resolved as '${semanticVersionSpec}'`);
95995+
=======
95996+
function installPip(pythonLocation) {
95997+
return __awaiter(this, void 0, void 0, function* () {
95998+
const pipVersion = core.getInput('pip-version');
95999+
// Validate pip-version format: major[.minor][.patch]
96000+
const versionRegex = /^\d+(\.\d+)?(\.\d+)?$/;
96001+
if (pipVersion && !versionRegex.test(pipVersion)) {
96002+
throw new Error(`Invalid pip-version "${pipVersion}". Please specify a version in the format major[.minor][.patch].`);
96003+
}
96004+
if (pipVersion) {
96005+
core.info(`pip-version input is specified. Installing pip version ${pipVersion}`);
96006+
yield exec.exec(`${pythonLocation}/python -m pip install --upgrade pip==${pipVersion} --disable-pip-version-check --no-warn-script-location`);
96007+
}
96008+
});
96009+
}
96010+
function useCpythonVersion(version, architecture, updateEnvironment, checkLatest, allowPreReleases, freethreaded) {
96011+
return __awaiter(this, void 0, void 0, function* () {
96012+
var _a;
96013+
let manifest = null;
96014+
const { version: desugaredVersionSpec, freethreaded: versionFreethreaded } = desugarVersion(version);
96015+
let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec, allowPreReleases);
96016+
if (versionFreethreaded) {
96017+
// Use the freethreaded version if it was specified in the input, e.g., 3.13t
96018+
freethreaded = true;
96019+
>>>>>>> e9c40fb (Add support for `pip-version` (#1129))
9599396020
}
9599496021
else {
9599596022
core.info(`Failed to resolve version ${semanticVersionSpec} from manifest`);
@@ -96053,6 +96080,7 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
9605396080
const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
9605496081
core.addPath(userScriptsDir);
9605596082
}
96083+
<<<<<<< HEAD
9605696084
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
9605796085
}
9605896086
const installed = versionFromPath(installDir);
@@ -96064,6 +96092,70 @@ async function useCpythonVersion(version, architecture, updateEnvironment, check
9606496092
core.setOutput('python-version', pythonVersion);
9606596093
core.setOutput('python-path', pythonPath);
9606696094
return { impl: 'CPython', version: pythonVersion };
96095+
=======
96096+
if (!installDir) {
96097+
const osInfo = yield (0, utils_1.getOSInfo)();
96098+
const msg = [
96099+
`The version '${version}' with architecture '${architecture}' was not found for ${osInfo
96100+
? `${osInfo.osName} ${osInfo.osVersion}`
96101+
: 'this operating system'}.`
96102+
];
96103+
if (freethreaded) {
96104+
msg.push(`Free threaded versions are only available for Python 3.13.0 and later.`);
96105+
}
96106+
msg.push(`The list of all available versions can be found here: ${installer.MANIFEST_URL}`);
96107+
throw new Error(msg.join(os.EOL));
96108+
}
96109+
const _binDir = binDir(installDir);
96110+
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
96111+
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
96112+
if (updateEnvironment) {
96113+
core.exportVariable('pythonLocation', installDir);
96114+
core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
96115+
core.exportVariable('pythonLocation', installDir);
96116+
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
96117+
core.exportVariable('Python_ROOT_DIR', installDir);
96118+
// https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
96119+
core.exportVariable('Python2_ROOT_DIR', installDir);
96120+
// https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
96121+
core.exportVariable('Python3_ROOT_DIR', installDir);
96122+
core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
96123+
if (utils_1.IS_LINUX) {
96124+
const libPath = process.env.LD_LIBRARY_PATH
96125+
? `:${process.env.LD_LIBRARY_PATH}`
96126+
: '';
96127+
const pyLibPath = path.join(installDir, 'lib');
96128+
if (!libPath.split(':').includes(pyLibPath)) {
96129+
core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath);
96130+
}
96131+
}
96132+
core.addPath(installDir);
96133+
core.addPath(_binDir);
96134+
if (utils_1.IS_WINDOWS) {
96135+
// Add --user directory
96136+
// `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python/<semantic version>/x64/
96137+
// So if `findLocalTool` succeeded above, we must have a conformant `installDir`
96138+
const version = path.basename(path.dirname(installDir));
96139+
const major = semver.major(version);
96140+
const minor = semver.minor(version);
96141+
const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
96142+
core.addPath(userScriptsDir);
96143+
}
96144+
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
96145+
}
96146+
const installed = versionFromPath(installDir);
96147+
let pythonVersion = installed;
96148+
if (freethreaded) {
96149+
// Add the freethreaded suffix to the version (e.g., 3.13.1t)
96150+
pythonVersion += 't';
96151+
}
96152+
core.setOutput('python-version', pythonVersion);
96153+
core.setOutput('python-path', pythonPath);
96154+
const binaryPath = utils_1.IS_WINDOWS ? installDir : _binDir;
96155+
yield installPip(binaryPath);
96156+
return { impl: 'CPython', version: pythonVersion };
96157+
});
96158+
>>>>>>> e9c40fb (Add support for `pip-version` (#1129))
9606796159
}
9606896160
/* Desugar free threaded and dev versions */
9606996161
function desugarVersion(versionSpec) {

docs/advanced-usage.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [macOS](advanced-usage.md#macos)
2323
- [Using `setup-python` on GHES](advanced-usage.md#using-setup-python-on-ghes)
2424
- [Allow pre-releases](advanced-usage.md#allow-pre-releases)
25+
- [Using the pip-version input](advanced-usage.md#using-the-pip-version-input)
2526

2627
## Using the `python-version` input
2728

@@ -643,3 +644,22 @@ jobs:
643644
- run: pipx run nox --error-on-missing-interpreters -s tests-${{ matrix.python_version }}
644645
```
645646

647+
## Using the pip-version input
648+
649+
The `pip-version` input allows you to specify the desired version of **Pip** to use with the standard Python version.
650+
The version of Pip should be specified in the format `major`, `major.minor`, or `major.minor.patch` (for example: 25, 25.1, or 25.0.1).
651+
652+
```yaml
653+
steps:
654+
- uses: actions/checkout@v4
655+
- name: Set up Python
656+
uses: actions/setup-python@v5
657+
with:
658+
python-version: '3.13'
659+
pip-version: '25.0.1'
660+
- name: Display Pip version
661+
run: pip --version
662+
```
663+
> The `pip-version` input is supported only with standard Python versions. It is not available when using PyPy or GraalPy.
664+
665+
> 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/).

src/find-python.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as installer from './install-python';
88

99
import * as core from '@actions/core';
1010
import * as tc from '@actions/tool-cache';
11+
import * as exec from '@actions/exec';
1112

1213
// Python has "scripts" or "bin" directories where command-line tools that come with packages are installed.
1314
// This is where pip is, along with anything that pip installs.
@@ -30,6 +31,27 @@ function binDir(installDir: string): string {
3031
}
3132
}
3233

34+
async function installPip(pythonLocation: string) {
35+
const pipVersion = core.getInput('pip-version');
36+
37+
// Validate pip-version format: major[.minor][.patch]
38+
const versionRegex = /^\d+(\.\d+)?(\.\d+)?$/;
39+
if (pipVersion && !versionRegex.test(pipVersion)) {
40+
throw new Error(
41+
`Invalid pip-version "${pipVersion}". Please specify a version in the format major[.minor][.patch].`
42+
);
43+
}
44+
45+
if (pipVersion) {
46+
core.info(
47+
`pip-version input is specified. Installing pip version ${pipVersion}`
48+
);
49+
await exec.exec(
50+
`${pythonLocation}/python -m pip install --upgrade pip==${pipVersion} --disable-pip-version-check --no-warn-script-location`
51+
);
52+
}
53+
}
54+
3355
export async function useCpythonVersion(
3456
version: string,
3557
architecture: string,
@@ -179,6 +201,9 @@ export async function useCpythonVersion(
179201
core.setOutput('python-version', pythonVersion);
180202
core.setOutput('python-path', pythonPath);
181203

204+
const binaryPath = IS_WINDOWS ? installDir : _binDir;
205+
await installPip(binaryPath);
206+
182207
return {impl: 'CPython', version: pythonVersion};
183208
}
184209

0 commit comments

Comments
 (0)