Compare commits

...

6 commits

Author SHA1 Message Date
Aarni Koskela
096a587912
Merge 8f0a0b0eda into 19e4675e06 2025-03-16 21:59:44 +02:00
mahabaleshwars
19e4675e06
Add support for .tool-versions file in setup-python (#1043)
* add support for .tool-versions file

* update regex

* optimize code

* update test-python.yml for .tool-versions

* fix format-check errors

* fix formatting in test-python.yml

* Fix test-python.yml error

* workflow update with latest versions

* update test cases

* fix lint issue
2025-03-13 10:21:27 -05:00
dependabot[bot]
6fd11e170a
Bump @actions/glob from 0.4.0 to 0.5.0 (#1015)
* Bump @actions/glob from 0.4.0 to 0.5.0

Bumps [@actions/glob](https://github.com/actions/toolkit/tree/HEAD/packages/glob) from 0.4.0 to 0.5.0.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/glob/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/glob)

---
updated-dependencies:
- dependency-name: "@actions/glob"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix for check failures

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aparna Jyothi <aparnajyothi-y@github.com>
2025-03-12 12:00:26 -05:00
Aarni Koskela
8f0a0b0eda Add cache-save: false option 2023-12-12 15:26:56 +02:00
Aarni Koskela
7e8faf07b9 Tests: do not mock getInput with an incompatible implementation 2023-12-12 15:24:36 +02:00
Aarni Koskela
3cfd90a803 Fix subheadings in caching guide 2023-12-12 15:24:36 +02:00
12 changed files with 405 additions and 100 deletions

View file

@ -245,6 +245,39 @@ jobs:
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'
setup-versions-from-tool-versions-file:
name: Setup ${{ matrix.python }} ${{ matrix.os }} .tool-versions file
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-20.04,
ubuntu-22.04,
macos-13,
ubuntu-latest
]
python: [3.13.0, 3.14-dev, pypy3.11-7.3.18, graalpy-24.1.2]
exclude:
- os: windows-latest
python: graalpy-24.1.2
steps:
- name: Checkout
uses: actions/checkout@v4
- name: build-tool-versions-file ${{ matrix.python }}
run: |
echo "python ${{ matrix.python }}" > .tool-versions
- name: setup-python using .tool-versions ${{ matrix.python }}
id: setup-python-tool-versions
uses: ./
with:
python-version-file: .tool-versions
setup-pre-release-version-from-manifest:
name: Setup 3.14.0-alpha.1 ${{ matrix.os }}
runs-on: ${{ matrix.os }}

View file

@ -1,6 +1,6 @@
---
name: "@actions/glob"
version: 0.4.0
version: 0.5.0
type: npm
summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob

View file

@ -20,7 +20,6 @@ describe('run', () => {
let debugSpy: jest.SpyInstance;
let saveStateSpy: jest.SpyInstance;
let getStateSpy: jest.SpyInstance;
let getInputSpy: jest.SpyInstance;
let setFailedSpy: jest.SpyInstance;
// cache spy
@ -29,10 +28,17 @@ describe('run', () => {
// exec spy
let getExecOutputSpy: jest.SpyInstance;
let inputs = {} as any;
function setInput(name: string, value: string): void {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value;
}
beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
for (const key in process.env) {
if (key.startsWith('INPUT_')) {
delete process.env[key];
}
}
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(input => undefined);
@ -56,9 +62,6 @@ describe('run', () => {
setFailedSpy = jest.spyOn(core, 'setFailed');
getInputSpy = jest.spyOn(core, 'getInput');
getInputSpy.mockImplementation(input => inputs[input]);
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
getExecOutputSpy.mockImplementation((input: string) => {
if (input.includes('pip')) {
@ -74,10 +77,9 @@ describe('run', () => {
describe('Package manager validation', () => {
it('Package manager is not provided, skip caching', async () => {
inputs['cache'] = '';
setInput('cache', '');
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(infoSpy).not.toHaveBeenCalled();
expect(saveCacheSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
@ -86,12 +88,11 @@ describe('run', () => {
describe('Validate unchanged cache is not saved', () => {
it('should not save cache for pip', async () => {
inputs['cache'] = 'pip';
inputs['python-version'] = '3.10.0';
setInput('cache', 'pip');
setInput('python-version', '3.10.0');
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(debugSpy).toHaveBeenCalledWith(
`paths for caching are ${__dirname}`
);
@ -103,12 +104,11 @@ describe('run', () => {
});
it('should not save cache for pipenv', async () => {
inputs['cache'] = 'pipenv';
inputs['python-version'] = '3.10.0';
setInput('cache', 'pipenv');
setInput('python-version', '3.10.0');
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(debugSpy).toHaveBeenCalledWith(
`paths for caching are ${__dirname}`
);
@ -122,8 +122,8 @@ describe('run', () => {
describe('action saves the cache', () => {
it('saves cache from pip', async () => {
inputs['cache'] = 'pip';
inputs['python-version'] = '3.10.0';
setInput('cache', 'pip');
setInput('python-version', '3.10.0');
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return requirementsHash;
@ -136,7 +136,6 @@ describe('run', () => {
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${requirementsHash}, not saving cache.`
@ -149,8 +148,8 @@ describe('run', () => {
});
it('saves cache from pipenv', async () => {
inputs['cache'] = 'pipenv';
inputs['python-version'] = '3.10.0';
setInput('cache', 'pipenv');
setInput('python-version', '3.10.0');
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return pipFileLockHash;
@ -163,7 +162,6 @@ describe('run', () => {
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${pipFileLockHash}, not saving cache.`
@ -176,8 +174,8 @@ describe('run', () => {
});
it('saves cache from poetry', async () => {
inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0';
setInput('cache', 'poetry');
setInput('python-version', '3.10.0');
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return poetryLockHash;
@ -190,7 +188,6 @@ describe('run', () => {
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${poetryLockHash}, not saving cache.`
@ -203,8 +200,8 @@ describe('run', () => {
});
it('saves with -1 cacheId , should not fail workflow', async () => {
inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0';
setInput('cache', 'poetry');
setInput('python-version', '3.10.0');
getStateSpy.mockImplementation((name: string) => {
if (name === State.STATE_CACHE_PRIMARY_KEY) {
return poetryLockHash;
@ -221,7 +218,6 @@ describe('run', () => {
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalled();
expect(saveCacheSpy).toHaveBeenCalled();
@ -232,8 +228,8 @@ describe('run', () => {
});
it('saves with error from toolkit, should not fail the workflow', async () => {
inputs['cache'] = 'npm';
inputs['python-version'] = '3.10.0';
setInput('cache', 'npm');
setInput('python-version', '3.10.0');
getStateSpy.mockImplementation((name: string) => {
if (name === State.STATE_CACHE_PRIMARY_KEY) {
return poetryLockHash;
@ -250,17 +246,27 @@ describe('run', () => {
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).not.toHaveBeenCalledWith();
expect(saveCacheSpy).toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('should not save the cache when requested not to', async () => {
setInput('cache', 'pip');
setInput('cache-save', 'false');
setInput('python-version', '3.10.0');
await run();
expect(infoSpy).toHaveBeenCalledWith(
'Not saving cache since `cache-save` is false'
);
expect(saveCacheSpy).not.toHaveBeenCalled();
expect(setFailedSpy).not.toHaveBeenCalled();
});
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
inputs = {};
});
});

View file

@ -15,7 +15,8 @@ import {
getNextPageUrl,
isGhes,
IS_WINDOWS,
getDownloadFileName
getDownloadFileName,
getVersionInputFromToolVersions
} from '../src/utils';
jest.mock('@actions/cache');
@ -139,6 +140,82 @@ describe('Version from file test', () => {
expect(_fn(pythonVersionFilePath)).toEqual([]);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python 3.9.10\nnodejs 16';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with comment',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = '# python 3.8\npython 3.9';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with whitespace',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = ' python 3.10 ';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.10']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with v prefix',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python v3.9.10';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.9.10']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with pypy version',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python pypy3.10-7.3.14';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['pypy3.10-7.3.14']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with alpha Releases',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python 3.14.0a5t';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.14.0a5t']);
}
);
it.each([getVersionInputFromToolVersions])(
'Version from .tool-versions with dev suffix',
async _fn => {
const toolVersionFileName = '.tool-versions';
const toolVersionFilePath = path.join(tempDir, toolVersionFileName);
const toolVersionContent = 'python 3.14t-dev';
fs.writeFileSync(toolVersionFilePath, toolVersionContent);
expect(_fn(toolVersionFilePath)).toEqual(['3.14t-dev']);
}
);
});
describe('getNextPageUrl', () => {

View file

@ -20,6 +20,9 @@ inputs:
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
cache-dependency-path:
description: "Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies."
cache-save:
description: "Set this option if you want the action to save the cache after the run. Defaults to true. It can be useful to set this to false if you have e.g. optional dependencies that only some workflows require, and they should not be cached."
default: true
update-environment:
description: "Set this option if you want the action to update environment variables."
default: true

View file

@ -89566,11 +89566,25 @@ function run(earlyExit) {
try {
const cache = core.getInput('cache');
if (cache) {
let shouldSave = true;
try {
shouldSave = core.getBooleanInput('cache-save', { required: false });
}
catch (e) {
// If we fail to parse the input, assume it's
// > "Input does not meet YAML 1.2 "core schema" specification."
// and assume it's the `true` default.
}
if (shouldSave) {
yield saveCache(cache);
if (earlyExit) {
process.exit(0);
}
}
else {
core.info('Not saving cache since `cache-save` is false');
}
}
}
catch (error) {
const err = error;

171
dist/setup/index.js vendored
View file

@ -8752,7 +8752,7 @@ function hashFiles(patterns, currentWorkspace = '', options, verbose = false) {
followSymbolicLinks = options.followSymbolicLinks;
}
const globber = yield create(patterns, { followSymbolicLinks });
return internal_hash_files_1.hashFiles(globber, currentWorkspace, verbose);
return (0, internal_hash_files_1.hashFiles)(globber, currentWorkspace, verbose);
});
}
exports.hashFiles = hashFiles;
@ -8767,7 +8767,11 @@ exports.hashFiles = hashFiles;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -8780,7 +8784,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -8795,7 +8799,8 @@ function getOptions(copy) {
followSymbolicLinks: true,
implicitDescendants: true,
matchDirectories: true,
omitBrokenSymbolicLinks: true
omitBrokenSymbolicLinks: true,
excludeHiddenFiles: false
};
if (copy) {
if (typeof copy.followSymbolicLinks === 'boolean') {
@ -8814,6 +8819,10 @@ function getOptions(copy) {
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks;
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`);
}
if (typeof copy.excludeHiddenFiles === 'boolean') {
result.excludeHiddenFiles = copy.excludeHiddenFiles;
core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`);
}
}
return result;
}
@ -8829,7 +8838,11 @@ exports.getOptions = getOptions;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -8842,7 +8855,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -8896,19 +8909,21 @@ class DefaultGlobber {
return this.searchPaths.slice();
}
glob() {
var e_1, _a;
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const result = [];
try {
for (var _b = __asyncValues(this.globGenerator()), _c; _c = yield _b.next(), !_c.done;) {
const itemPath = _c.value;
for (var _d = true, _e = __asyncValues(this.globGenerator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const itemPath = _c;
result.push(itemPath);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
@ -8966,6 +8981,10 @@ class DefaultGlobber {
if (!stats) {
continue;
}
// Hidden file or directory?
if (options.excludeHiddenFiles && path.basename(item.path).match(/^\./)) {
continue;
}
// Directory
if (stats.isDirectory()) {
// Matched
@ -9071,7 +9090,11 @@ exports.DefaultGlobber = DefaultGlobber;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -9084,7 +9107,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -9113,19 +9136,21 @@ const stream = __importStar(__nccwpck_require__(2203));
const util = __importStar(__nccwpck_require__(9023));
const path = __importStar(__nccwpck_require__(6928));
function hashFiles(globber, currentWorkspace, verbose = false) {
var e_1, _a;
var _b;
var _a, e_1, _b, _c;
var _d;
return __awaiter(this, void 0, void 0, function* () {
const writeDelegate = verbose ? core.info : core.debug;
let hasMatch = false;
const githubWorkspace = currentWorkspace
? currentWorkspace
: (_b = process.env['GITHUB_WORKSPACE']) !== null && _b !== void 0 ? _b : process.cwd();
: (_d = process.env['GITHUB_WORKSPACE']) !== null && _d !== void 0 ? _d : process.cwd();
const result = crypto.createHash('sha256');
let count = 0;
try {
for (var _c = __asyncValues(globber.globGenerator()), _d; _d = yield _c.next(), !_d.done;) {
const file = _d.value;
for (var _e = true, _f = __asyncValues(globber.globGenerator()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
_c = _g.value;
_e = false;
const file = _c;
writeDelegate(file);
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
writeDelegate(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`);
@ -9148,7 +9173,7 @@ function hashFiles(globber, currentWorkspace, verbose = false) {
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c.return)) yield _a.call(_c);
if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
}
finally { if (e_1) throw e_1.error; }
}
@ -9188,7 +9213,7 @@ var MatchKind;
MatchKind[MatchKind["File"] = 2] = "File";
/** Matched */
MatchKind[MatchKind["All"] = 3] = "All";
})(MatchKind = exports.MatchKind || (exports.MatchKind = {}));
})(MatchKind || (exports.MatchKind = MatchKind = {}));
//# sourceMappingURL=internal-match-kind.js.map
/***/ }),
@ -9200,7 +9225,11 @@ var MatchKind;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -9213,7 +9242,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -9263,8 +9292,8 @@ exports.dirname = dirname;
* or `C:` are expanded based on the current working directory.
*/
function ensureAbsoluteRoot(root, itemPath) {
assert_1.default(root, `ensureAbsoluteRoot parameter 'root' must not be empty`);
assert_1.default(itemPath, `ensureAbsoluteRoot parameter 'itemPath' must not be empty`);
(0, assert_1.default)(root, `ensureAbsoluteRoot parameter 'root' must not be empty`);
(0, assert_1.default)(itemPath, `ensureAbsoluteRoot parameter 'itemPath' must not be empty`);
// Already rooted
if (hasAbsoluteRoot(itemPath)) {
return itemPath;
@ -9274,7 +9303,7 @@ function ensureAbsoluteRoot(root, itemPath) {
// Check for itemPath like C: or C:foo
if (itemPath.match(/^[A-Z]:[^\\/]|^[A-Z]:$/i)) {
let cwd = process.cwd();
assert_1.default(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
(0, assert_1.default)(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
// Drive letter matches cwd? Expand to cwd
if (itemPath[0].toUpperCase() === cwd[0].toUpperCase()) {
// Drive only, e.g. C:
@ -9299,11 +9328,11 @@ function ensureAbsoluteRoot(root, itemPath) {
// Check for itemPath like \ or \foo
else if (normalizeSeparators(itemPath).match(/^\\$|^\\[^\\]/)) {
const cwd = process.cwd();
assert_1.default(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
(0, assert_1.default)(cwd.match(/^[A-Z]:\\/i), `Expected current directory to start with an absolute drive root. Actual '${cwd}'`);
return `${cwd[0]}:\\${itemPath.substr(1)}`;
}
}
assert_1.default(hasAbsoluteRoot(root), `ensureAbsoluteRoot parameter 'root' must have an absolute root`);
(0, assert_1.default)(hasAbsoluteRoot(root), `ensureAbsoluteRoot parameter 'root' must have an absolute root`);
// Otherwise ensure root ends with a separator
if (root.endsWith('/') || (IS_WINDOWS && root.endsWith('\\'))) {
// Intentionally empty
@ -9320,7 +9349,7 @@ exports.ensureAbsoluteRoot = ensureAbsoluteRoot;
* `\\hello\share` and `C:\hello` (and using alternate separator).
*/
function hasAbsoluteRoot(itemPath) {
assert_1.default(itemPath, `hasAbsoluteRoot parameter 'itemPath' must not be empty`);
(0, assert_1.default)(itemPath, `hasAbsoluteRoot parameter 'itemPath' must not be empty`);
// Normalize separators
itemPath = normalizeSeparators(itemPath);
// Windows
@ -9337,7 +9366,7 @@ exports.hasAbsoluteRoot = hasAbsoluteRoot;
* `\`, `\hello`, `\\hello\share`, `C:`, and `C:\hello` (and using alternate separator).
*/
function hasRoot(itemPath) {
assert_1.default(itemPath, `isRooted parameter 'itemPath' must not be empty`);
(0, assert_1.default)(itemPath, `isRooted parameter 'itemPath' must not be empty`);
// Normalize separators
itemPath = normalizeSeparators(itemPath);
// Windows
@ -9405,7 +9434,11 @@ exports.safeTrimTrailingSeparator = safeTrimTrailingSeparator;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -9418,7 +9451,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -9443,7 +9476,7 @@ class Path {
this.segments = [];
// String
if (typeof itemPath === 'string') {
assert_1.default(itemPath, `Parameter 'itemPath' must not be empty`);
(0, assert_1.default)(itemPath, `Parameter 'itemPath' must not be empty`);
// Normalize slashes and trim unnecessary trailing slash
itemPath = pathHelper.safeTrimTrailingSeparator(itemPath);
// Not rooted
@ -9470,24 +9503,24 @@ class Path {
// Array
else {
// Must not be empty
assert_1.default(itemPath.length > 0, `Parameter 'itemPath' must not be an empty array`);
(0, assert_1.default)(itemPath.length > 0, `Parameter 'itemPath' must not be an empty array`);
// Each segment
for (let i = 0; i < itemPath.length; i++) {
let segment = itemPath[i];
// Must not be empty
assert_1.default(segment, `Parameter 'itemPath' must not contain any empty segments`);
(0, assert_1.default)(segment, `Parameter 'itemPath' must not contain any empty segments`);
// Normalize slashes
segment = pathHelper.normalizeSeparators(itemPath[i]);
// Root segment
if (i === 0 && pathHelper.hasRoot(segment)) {
segment = pathHelper.safeTrimTrailingSeparator(segment);
assert_1.default(segment === pathHelper.dirname(segment), `Parameter 'itemPath' root segment contains information for multiple segments`);
(0, assert_1.default)(segment === pathHelper.dirname(segment), `Parameter 'itemPath' root segment contains information for multiple segments`);
this.segments.push(segment);
}
// All other segments
else {
// Must not contain slash
assert_1.default(!segment.includes(path.sep), `Parameter 'itemPath' contains unexpected path separators`);
(0, assert_1.default)(!segment.includes(path.sep), `Parameter 'itemPath' contains unexpected path separators`);
this.segments.push(segment);
}
}
@ -9525,7 +9558,11 @@ exports.Path = Path;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -9538,7 +9575,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -9626,7 +9663,11 @@ exports.partialMatch = partialMatch;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@ -9639,7 +9680,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
@ -9671,9 +9712,9 @@ class Pattern {
else {
// Convert to pattern
segments = segments || [];
assert_1.default(segments.length, `Parameter 'segments' must not empty`);
(0, assert_1.default)(segments.length, `Parameter 'segments' must not empty`);
const root = Pattern.getLiteral(segments[0]);
assert_1.default(root && pathHelper.hasAbsoluteRoot(root), `Parameter 'segments' first element must be a root path`);
(0, assert_1.default)(root && pathHelper.hasAbsoluteRoot(root), `Parameter 'segments' first element must be a root path`);
pattern = new internal_path_1.Path(segments).toString().trim();
if (patternOrNegate) {
pattern = `!${pattern}`;
@ -9767,13 +9808,13 @@ class Pattern {
*/
static fixupPattern(pattern, homedir) {
// Empty
assert_1.default(pattern, 'pattern cannot be empty');
(0, assert_1.default)(pattern, 'pattern cannot be empty');
// Must not contain `.` segment, unless first segment
// Must not contain `..` segment
const literalSegments = new internal_path_1.Path(pattern).segments.map(x => Pattern.getLiteral(x));
assert_1.default(literalSegments.every((x, i) => (x !== '.' || i === 0) && x !== '..'), `Invalid pattern '${pattern}'. Relative pathing '.' and '..' is not allowed.`);
(0, assert_1.default)(literalSegments.every((x, i) => (x !== '.' || i === 0) && x !== '..'), `Invalid pattern '${pattern}'. Relative pathing '.' and '..' is not allowed.`);
// Must not contain globs in root, e.g. Windows UNC path \\foo\b*r
assert_1.default(!pathHelper.hasRoot(pattern) || literalSegments[0], `Invalid pattern '${pattern}'. Root segment must not contain globs.`);
(0, assert_1.default)(!pathHelper.hasRoot(pattern) || literalSegments[0], `Invalid pattern '${pattern}'. Root segment must not contain globs.`);
// Normalize slashes
pattern = pathHelper.normalizeSeparators(pattern);
// Replace leading `.` segment
@ -9783,8 +9824,8 @@ class Pattern {
// Replace leading `~` segment
else if (pattern === '~' || pattern.startsWith(`~${path.sep}`)) {
homedir = homedir || os.homedir();
assert_1.default(homedir, 'Unable to determine HOME directory');
assert_1.default(pathHelper.hasAbsoluteRoot(homedir), `Expected HOME directory to be a rooted path. Actual '${homedir}'`);
(0, assert_1.default)(homedir, 'Unable to determine HOME directory');
(0, assert_1.default)(pathHelper.hasAbsoluteRoot(homedir), `Expected HOME directory to be a rooted path. Actual '${homedir}'`);
pattern = Pattern.globEscape(homedir) + pattern.substr(1);
}
// Replace relative drive root, e.g. pattern is C: or C:foo
@ -100494,7 +100535,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = 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_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
exports.getDownloadFileName = exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromToolVersions = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = 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_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
/* eslint no-unsafe-finally: "off" */
const cache = __importStar(__nccwpck_require__(5116));
const core = __importStar(__nccwpck_require__(7484));
@ -100718,12 +100759,46 @@ function getVersionInputFromPlainFile(versionFile) {
}
exports.getVersionInputFromPlainFile = getVersionInputFromPlainFile;
/**
* Python version extracted from a plain or TOML file.
* Python version extracted from a .tool-versions file.
*/
function getVersionInputFromToolVersions(versionFile) {
var _a;
if (!fs_1.default.existsSync(versionFile)) {
core.warning(`File ${versionFile} does not exist.`);
return [];
}
try {
const fileContents = fs_1.default.readFileSync(versionFile, 'utf8');
const lines = fileContents.split('\n');
for (const line of lines) {
// Skip commented lines
if (line.trim().startsWith('#')) {
continue;
}
const match = line.match(/^\s*python\s*v?\s*(?<version>[^\s]+)\s*$/);
if (match) {
return [((_a = match.groups) === null || _a === void 0 ? void 0 : _a.version.trim()) || ''];
}
}
core.warning(`No Python version found in ${versionFile}`);
return [];
}
catch (error) {
core.error(`Error reading ${versionFile}: ${error.message}`);
return [];
}
}
exports.getVersionInputFromToolVersions = getVersionInputFromToolVersions;
/**
* Python version extracted from a plain, .tool-versions or TOML file.
*/
function getVersionInputFromFile(versionFile) {
if (versionFile.endsWith('.toml')) {
return getVersionInputFromTomlFile(versionFile);
}
else if (versionFile.match('.tool-versions')) {
return getVersionInputFromToolVersions(versionFile);
}
else {
return getVersionInputFromPlainFile(versionFile);
}

View file

@ -278,9 +278,9 @@ jobs:
## Using the `python-version-file` input
`setup-python` action can read the Python or PyPy version from a version file. `python-version-file` input is used to specify the path to the version file. If the file that was supplied to `python-version-file` input doesn't exist, the action will fail with an error.
`setup-python` action can read Python or PyPy version from a version file. `python-version-file` input is used for specifying the path to the version file. If the file that was supplied to `python-version-file` input doesn't exist, the action will fail with error.
>In case both `python-version` and `python-version-file` inputs are supplied, the `python-version-file` input will be ignored due to its lower priority.
>In case both `python-version` and `python-version-file` inputs are supplied, the `python-version-file` input will be ignored due to its lower priority. The .tool-versions file supports version specifications in accordance with asdf standards, adhering to Semantic Versioning ([semver](https://semver.org)).
```yaml
steps:
@ -300,6 +300,15 @@ steps:
- run: python my_script.py
```
```yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version-file: '.tool-versions' # Read python version from a file .tool-versions
- run: python my_script.py
```
## 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 or PyPy` version is always used.
@ -320,7 +329,8 @@ steps:
## Caching packages
**Caching pipenv dependencies:**
### Caching pipenv dependencies
```yaml
steps:
- uses: actions/checkout@v4
@ -333,7 +343,8 @@ steps:
- run: pipenv install
```
**Caching poetry dependencies:**
### Caching poetry dependencies
```yaml
steps:
- uses: actions/checkout@v4
@ -348,7 +359,8 @@ steps:
```
>**Note:** If the `setup-python` version does not match the version specified in `pyproject.toml` and the python version in `pyproject.toml` is less than the runner's python version, `poetry install` will default to using the runner's Python version.
**Using a list of file paths to cache dependencies**
### Using a list of file paths to cache dependencies
```yaml
steps:
- uses: actions/checkout@v4
@ -363,7 +375,9 @@ steps:
run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
- run: pipenv install
```
**Using wildcard patterns to cache dependencies**
### Using wildcard patterns to cache dependencies
```yaml
steps:
- uses: actions/checkout@v4
@ -375,7 +389,8 @@ steps:
- run: pip install -r subdirectory/requirements-dev.txt
```
**Using a list of wildcard patterns to cache dependencies**
### Using a list of wildcard patterns to cache dependencies
```yaml
steps:
- uses: actions/checkout@v4
@ -389,7 +404,7 @@ steps:
- run: pip install -e . -r subdirectory/requirements-dev.txt
```
**Caching projects that use setup.py:**
### Caching projects that use setup.py (or pyproject.toml)
```yaml
steps:
@ -403,6 +418,42 @@ steps:
# Or pip install -e '.[test]' to install test dependencies
```
### Skipping cache saving
For some scenarios, it may be useful to only save a given subset of dependencies,
but restore more of them for other workflows. For instance, there may be a heavy
`extras` dependency that you do not need your entire test matrix to download, but
you want to download and test it separately without it being saved in the cache
archive for all runs.
To achieve this, you can use `cache-save: false` on the run that uses the heavy
dependency.
```yaml
test:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: pyproject.toml
- run: pip install -e .
test-heavy-extra:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: pyproject.toml
cache-save: false
- run: pip install -e '.[heavy-extra]'
```
# Outputs and environment variables
## Outputs

9
package-lock.json generated
View file

@ -12,7 +12,7 @@
"@actions/cache": "^4.0.0",
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.0",
"@actions/glob": "^0.4.0",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^2.2.3",
"@actions/io": "^1.0.2",
"@actions/tool-cache": "^2.0.1",
@ -100,9 +100,10 @@
}
},
"node_modules/@actions/glob": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.4.0.tgz",
"integrity": "sha512-+eKIGFhsFa4EBwaf/GMyzCdWrXWymGXfFmZU3FHQvYS8mPcHtTtZONbkcqqUMzw9mJ/pImEBFET1JNifhqGsAQ==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.0.tgz",
"integrity": "sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"minimatch": "^3.0.4"

View file

@ -28,7 +28,7 @@
"@actions/cache": "^4.0.0",
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.0",
"@actions/glob": "^0.4.0",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^2.2.3",
"@actions/io": "^1.0.2",
"@actions/tool-cache": "^2.0.1",

View file

@ -11,11 +11,22 @@ export async function run(earlyExit?: boolean) {
try {
const cache = core.getInput('cache');
if (cache) {
let shouldSave = true;
try {
shouldSave = core.getBooleanInput('cache-save', {required: false});
} catch (e) {
// If we fail to parse the input, assume it's
// > "Input does not meet YAML 1.2 "core schema" specification."
// and assume it's the `true` default.
}
if (shouldSave) {
await saveCache(cache);
if (earlyExit) {
process.exit(0);
}
} else {
core.info('Not saving cache since `cache-save` is false');
}
}
} catch (error) {
const err = error as Error;

View file

@ -279,11 +279,45 @@ export function getVersionInputFromPlainFile(versionFile: string): string[] {
}
/**
* Python version extracted from a plain or TOML file.
* Python version extracted from a .tool-versions file.
*/
export function getVersionInputFromToolVersions(versionFile: string): string[] {
if (!fs.existsSync(versionFile)) {
core.warning(`File ${versionFile} does not exist.`);
return [];
}
try {
const fileContents = fs.readFileSync(versionFile, 'utf8');
const lines = fileContents.split('\n');
for (const line of lines) {
// Skip commented lines
if (line.trim().startsWith('#')) {
continue;
}
const match = line.match(/^\s*python\s*v?\s*(?<version>[^\s]+)\s*$/);
if (match) {
return [match.groups?.version.trim() || ''];
}
}
core.warning(`No Python version found in ${versionFile}`);
return [];
} catch (error) {
core.error(`Error reading ${versionFile}: ${(error as Error).message}`);
return [];
}
}
/**
* Python version extracted from a plain, .tool-versions or TOML file.
*/
export function getVersionInputFromFile(versionFile: string): string[] {
if (versionFile.endsWith('.toml')) {
return getVersionInputFromTomlFile(versionFile);
} else if (versionFile.match('.tool-versions')) {
return getVersionInputFromToolVersions(versionFile);
} else {
return getVersionInputFromPlainFile(versionFile);
}