feat: support python-version-file input

* feat: support python-version-file input

Signed-off-by: Frost Ming <me@frostming.com>

* fix: build esm

Signed-off-by: Frost Ming <me@frostming.com>

* fix: commonjs

Signed-off-by: Frost Ming <me@frostming.com>
This commit is contained in:
Frost Ming 2024-01-25 15:07:31 +08:00 committed by GitHub
parent 4b6ba52fde
commit 4882716d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 2751 additions and 286 deletions

View File

@ -10,8 +10,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: ["windows-latest", "ubuntu-latest", "macos-latest"]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
os: [windows-latest, ubuntu-latest, macos-latest]
name: Test the action
steps:
- uses: actions/checkout@v3

View File

@ -13,12 +13,12 @@ Include the action in your workflow yaml:
```yaml
steps:
- uses: actions/checkout@v3
- name: Setup PDM
uses: pdm-project/setup-pdm@v3
- uses: actions/checkout@v3
- name: Setup PDM
uses: pdm-project/setup-pdm@v3
# You are now able to use PDM in your workflow
- name: Install dependencies
run: pdm install
- name: Install dependencies
run: pdm install
```
You don't need `actions/setup-python` actually.

View File

@ -1,18 +1,20 @@
---
name: "Setup PDM"
description: "Set up a specific version of PDM and uses a given Python version to work on"
author: "Frost Ming"
name: Setup PDM
description: Set up a specific version of PDM and uses a given Python version to work on
author: Frost Ming
inputs:
python-version:
description: "Version range or exact version of a Python version to use, using SemVer's version range syntax."
default: "3.x"
description: 'Version range or exact version of a Python version to use, using SemVer''s version range syntax.'
default: 3.x
required: false
python-version-file:
description: 'File containing the Python version to use. Example: .python-version'
architecture:
description: "The target architecture (x86, x64) of the Python interpreter."
description: 'The target architecture (x86, x64) of the Python interpreter.'
required: false
allow-python-prereleases:
description: "Allow prerelease versions of Python to be installed."
default: "false"
description: Allow prerelease versions of Python to be installed.
default: 'false'
required: false
token:
description: Used to pull python distributions from actions/python-versions. Since there's a default, this is typically not supplied by the user.
@ -23,39 +25,39 @@ inputs:
required: false
prerelease:
description: Allow prerelease versions to be installed
default: "false"
default: 'false'
required: false
enable-pep582:
description: "Enable PEP 582 package loading globally."
default: "false"
description: Enable PEP 582 package loading globally.
default: 'false'
required: false
cache:
description: "Cache PDM installation."
default: "false"
description: Cache PDM installation.
default: 'false'
required: false
cache-dependency-path:
description: "The dependency file(s) to cache."
default: "pdm.lock"
description: The dependency file(s) to cache.
default: pdm.lock
required: false
update-python:
description: "Whether to update the environment with the requested Python"
default: "true"
description: Whether to update the environment with the requested Python
default: 'true'
outputs:
python-version:
description: "The installed Python or PyPy version. Useful when given a version range as input."
description: The installed Python or PyPy version. Useful when given a version range as input.
python-path:
description: "The absolute path to the Python or PyPy executable."
description: The absolute path to the Python or PyPy executable.
pdm-version:
description: "The installed PDM version."
description: The installed PDM version.
pdm-bin:
description: "The absolute path to the PDM executable."
description: The absolute path to the PDM executable.
cache-hit:
description: "Whether or not there was a cache hit."
description: Whether or not there was a cache hit.
runs:
using: "node20"
main: "dist/setup-pdm.js"
post: "dist/cache-save.js"
using: node20
main: dist/setup-pdm.js
post: dist/cache-save.js
post-if: success()
branding:
icon: "code"
color: "green"
icon: code
color: green

9
build.ts Normal file
View File

@ -0,0 +1,9 @@
import esbuild from 'esbuild'
esbuild.build({
platform: 'node',
target: 'node20',
bundle: true,
entryPoints: ['src/setup-pdm.ts', 'src/cache-save.ts'],
outdir: 'dist',
})

11
dist/cache-save.js vendored
View File

