feat: built-in cache support (#22)
This commit is contained in:
parent
1c39d42ed6
commit
5f2990033f
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -17,6 +17,7 @@ jobs:
|
||||
id: setup-pdm
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: true
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
|
38
README.md
38
README.md
@ -14,7 +14,7 @@ Include the action in your workflow yaml file with the following arguments:
|
||||
```yaml
|
||||
steps:
|
||||
...
|
||||
- uses: pdm-project/setup-pdm@main
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
name: Setup PDM
|
||||
with:
|
||||
python-version: 3.9 # Version range or exact version of a Python version to use, the same as actions/setup-python
|
||||
@ -44,3 +44,39 @@ outputs:
|
||||
pdm-bin:
|
||||
description: "The absolute path to the PDM executable."
|
||||
```
|
||||
|
||||
## Caches
|
||||
|
||||
This action has a built-in cache support. You can use it like this:
|
||||
|
||||
```yaml
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
cache: true
|
||||
```
|
||||
|
||||
The default path to calculate the cache key is `./pdm.lock`, you can change it by setting the `cache-dependency-path` input.
|
||||
|
||||
**Using a list of file paths to cache dependencies**
|
||||
|
||||
```yaml
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
cache: true
|
||||
cache-dependency-path: |
|
||||
./pdm.lock
|
||||
./pdm.new.lock
|
||||
```
|
||||
|
||||
**Using a glob pattern to cache dependencies**
|
||||
|
||||
```yaml
|
||||
```yaml
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
cache: true
|
||||
cache-dependency-path: '**/pdm.lock'
|
||||
```
|
||||
|
12
action.yml
12
action.yml
@ -25,6 +25,14 @@ inputs:
|
||||
description: "Enable PEP 582 package loading globally."
|
||||
default: "true"
|
||||
required: false
|
||||
cache:
|
||||
description: "Cache PDM installation."
|
||||
default: "false"
|
||||
required: false
|
||||
cache-dependency-path:
|
||||
description: "The dependency file(s) to cache."
|
||||
default: "pdm.lock"
|
||||
required: false
|
||||
outputs:
|
||||
python-version:
|
||||
description: "The installed Python or PyPy version. Useful when given a version range as input."
|
||||
@ -34,9 +42,13 @@ outputs:
|
||||
description: "The installed PDM version."
|
||||
pdm-bin:
|
||||
description: "The absolute path to the PDM executable."
|
||||
cache-hit:
|
||||
description: "Whether or not there was a cache hit."
|
||||
runs:
|
||||
using: "node16"
|
||||
main: "dist/setup-pdm.js"
|
||||
post: "dist/cache-save.js"
|
||||
post-if: success()
|
||||
branding:
|
||||
icon: "code"
|
||||
color: "green"
|
||||
|
64786
dist/cache-save.js
vendored
Normal file
64786
dist/cache-save.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1940
dist/setup-pdm.js
vendored
1940
dist/setup-pdm.js
vendored
File diff suppressed because it is too large
Load Diff
@ -4,15 +4,17 @@
|
||||
"description": "The GitHub Action for using pdm as the package manager",
|
||||
"main": "dist/setup-pdm.js",
|
||||
"scripts": {
|
||||
"build": "esbuild src/setup-pdm.ts --bundle --platform=node --outfile=dist/setup-pdm.js"
|
||||
"build": "esbuild src/setup-pdm.ts --bundle --platform=node --outfile=dist/setup-pdm.js && esbuild src/cache-save.ts --bundle --platform=node --outfile=dist/cache-save.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pdm-project/setup-pdm.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/cache": "^3.0.6",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/glob": "^0.3.0",
|
||||
"got": "^12.5.2",
|
||||
"semver": "^7.3.8",
|
||||
"setup-python": "actions/setup-python"
|
||||
|
52
src/cache-save.ts
Normal file
52
src/cache-save.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import fs from 'fs';
|
||||
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const cache = core.getInput('cache');
|
||||
if (cache) {
|
||||
await saveCache();
|
||||
}
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
core.setFailed(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCache() {
|
||||
const cachePaths = JSON.parse(core.getState('cache-paths')) as string[];
|
||||
|
||||
core.debug(`paths for caching are ${cachePaths.join(', ')}`);
|
||||
|
||||
if (cachePaths.every(path => !fs.existsSync(path))) {
|
||||
throw new Error(
|
||||
`Cache folder path is retrieved for pdm but doesn't exist on disk: ${cachePaths.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
const primaryKey = core.getState('cache-primary-key');
|
||||
const matchedKey = core.getState('cache-matched-key');
|
||||
|
||||
if (!primaryKey) {
|
||||
core.warning('Error retrieving key from state.');
|
||||
return;
|
||||
} else if (matchedKey === primaryKey) {
|
||||
// no change in target directories
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheId = await cache.saveCache(cachePaths, primaryKey);
|
||||
if (cacheId == -1) {
|
||||
return;
|
||||
}
|
||||
core.info(`Cache saved with the key: ${primaryKey}`);
|
||||
}
|
||||
|
||||
run();
|
58
src/caches.ts
Normal file
58
src/caches.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import path from 'path';
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import { getOutput } from './utils';
|
||||
import { hashFiles } from '@actions/glob';
|
||||
|
||||
async function calculateCacheKeys(pythonVersion: string, cacheDependencyPath: string): Promise<{ primaryKey: string; restoreKeys: string[]; }> {
|
||||
const hash = await hashFiles(cacheDependencyPath);
|
||||
const primaryKey = `setup-pdm-${process.env['RUNNER_OS']}-python-${pythonVersion}-${hash}`;
|
||||
const restoreKey = `setup-pdm-${process.env['RUNNER_OS']}-python-${pythonVersion}-`;
|
||||
return { primaryKey, restoreKeys: [restoreKey] };
|
||||
}
|
||||
|
||||
|
||||
async function cacheDependencies(pdmBin: string, pythonVersion: string) {
|
||||
const cacheDependencyPath = core.getInput('cache-dependency-path') || 'pdm.lock';
|
||||
const { primaryKey, restoreKeys } = await calculateCacheKeys(pythonVersion, cacheDependencyPath);
|
||||
if (primaryKey.endsWith('-')) {
|
||||
throw new Error(
|
||||
`No file in ${process.cwd()} matched to [${cacheDependencyPath
|
||||
.split('\n')
|
||||
.join(',')}], make sure you have checked out the target repository`
|
||||
);
|
||||
}
|
||||
|
||||
const cachePath = await getCacheDirectories(pdmBin);
|
||||
|
||||
core.saveState('cache-paths', cachePath);
|
||||
core.saveState('cache-primary-key', primaryKey);
|
||||
|
||||
const matchedKey = await cache.restoreCache(cachePath, primaryKey, restoreKeys);
|
||||
|
||||
handleMatchResult(matchedKey, primaryKey);
|
||||
}
|
||||
|
||||
|
||||
async function getCacheDirectories(pdmBin: string): Promise<string[]> {
|
||||
const paths = [
|
||||
path.join(process.cwd(), '.venv'),
|
||||
path.join(process.cwd(), '__pypackages__'),
|
||||
];
|
||||
paths.push(await getOutput(pdmBin, ["config", "cache_dir"]));
|
||||
paths.push(await getOutput(pdmBin, ["config", "venv.location"]));
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
||||
function handleMatchResult(matchedKey: string | undefined, primaryKey: string) {
|
||||
if (matchedKey) {
|
||||
core.saveState('cache-matched-key', matchedKey);
|
||||
core.info(`Cache restored from key: ${matchedKey}`);
|
||||
} else {
|
||||
core.info(`pdm cache is not found`);
|
||||
}
|
||||
core.setOutput('cache-hit', matchedKey === primaryKey);
|
||||
}
|
||||
|
||||
export { cacheDependencies };
|
@ -1,10 +1,11 @@
|
||||
import * as os from 'os'
|
||||
import path from 'path'
|
||||
import * as core from '@actions/core'
|
||||
import * as exec from '@actions/exec'
|
||||
import { exec } from '@actions/exec'
|
||||
import { IS_WINDOWS } from 'setup-python/src/utils'
|
||||
import semParse from 'semver/functions/parse'
|
||||
import * as utils from './utils'
|
||||
import { cacheDependencies } from './caches'
|
||||
|
||||
const INSTALL_SCRIPT_URL = 'https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py'
|
||||
interface InstallOutput {
|
||||
@ -28,7 +29,7 @@ async function run(): Promise<void> {
|
||||
const pdmVersion = core.getInput('version')
|
||||
const pythonVersion = core.getInput('python-version')
|
||||
const cmdArgs = ['-']
|
||||
if (core.getInput('prerelease') === 'true') {
|
||||
if (core.getBooleanInput('prerelease')) {
|
||||
cmdArgs.push('--prerelease')
|
||||
}
|
||||
if (pdmVersion) {
|
||||
@ -37,13 +38,13 @@ async function run(): Promise<void> {
|
||||
cmdArgs.push('-o', 'install-output.json')
|
||||
// Use the default python version installed with the runner
|
||||
try {
|
||||
await exec.exec('python', cmdArgs, { input: await utils.fetchUrlAsBuffer(INSTALL_SCRIPT_URL) })
|
||||
await exec('python', cmdArgs, { input: await utils.fetchUrlAsBuffer(INSTALL_SCRIPT_URL) })
|
||||
const installOutput: InstallOutput = JSON.parse(await utils.readFile('install-output.json'))
|
||||
core.debug(`Install output: ${installOutput}`)
|
||||
core.setOutput('pdm-version', installOutput.pdm_version)
|
||||
core.setOutput('pdm-bin', path.join(installOutput.install_location, installOutput.pdm_bin))
|
||||
core.addPath(path.dirname(installOutput.pdm_bin))
|
||||
if (core.getInput('enable-pep582') === 'true') {
|
||||
if (core.getBooleanInput('enable-pep582')) {
|
||||
core.exportVariable('PYTHONPATH', getPep582Path(installOutput.install_location, installOutput.install_python_version))
|
||||
}
|
||||
|
||||
@ -56,6 +57,9 @@ async function run(): Promise<void> {
|
||||
core.info(`Successfully setup ${installOutput.pdm_version} with Python ${installedPython}`)
|
||||
const matchersPath = path.join(__dirname, '..', '.github')
|
||||
core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`)
|
||||
if (utils.isCacheAvailable()) {
|
||||
await cacheDependencies(installOutput.pdm_bin, installedPython);
|
||||
}
|
||||
} catch (error: any) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
|
22
src/utils.ts
22
src/utils.ts
@ -1,8 +1,10 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import got from 'got';
|
||||
import { promises as fs } from 'fs';
|
||||
import { useCpythonVersion } from 'setup-python/src/find-python';
|
||||
import { findPyPyVersion } from 'setup-python/src/find-pypy';
|
||||
import { getExecOutput } from '@actions/exec';
|
||||
|
||||
|
||||
function isPyPyVersion(versionSpec: string): boolean {
|
||||
@ -49,3 +51,23 @@ export async function readFile(filePath: string): Promise<string> {
|
||||
return await fs.readFile(filePath, 'utf8');
|
||||
}
|
||||
|
||||
export async function getOutput(command: string, args: string[]): Promise<string> {
|
||||
const { stdout, exitCode, stderr } = await getExecOutput(command, args);
|
||||
if (exitCode && stderr) {
|
||||
throw new Error(`Could not run ${command} ${args.join(' ')}: ${stderr}`);
|
||||
}
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
|
||||
export function isCacheAvailable(): boolean {
|
||||
if (!core.getBooleanInput('cache')) {
|
||||
return false;
|
||||
}
|
||||
if (!cache.isFeatureAvailable()) {
|
||||
core.warning('Caching is not supported on this platform.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user