feat: built-in cache support (#22)

This commit is contained in:
Frost Ming 2022-10-21 12:06:56 +08:00 committed by GitHub
parent 1c39d42ed6
commit 5f2990033f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 66521 additions and 404 deletions

View File

@ -17,6 +17,7 @@ jobs:
id: setup-pdm
with:
python-version: ${{ matrix.python-version }}
cache: true
- name: Check output
run: |

View File

@ -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'
```

View File

@ -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

File diff suppressed because one or more lines are too long

1940
dist/setup-pdm.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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 };

View File

@ -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)
}

View File

@ -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;
}