@ -74831,15 +74831,14 @@ var require_cache2 = __commonJS({
});
// src/cache-save.ts
var import_node_fs = __toESM(require("node:fs"));
var core = __toESM(require_core());
var cache = __toESM(require_cache2());
var import_fs = __toESM(require("fs"));
async function run() {
try {
const cache2 = core.getBooleanInput("cache");
if (cache2) {
if (cache2)
await saveCache2();
}
} catch (error) {
const err = error;
core.setFailed(err.message);
@ -74848,9 +74847,8 @@ async function run() {
async function saveCache2() {
const cachePaths = JSON.parse(core.getState("cache-paths"));
core.debug(`paths for caching are ${cachePaths.join(", ")}`);
if (cachePaths.every((path) => !import_fs.default.existsSync(path))) {
if (cachePaths.every((path) => !import_node_fs.default.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) {
@ -74861,9 +74859,8 @@ async function saveCache2() {
return;
}
const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId == -1) {
if (cacheId === -1)
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
}
run();

192
dist/setup-pdm.js vendored
View File

@ -2066,7 +2066,7 @@ var require_core = __commonJS({
process.env["PATH"] = `${inputPath}${path8.delimiter}${process.env["PATH"]}`;
}
exports2.addPath = addPath4;
function getInput4(name, options) {
function getInput5(name, options) {
const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
@ -2076,19 +2076,19 @@ var require_core = __commonJS({
}
return val.trim();
}
exports2.getInput = getInput4;
function getMultilineInput(name, options) {
const inputs = getInput4(name, options).split("\n").filter((x) => x !== "");
exports2.getInput = getInput5;
function getMultilineInput2(name, options) {
const inputs = getInput5(name, options).split("\n").filter((x) => x !== "");
if (options && options.trimWhitespace === false) {
return inputs;
}
return inputs.map((input) => input.trim());
}
exports2.getMultilineInput = getMultilineInput;
exports2.getMultilineInput = getMultilineInput2;
function getBooleanInput3(name, options) {
const trueValue = ["true", "True", "TRUE"];
const falseValue = ["false", "False", "FALSE"];
const val = getInput4(name, options);
const val = getInput5(name, options);
if (trueValue.includes(val))
return true;
if (falseValue.includes(val))
@ -10762,7 +10762,7 @@ var require_dataURL = __commonJS({
}
return bytes;
}
function collectAnHTTPQuotedString(input, position, extractValue) {
function collectAnHTTPQuotedString(input, position, extractValue2) {
const positionStart = position.position;
let value = "";
assert2(input[position.position] === '"');
@ -10790,7 +10790,7 @@ var require_dataURL = __commonJS({
break;
}
}
if (extractValue) {
if (extractValue2) {
return value;
}
return input.slice(positionStart, position.position);
@ -29898,10 +29898,10 @@ var require_parser = __commonJS({
return typeof thing === "object" && thing != null && Object.keys(thing).length === 0;
};
processItem = function(processors2, item, key) {
var i, len, process4;
var i, len, process6;
for (i = 0, len = processors2.length; i < len; i++) {
process4 = processors2[i];
item = process4(item, key);
process6 = processors2[i];
item = process6(item, key);
}
return item;
};
@ -84120,11 +84120,12 @@ var require_glob2 = __commonJS({
});
// src/setup-pdm.ts
var os4 = __toESM(require("os"));
var import_path2 = __toESM(require("path"));
var os4 = __toESM(require("node:os"));
var import_node_path2 = __toESM(require("node:path"));
var import_node_fs2 = require("node:fs");
var import_node_process4 = __toESM(require("node:process"));
var core8 = __toESM(require_core());
var import_exec2 = __toESM(require_exec());
var import_fs4 = require("fs");
// node_modules/.pnpm/github.com+actions+setup-python@2f078955e4d0f34cc7a8b0108b2eb7bbe154438e/node_modules/setup-python/src/utils.ts
var cache = __toESM(require_cache2());
@ -84176,6 +84177,10 @@ function validatePythonVersionFormatForPyPy(version2) {
const re = /^\d+\.\d+$/;
return re.test(version2);
}
function logWarning(message) {
const warningPrefix = "[warning]";
core.info(`${warningPrefix}${message}`);
}
async function getWindowsInfo() {
const { stdout } = await exec.getExecOutput(
'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',
@ -84219,6 +84224,61 @@ async function getOSInfo() {
return osInfo;
}
}
function extractValue(obj, keys) {
if (keys.length > 0) {
const value = obj[keys[0]];
if (keys.length > 1 && value !== void 0) {
return extractValue(value, keys.slice(1));
} else {
return value;
}
} else {
return;
}
}
function getVersionInputFromTomlFile(versionFile) {
core.debug(`Trying to resolve version form ${versionFile}`);
const pyprojectFile = import_fs.default.readFileSync(versionFile, "utf8");
const pyprojectConfig = toml.parse(pyprojectFile);
let keys = [];
if ("project" in pyprojectConfig) {
keys = ["project", "requires-python"];
} else {
keys = ["tool", "poetry", "dependencies", "python"];
}
const versions = [];
const version2 = extractValue(pyprojectConfig, keys);
if (version2 !== void 0) {
versions.push(version2);
}
core.info(`Extracted ${versions} from ${versionFile}`);
const rawVersions = Array.from(
versions,
(version3) => version3.split(",").join(" ")
);
const validatedVersions = rawVersions.map((item) => semver.validRange(item, true)).filter((versionRange, index) => {
if (!versionRange) {
core.debug(
`The version ${rawVersions[index]} is not valid SemVer range`
);
}
return !!versionRange;
});
return validatedVersions;
}
function getVersionInputFromPlainFile(versionFile) {
core.debug(`Trying to resolve version form ${versionFile}`);
const version2 = import_fs.default.readFileSync(versionFile, "utf8").trim();
core.info(`Resolved ${versionFile} as ${version2}`);
return [version2];
}
function getVersionInputFromFile(versionFile) {
if (versionFile.endsWith(".toml")) {
return getVersionInputFromTomlFile(versionFile);
} else {
return getVersionInputFromPlainFile(versionFile);
}
}
function getBinaryDirectory(installDir) {
return IS_WINDOWS ? installDir : path.join(installDir, "bin");
}
@ -84227,6 +84287,8 @@ function getBinaryDirectory(installDir) {
var import_parse3 = __toESM(require_parse2());
// src/utils.ts
var import_node_fs = __toESM(require("node:fs"));
var import_node_buffer3 = require("node:buffer");
var core6 = __toESM(require_core());
var cache2 = __toESM(require_cache2());
@ -90329,9 +90391,6 @@ var defaults = {
var got = create_default(defaults);
var source_default2 = got;
// src/utils.ts
var import_fs3 = require("fs");
// node_modules/.pnpm/github.com+actions+setup-python@2f078955e4d0f34cc7a8b0108b2eb7bbe154438e/node_modules/setup-python/src/find-python.ts
var os2 = __toESM(require("os"));
var path3 = __toESM(require("path"));
@ -90892,10 +90951,9 @@ function isPyPyVersion(versionSpec) {
}
async function fetchUrlAsBuffer(url) {
const response = await source_default2(url);
if (!response.ok) {
if (!response.ok)
throw new Error(`Failed to fetch ${url}`);
}
return Buffer.from(response.body);
return import_node_buffer3.Buffer.from(response.body);
}
async function findPythonVersion(version2, architecture, allowPreReleases, updateEnvironment = true) {
let pythonVersion = "";
@ -90926,35 +90984,72 @@ async function findPythonVersion(version2, architecture, allowPreReleases, updat
}
}
async function readFile(filePath) {
return await import_fs3.promises.readFile(filePath, "utf8");
return await import_node_fs.default.promises.readFile(filePath, "utf8");
}
async function getOutput(command, args) {
const { stdout, exitCode, stderr } = await (0, import_exec.getExecOutput)(command, args);
if (exitCode && stderr) {
if (exitCode && stderr)
throw new Error(`Could not run ${command} ${args.join(" ")}: ${stderr}`);
}
return stdout.trim();
}
function isCacheAvailable() {
if (!core6.getBooleanInput("cache")) {
if (!core6.getBooleanInput("cache"))
return false;
}
if (!cache2.isFeatureAvailable()) {
core6.warning("Caching is not supported on this platform.");
return false;
}
return true;
}
function resolveVersionInputFromDefaultFile() {
const couples = [
[".python-version", getVersionInputFromPlainFile]
];
for (const [versionFile, _fn] of couples) {
logWarning(
`Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '${versionFile}' file.`
);
if (import_node_fs.default.existsSync(versionFile))
return _fn(versionFile);
else
logWarning(`${versionFile} doesn't exist.`);
}
return [];
}
function resolveVersionInput() {
let versions = core6.getMultilineInput("python-version");
const versionFile = core6.getInput("python-version-file");
if (versions.length) {
if (versionFile) {
core6.warning(
"Both python-version and python-version-file inputs are specified, only python-version will be used."
);
}
} else {
if (versionFile) {
if (!import_node_fs.default.existsSync(versionFile)) {
throw new Error(
`The specified python version file at: ${versionFile} doesn't exist.`
);
}
versions = getVersionInputFromFile(versionFile);
} else {
versions = resolveVersionInputFromDefaultFile();
}
}
return versions;
}
// src/caches.ts
var import_path = __toESM(require("path"));
var import_node_path = __toESM(require("node:path"));
var import_node_process3 = __toESM(require("node:process"));
var core7 = __toESM(require_core());
var cache3 = __toESM(require_cache2());
var import_glob = __toESM(require_glob2());
async function calculateCacheKeys(pythonVersion, cacheDependencyPath) {
const hash = await (0, import_glob.hashFiles)(cacheDependencyPath);
const primaryKey = `setup-pdm-${process.env["RUNNER_OS"]}-python-${pythonVersion}-${hash}`;
const restoreKey = `setup-pdm-${process.env["RUNNER_OS"]}-python-${pythonVersion}-`;
const primaryKey = `setup-pdm-${import_node_process3.default.env.RUNNER_OS}-python-${pythonVersion}-${hash}`;
const restoreKey = `setup-pdm-${import_node_process3.default.env.RUNNER_OS}-python-${pythonVersion}-`;
return { primaryKey, restoreKeys: [restoreKey] };
}
async function cacheDependencies(pdmBin, pythonVersion) {
@ -90962,7 +91057,7 @@ async function cacheDependencies(pdmBin, pythonVersion) {
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`
`No file in ${import_node_process3.default.cwd()} matched to [${cacheDependencyPath.split("\n").join(",")}], make sure you have checked out the target repository`
);
}
const cachePath = await getCacheDirectories(pdmBin);
@ -90973,8 +91068,8 @@ async function cacheDependencies(pdmBin, pythonVersion) {
}
async function getCacheDirectories(pdmBin) {
const paths = [
import_path.default.join(process.cwd(), ".venv"),
import_path.default.join(process.cwd(), "__pypackages__")
import_node_path.default.join(import_node_process3.default.cwd(), ".venv"),
import_node_path.default.join(import_node_process3.default.cwd(), "__pypackages__")
];
paths.push(await getOutput(pdmBin, ["config", "cache_dir"]));
paths.push(await getOutput(pdmBin, ["config", "venv.location"]));
@ -90994,47 +91089,42 @@ function handleMatchResult(matchedKey, primaryKey) {
var INSTALL_SCRIPT_URL = "https://pdm.fming.dev/install-pdm.py";
function getPep582Path(installDir, pythonVersion) {
const parsedVersion = (0, import_parse3.default)(pythonVersion);
if (IS_WINDOWS) {
return import_path2.default.resolve(installDir, "Lib/site-packages/pdm/pep582");
} else {
return import_path2.default.resolve(installDir, "lib", `python${parsedVersion.major}.${parsedVersion.minor}`, "site-packages/pdm/pep582");
}
if (IS_WINDOWS)
return import_node_path2.default.resolve(installDir, "Lib/site-packages/pdm/pep582");
else
return import_node_path2.default.resolve(installDir, "lib", `python${parsedVersion.major}.${parsedVersion.minor}`, "site-packages/pdm/pep582");
}
async function run() {
const arch2 = core8.getInput("architecture") || os4.arch();
const pdmVersion = core8.getInput("version");
const pythonVersion = core8.getInput("python-version");
const pythonVersion = resolveVersionInput()[0] || "3.x";
const updateEnvironment = core8.getBooleanInput("update-python");
const allowPythonPreReleases = core8.getBooleanInput("allow-python-prereleases");
const cmdArgs = ["-"];
if (core8.getBooleanInput("prerelease")) {
if (core8.getBooleanInput("prerelease"))
cmdArgs.push("--prerelease");
}
if (pdmVersion) {
if (pdmVersion)
cmdArgs.push("--version", pdmVersion);
}
cmdArgs.push("-o", "install-output.json");
try {
await (0, import_exec2.exec)(IS_WINDOWS ? "python" : "python3", cmdArgs, { input: await fetchUrlAsBuffer(INSTALL_SCRIPT_URL) });
const installOutput = JSON.parse(await readFile("install-output.json"));
core8.debug(`Install output: ${installOutput}`);
core8.setOutput("pdm-version", installOutput.pdm_version);
core8.setOutput("pdm-bin", import_path2.default.join(installOutput.install_location, installOutput.pdm_bin));
core8.addPath(import_path2.default.dirname(installOutput.pdm_bin));
if (core8.getBooleanInput("enable-pep582")) {
core8.setOutput("pdm-bin", import_node_path2.default.join(installOutput.install_location, installOutput.pdm_bin));
core8.addPath(import_node_path2.default.dirname(installOutput.pdm_bin));
if (core8.getBooleanInput("enable-pep582"))
core8.exportVariable("PYTHONPATH", getPep582Path(installOutput.install_location, installOutput.install_python_version));
}
const installedPython = await findPythonVersion(pythonVersion, arch2, allowPythonPreReleases, updateEnvironment);
if (process.platform === "linux") {
if (import_node_process4.default.platform === "linux") {
core8.exportVariable("LD_PRELOAD", "/lib/x86_64-linux-gnu/libgcc_s.so.1");
}
core8.info(`Successfully setup ${installOutput.pdm_version} with Python ${installedPython}`);
const matchersPath = import_path2.default.join(__dirname, "..", ".github");
core8.info(`##[add-matcher]${import_path2.default.join(matchersPath, "python.json")}`);
if (isCacheAvailable()) {
const matchersPath = import_node_path2.default.join(__dirname, "..", ".github");
core8.info(`##[add-matcher]${import_node_path2.default.join(matchersPath, "python.json")}`);
if (isCacheAvailable())
await cacheDependencies(installOutput.pdm_bin, installedPython);
}
await import_fs4.promises.rm("install-output.json");
await import_node_fs2.promises.rm("install-output.json");
} catch (error2) {
core8.setFailed(error2.message);
}

4
eslint.config.js Normal file
View File

@ -0,0 +1,4 @@
// eslint.config.js
const antfu = require('@antfu/eslint-config').default
module.exports = antfu()

View File

@ -1,16 +1,18 @@
{
"name": "setup-pdm",
"version": "2.0.0",
"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 --target=node20 --outfile=dist/setup-pdm.js && esbuild src/cache-save.ts --bundle --platform=node --target=node20 --outfile=dist/cache-save.js"
},
"packageManager": "pnpm@8.14.3",
"description": "The GitHub Action for using pdm as the package manager",
"repository": {
"type": "git",
"url": "git+https://github.com/pdm-project/setup-pdm.git"
},
"main": "dist/setup-pdm.js",
"scripts": {
"build": "esno build.ts",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@actions/cache": "^3.2.3",
"@actions/core": "^1.10.1",
@ -21,9 +23,12 @@
"setup-python": "actions/setup-python"
},
"devDependencies": {
"@antfu/eslint-config": "^2.4.6",
"@types/node": "^18.11.0",
"@types/semver": "^7.5.4",
"esbuild": "^0.19.12",
"eslint": "^8.56.0",
"esno": "^4.0.0",
"typescript": "^4.8.4"
}
}

2345
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,18 @@
[project]
name = "setup-pdm"
version = "0.0.0"
authors = [
{name = "Frost Ming", email = "mianghong@gmail.com"},
]
authors = [ { name = "Frost Ming", email = "mianghong@gmail.com" }, ]
requires-python = ">=3.8"
license = {text = "MIT"}
license = { text = "MIT" }
[tool.pdm]
distribution = false
[tool.pdm.dev-dependencies]
dev = [
"urllib3; python_version=='3.8'",
"certifi; python_version=='3.9'",
"pytz; python_version=='3.10'",
"setuptools; python_version=='3.11'",
"six; python_version=='3.12'",
"urllib3; python_version=='3.8'",
"certifi; python_version=='3.9'",
"pytz; python_version=='3.10'",
"setuptools; python_version=='3.11'",
"six; python_version=='3.12'",
]

View File

@ -1,45 +1,45 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import fs from 'fs';
import fs from 'node:fs'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
async function run() {
try {
const cache = core.getBooleanInput('cache');
if (cache) {
await saveCache();
}
} catch (error) {
const err = error as Error;
core.setFailed(err.message);
const cache = core.getBooleanInput('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[];
const cachePaths = JSON.parse(core.getState('cache-paths')) as string[]
core.debug(`paths for caching are ${cachePaths.join(', ')}`);
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(', ')}`);
}
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');
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) {
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;
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}`);
const cacheId = await cache.saveCache(cachePaths, primaryKey)
if (cacheId === -1)
return
core.info(`Cache saved with the key: ${primaryKey}`)
}
run();
run()

View File

@ -1,58 +1,57 @@
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';
import path from 'node:path'
import process from 'node:process'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import { hashFiles } from '@actions/glob'
import { getOutput } from './utils'
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 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);
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`
);
.join(',')}], make sure you have checked out the target repository`,
)
}
const cachePath = await getCacheDirectories(pdmBin);
const cachePath = await getCacheDirectories(pdmBin)
core.saveState('cache-paths', cachePath);
core.saveState('cache-primary-key', primaryKey);
core.saveState('cache-paths', cachePath)
core.saveState('cache-primary-key', primaryKey)
const matchedKey = await cache.restoreCache(cachePath, primaryKey, restoreKeys);
const matchedKey = await cache.restoreCache(cachePath, primaryKey, restoreKeys)
handleMatchResult(matchedKey, primaryKey);
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;
]
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.saveState('cache-matched-key', matchedKey)
core.info(`Cache restored from key: ${matchedKey}`)
}
core.setOutput('cache-hit', matchedKey === primaryKey);
else {
core.info(`pdm cache is not found`)
}
core.setOutput('cache-hit', matchedKey === primaryKey)
}
export { cacheDependencies };
export { cacheDependencies }

View File

@ -1,72 +1,72 @@
import * as os from 'os';
import path from 'path';
import * as core from '@actions/core';
import { exec } from '@actions/exec';
import { promises as fs } from 'fs';
import { IS_WINDOWS } from 'setup-python/src/utils';
import semParse from 'semver/functions/parse';
import * as utils from './utils';
import { cacheDependencies } from './caches';
import * as os from 'node:os'
import path from 'node:path'
import { promises as fs } from 'node:fs'
import process from 'node:process'
import * as core from '@actions/core'
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://pdm.fming.dev/install-pdm.py';
const INSTALL_SCRIPT_URL = 'https://pdm.fming.dev/install-pdm.py'
interface InstallOutput {
pdm_version: string;
pdm_bin: string;
install_python_version: string;
install_location: string;
pdm_version: string
pdm_bin: string
install_python_version: string
install_location: string
}
function getPep582Path(installDir: string, pythonVersion: string): string {
const parsedVersion = semParse(pythonVersion)!;
if (IS_WINDOWS) {
return path.resolve(installDir, 'Lib/site-packages/pdm/pep582');
} else {
return path.resolve(installDir, 'lib', `python${parsedVersion.major}.${parsedVersion.minor}`, 'site-packages/pdm/pep582');
}
const parsedVersion = semParse(pythonVersion)!
if (IS_WINDOWS)
return path.resolve(installDir, 'Lib/site-packages/pdm/pep582')
else
return path.resolve(installDir, 'lib', `python${parsedVersion.major}.${parsedVersion.minor}`, 'site-packages/pdm/pep582')
}
async function run(): Promise<void> {
const arch = core.getInput('architecture') || os.arch();
const pdmVersion = core.getInput('version');
const pythonVersion = core.getInput('python-version');
const updateEnvironment = core.getBooleanInput('update-python');
const allowPythonPreReleases = core.getBooleanInput('allow-python-prereleases');
const cmdArgs = ['-'];
if (core.getBooleanInput('prerelease')) {
cmdArgs.push('--prerelease');
}
if (pdmVersion) {
cmdArgs.push('--version', pdmVersion);
}
cmdArgs.push('-o', 'install-output.json');
const arch = core.getInput('architecture') || os.arch()
const pdmVersion = core.getInput('version')
const pythonVersion = utils.resolveVersionInput()[0] || '3.x'
const updateEnvironment = core.getBooleanInput('update-python')
const allowPythonPreReleases = core.getBooleanInput('allow-python-prereleases')
const cmdArgs = ['-']
if (core.getBooleanInput('prerelease'))
cmdArgs.push('--prerelease')
if (pdmVersion)
cmdArgs.push('--version', pdmVersion)
cmdArgs.push('-o', 'install-output.json')
// Use the default python version installed with the runner
try {
await exec(IS_WINDOWS ? 'python' : 'python3', 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.getBooleanInput('enable-pep582')) {
core.exportVariable('PYTHONPATH', getPep582Path(installOutput.install_location, installOutput.install_python_version));
}
await exec(IS_WINDOWS ? 'python' : 'python3', 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.getBooleanInput('enable-pep582'))
core.exportVariable('PYTHONPATH', getPep582Path(installOutput.install_location, installOutput.install_python_version))
const installedPython = await utils.findPythonVersion(pythonVersion, arch, allowPythonPreReleases, updateEnvironment);
const installedPython = await utils.findPythonVersion(pythonVersion, arch, allowPythonPreReleases, updateEnvironment)
if (process.platform === 'linux') {
// See https://github.com/actions/virtual-environments/issues/2803
core.exportVariable('LD_PRELOAD', '/lib/x86_64-linux-gnu/libgcc_s.so.1');
}
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);
core.exportVariable('LD_PRELOAD', '/lib/x86_64-linux-gnu/libgcc_s.so.1')
}
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)
await fs.rm('install-output.json')
} catch (error: any) {
core.setFailed(error.message);
}
catch (error: any) {
core.setFailed(error.message)
}
}
run();
run()

View File

@ -1,75 +1,123 @@
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';
import fs from 'node:fs'
import { Buffer } from 'node:buffer'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import got from 'got'
import { useCpythonVersion } from 'setup-python/src/find-python'
import { findPyPyVersion } from 'setup-python/src/find-pypy'
import {
getVersionInputFromFile,
getVersionInputFromPlainFile,
logWarning,
} from 'setup-python/src/utils'
import { getExecOutput } from '@actions/exec'
function isPyPyVersion(versionSpec: string): boolean {
return versionSpec.startsWith('pypy');
return versionSpec.startsWith('pypy')
}
export async function fetchUrlAsBuffer(url: string): Promise<Buffer> {
const response = await got(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}`);
}
return Buffer.from(response.body);
const response = await got(url)
if (!response.ok)
throw new Error(`Failed to fetch ${url}`)
return Buffer.from(response.body)
}
export async function findPythonVersion(version: string, architecture: string, allowPreReleases: boolean, updateEnvironment: boolean = true): Promise<string> {
let pythonVersion = '';
let pythonVersion = ''
if (isPyPyVersion(version)) {
const installed = await findPyPyVersion(
version,
architecture,
updateEnvironment,
false,
allowPreReleases
);
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
allowPreReleases,
)
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`
core.info(
`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`
);
return `pypy-${installed.resolvedPythonVersion}`;
} else {
`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`,
)
return `pypy-${installed.resolvedPythonVersion}`
}
else {
const installed = await useCpythonVersion(
version,
architecture,
updateEnvironment,
false,
allowPreReleases
);
pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
return installed.version;
allowPreReleases,
)
pythonVersion = installed.version
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`)
return installed.version
}
}
export async function readFile(filePath: string): Promise<string> {
return await fs.readFile(filePath, 'utf8');
return await fs.promises.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();
}
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 (!core.getBooleanInput('cache'))
return false
if (!cache.isFeatureAvailable()) {
core.warning('Caching is not supported on this platform.');
return false;
core.warning('Caching is not supported on this platform.')
return false
}
return true;
return true
}
function resolveVersionInputFromDefaultFile(): string[] {
const couples: [string, (versionFile: string) => string[]][] = [
['.python-version', getVersionInputFromPlainFile],
]
for (const [versionFile, _fn] of couples) {
logWarning(
`Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '${versionFile}' file.`,
)
if (fs.existsSync(versionFile))
return _fn(versionFile)
else
logWarning(`${versionFile} doesn't exist.`)
}
return []
}
export function resolveVersionInput() {
let versions = core.getMultilineInput('python-version')
const versionFile = core.getInput('python-version-file')
if (versions.length) {
if (versionFile) {
core.warning(
'Both python-version and python-version-file inputs are specified, only python-version will be used.',
)
}
}
else {
if (versionFile) {
if (!fs.existsSync(versionFile)) {
throw new Error(
`The specified python version file at: ${versionFile} doesn't exist.`,
)
}
versions = getVersionInputFromFile(versionFile)
}
else {
versions = resolveVersionInputFromDefaultFile()
}
}
return versions
}

View File

@ -2,17 +2,9 @@
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "allowJs": true /* Allow javascript files to be compiled. */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./lib" /* Redirect output structure to the directory. */,
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
@ -23,6 +15,14 @@
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "allowJs": true /* Allow javascript files to be compiled. */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./lib" /* Redirect output structure to the directory. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */