mirror of
https://github.com/julia-actions/setup-julia.git
synced 2026-02-12 02:56:54 +08:00
Resolve min as the earliest compatible Julia version (compatible with the user's project) (#202)
* Support the special version "MIN" * Support JULIA_PROJECT * Add tests * Add forgotten test fixtures * Get latest prerelease/release * No special pre-release behaviour * Add test for NPM semver difference * Robust test suite * Disallow less-than-equal * Refactor validJuliaCompatRange to return a validRange * Rename MIN to min * Rename getProjectFile to getProjectFilePath * Comment on "project" input * Additional tests for getProjectFilePath * Add comment on `juliaCompatRange` Co-authored-by: Dilum Aluthge <dilum@aluthge.com> * Update dependencies --------- Co-authored-by: Dilum Aluthge <dilum@aluthge.com>
This commit is contained in:
108
lib/installer.js
generated
108
lib/installer.js
generated
@@ -34,6 +34,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getJuliaVersionInfo = getJuliaVersionInfo;
|
||||
exports.getJuliaVersions = getJuliaVersions;
|
||||
exports.getProjectFilePath = getProjectFilePath;
|
||||
exports.validJuliaCompatRange = validJuliaCompatRange;
|
||||
exports.readJuliaCompatRange = readJuliaCompatRange;
|
||||
exports.getJuliaVersion = getJuliaVersion;
|
||||
exports.getFileInfo = getFileInfo;
|
||||
exports.getDownloadURL = getDownloadURL;
|
||||
@@ -48,6 +51,7 @@ const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
const retry = require("async-retry");
|
||||
const semver = __importStar(require("semver"));
|
||||
const toml = __importStar(require("toml"));
|
||||
const LTS_VERSION = '1.6';
|
||||
const MAJOR_VERSION = '1'; // Could be deduced from versions.json
|
||||
// Translations between actions input and Julia arch names
|
||||
@@ -112,20 +116,106 @@ function getJuliaVersions(versionInfo) {
|
||||
return versions;
|
||||
});
|
||||
}
|
||||
function getJuliaVersion(availableReleases, versionInput, includePrerelease = false) {
|
||||
/**
|
||||
* @returns The path to the Julia project file
|
||||
*/
|
||||
function getProjectFilePath(projectInput = "") {
|
||||
let projectFilePath = "";
|
||||
// Default value for projectInput
|
||||
if (!projectInput) {
|
||||
projectInput = process.env.JULIA_PROJECT || ".";
|
||||
}
|
||||
if (fs.existsSync(projectInput) && fs.lstatSync(projectInput).isFile()) {
|
||||
projectFilePath = projectInput;
|
||||
}
|
||||
else {
|
||||
for (let projectFilename of ["JuliaProject.toml", "Project.toml"]) {
|
||||
let p = path.join(projectInput, projectFilename);
|
||||
if (fs.existsSync(p) && fs.lstatSync(p).isFile()) {
|
||||
projectFilePath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!projectFilePath) {
|
||||
throw new Error(`Unable to locate project file with project input: ${projectInput}`);
|
||||
}
|
||||
return projectFilePath;
|
||||
}
|
||||
/**
|
||||
* @returns A valid NPM semver range from a Julia compat range or null if it's not valid
|
||||
*/
|
||||
function validJuliaCompatRange(compatRange) {
|
||||
let ranges = [];
|
||||
for (let range of compatRange.split(",")) {
|
||||
range = range.trim();
|
||||
// An empty range isn't supported by Julia
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
// NPM's semver doesn't understand unicode characters such as `≥` so we'll convert to alternatives
|
||||
range = range.replace("≥", ">=").replace("≤", "<=");
|
||||
// Cleanup whitespace. Julia only allows whitespace between the specifier and version with certain specifiers
|
||||
range = range.replace(/\s+/g, " ").replace(/(?<=(>|>=|≥|<)) (?=\d)/g, "");
|
||||
if (!semver.validRange(range) || range.split(/(?<! -) (?!- )/).length > 1 || range.startsWith("<=") || range === "*") {
|
||||
return null;
|
||||
}
|
||||
else if (range.search(/^\d/) === 0 && !range.includes(" ")) {
|
||||
// Compat version is just a basic version number (e.g. 1.2.3). Since Julia's Pkg.jl's uses caret
|
||||
// as the default specifier (e.g. `1.2.3 == ^1.2.3`) and NPM's semver uses tilde as the default
|
||||
// specifier (e.g. `1.2.3 == 1.2.x == ~1.2.3`) we will introduce the caret specifier to ensure the
|
||||
// orignal intent is respected.
|
||||
// https://pkgdocs.julialang.org/v1/compatibility/#Version-specifier-format
|
||||
// https://github.com/npm/node-semver#x-ranges-12x-1x-12-
|
||||
range = "^" + range;
|
||||
}
|
||||
ranges.push(range);
|
||||
}
|
||||
return semver.validRange(ranges.join(" || "));
|
||||
}
|
||||
/**
|
||||
* @returns An array of version ranges compatible with the Julia project
|
||||
*/
|
||||
function readJuliaCompatRange(projectFileContent) {
|
||||
var _a;
|
||||
let compatRange;
|
||||
let meta = toml.parse(projectFileContent);
|
||||
if (((_a = meta.compat) === null || _a === void 0 ? void 0 : _a.julia) !== undefined) {
|
||||
compatRange = validJuliaCompatRange(meta.compat.julia);
|
||||
}
|
||||
else {
|
||||
compatRange = "*";
|
||||
}
|
||||
if (!compatRange) {
|
||||
throw new Error(`Invalid version range found in Julia compat: ${compatRange}`);
|
||||
}
|
||||
return compatRange;
|
||||
}
|
||||
function getJuliaVersion(availableReleases, versionInput, includePrerelease = false, juliaCompatRange = "") {
|
||||
// Note: `juliaCompatRange` is ignored unless `versionInput` is `min`
|
||||
let version;
|
||||
if (semver.valid(versionInput) == versionInput || versionInput.endsWith('nightly')) {
|
||||
// versionInput is a valid version or a nightly version, use it directly
|
||||
return versionInput;
|
||||
version = versionInput;
|
||||
}
|
||||
if (versionInput == 'lts') {
|
||||
return getJuliaVersion(availableReleases, LTS_VERSION, false);
|
||||
else if (versionInput == "min") {
|
||||
// Resolve "min" to the minimum supported Julia version compatible with the project file
|
||||
if (!juliaCompatRange) {
|
||||
throw new Error('Unable to use version "min" when the Julia project file does not specify a compat for Julia');
|
||||
}
|
||||
version = semver.minSatisfying(availableReleases, juliaCompatRange, { includePrerelease });
|
||||
}
|
||||
if (versionInput == 'pre') {
|
||||
return getJuliaVersion(availableReleases, MAJOR_VERSION, true);
|
||||
else if (versionInput == "lts") {
|
||||
version = semver.maxSatisfying(availableReleases, LTS_VERSION, { includePrerelease: false });
|
||||
}
|
||||
// Use the highest available version that matches versionInput
|
||||
let version = semver.maxSatisfying(availableReleases, versionInput, { includePrerelease });
|
||||
if (version == null) {
|
||||
else if (versionInput == "pre") {
|
||||
version = semver.maxSatisfying(availableReleases, MAJOR_VERSION, { includePrerelease: true });
|
||||
}
|
||||
else {
|
||||
// Use the highest available version that matches versionInput
|
||||
version = semver.maxSatisfying(availableReleases, versionInput, { includePrerelease });
|
||||
}
|
||||
if (!version) {
|
||||
throw new Error(`Could not find a Julia version that matches ${versionInput}`);
|
||||
}
|
||||
// GitHub tags start with v, remove it
|
||||
|
||||
9
lib/setup-julia.js
generated
9
lib/setup-julia.js
generated
@@ -72,6 +72,7 @@ function run() {
|
||||
const versionInput = core.getInput('version').trim();
|
||||
const includePrereleases = core.getInput('include-all-prereleases').trim() == 'true';
|
||||
const originalArchInput = core.getInput('arch').trim();
|
||||
const projectInput = core.getInput('project').trim(); // Julia project file
|
||||
// It can easily happen that, for example, a workflow file contains an input `version: ${{ matrix.julia-version }}`
|
||||
// while the strategy matrix only contains a key `${{ matrix.version }}`.
|
||||
// In that case, we want the action to fail, rather than trying to download julia from an URL that's missing parts and 404ing.
|
||||
@@ -100,9 +101,15 @@ function run() {
|
||||
// before we index into the `archSynonyms` dict.
|
||||
const arch = archSynonyms[processedArchInput.toLowerCase()];
|
||||
core.debug(`Mapped the "arch" from ${processedArchInput} to ${arch}`);
|
||||
// Determine the Julia compat ranges as specified by the Project.toml only for special versions that require them.
|
||||
let juliaCompatRange = "";
|
||||
if (versionInput === "min") {
|
||||
const projectFilePath = installer.getProjectFilePath(projectInput);
|
||||
juliaCompatRange = installer.readJuliaCompatRange(fs.readFileSync(projectFilePath).toString());
|
||||
}
|
||||
const versionInfo = yield installer.getJuliaVersionInfo();
|
||||
const availableReleases = yield installer.getJuliaVersions(versionInfo);
|
||||
const version = installer.getJuliaVersion(availableReleases, versionInput, includePrereleases);
|
||||
const version = installer.getJuliaVersion(availableReleases, versionInput, includePrereleases, juliaCompatRange);
|
||||
core.debug(`selected Julia version: ${arch}/${version}`);
|
||||
core.setOutput('julia-version', version);
|
||||
// Search in cache
|
||||
|
||||
Reference in New Issue
Block a user