diff --git a/dist/setup/index.js b/dist/setup/index.js index 26ddc626..5247153b 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -5920,9 +5920,9 @@ exports.ensureLocalInstaller = ensureLocalInstaller; * * @param installerPath must have an appropriate extension for this platform */ -function runInstaller(installerPath, useBundled) { +function runInstaller(installerPath, options) { return __awaiter(this, void 0, void 0, function* () { - const outputPath = conda_1.minicondaPath(useBundled); + const outputPath = conda_1.minicondaPath(options); const installerExtension = path.extname(installerPath); let command; switch (installerExtension) { @@ -7505,22 +7505,22 @@ const conda_1 = __webpack_require__(259); /** * Check if a given conda environment exists */ -function environmentExists(name, useBundled) { - const condaMetaPath = path.join(conda_1.minicondaPath(useBundled), "envs", name, "conda-meta"); +function environmentExists(inputs, options) { + const condaMetaPath = path.join(conda_1.minicondaPath(options), "envs", inputs.activateEnvironment, "conda-meta"); return fs.existsSync(condaMetaPath); } exports.environmentExists = environmentExists; /** * Create test environment */ -function createTestEnvironment(activateEnvironment, useBundled, useMamba) { +function createTestEnvironment(inputs, options) { return __awaiter(this, void 0, void 0, function* () { - if (activateEnvironment !== "root" && - activateEnvironment !== "base" && - activateEnvironment !== "") { - if (!environmentExists(activateEnvironment, useBundled)) { + if (inputs.activateEnvironment !== "root" && + inputs.activateEnvironment !== "base" && + inputs.activateEnvironment !== "") { + if (!environmentExists(inputs, options)) { core.startGroup("Create test environment..."); - yield conda_1.condaCommand(["create", "--name", activateEnvironment], useBundled, useMamba); + yield conda_1.condaCommand(["create", "--name", inputs.activateEnvironment], options); core.endGroup(); } } @@ -12271,9 +12271,9 @@ const utils_1 = __webpack_require__(163); /** * Provide current location of miniconda or location where it will be installed */ -function minicondaPath(useBundled = true) { +function minicondaPath(options) { let condaPath = constants_1.MINICONDA_DIR_PATH; - if (!useBundled) { + if (!options.useBundled) { if (constants_1.IS_MAC) { condaPath = "/Users/runner/miniconda3"; } @@ -12287,11 +12287,11 @@ exports.minicondaPath = minicondaPath; /** * Provide cross platform location of conda/mamba executable */ -function condaExecutable(useBundled, useMamba = false) { - const dir = minicondaPath(useBundled); +function condaExecutable(options) { + const dir = minicondaPath(options); let condaExe; let commandName; - commandName = useMamba ? "mamba" : "conda"; + commandName = options.useMamba ? "mamba" : "conda"; commandName = constants_1.IS_WINDOWS ? commandName + ".bat" : commandName; condaExe = path.join(dir, "condabin", commandName); return condaExe; @@ -12300,9 +12300,9 @@ exports.condaExecutable = condaExecutable; /** * Run Conda command */ -function condaCommand(cmd, useBundled, useMamba = false) { +function condaCommand(cmd, options) { return __awaiter(this, void 0, void 0, function* () { - const command = [condaExecutable(useBundled, useMamba), ...cmd]; + const command = [condaExecutable(options), ...cmd]; return yield utils_1.execute(command); }); } @@ -12310,23 +12310,27 @@ exports.condaCommand = condaCommand; /** * Setup Conda configuration */ -function applyCondaConfiguration(condaConfig, useBundled) { +function applyCondaConfiguration(options) { return __awaiter(this, void 0, void 0, function* () { - for (const key of Object.keys(condaConfig)) { - core.info(`"${key}": "${condaConfig[key]}"`); - if (condaConfig[key].length !== 0) { + // TODO: figure out a way to know a-priori if we have mamba for initial commands + const notMambaOptions = Object.assign(Object.assign({}, options), { useMamba: false }); + for (const key of Object.keys(options.condaConfig)) { + core.info(`"${key}": "${options.condaConfig[key]}"`); + if (options.condaConfig[key].length !== 0) { if (key === "channels") { // Split by comma and reverse order to preserve higher priority // as listed in the option - let channels = condaConfig[key].split(",").reverse(); + let channels = options.condaConfig[key] + .split(",") + .reverse(); let channel; for (channel of channels) { - yield condaCommand(["config", "--add", key, channel], useBundled, false); + yield condaCommand(["config", "--add", key, channel], notMambaOptions); } } else { try { - yield condaCommand(["config", "--set", key, condaConfig[key]], useBundled, false); + yield condaCommand(["config", "--set", key, options.condaConfig[key]], options); } catch (err) { core.warning(`Couldn't set conda configuration '${key}'`); @@ -12334,23 +12338,23 @@ function applyCondaConfiguration(condaConfig, useBundled) { } } } - yield condaCommand(["config", "--show-sources"], useBundled, false); - yield condaCommand(["config", "--show"], useBundled, false); + yield condaCommand(["config", "--show-sources"], notMambaOptions); + yield condaCommand(["config", "--show"], notMambaOptions); }); } exports.applyCondaConfiguration = applyCondaConfiguration; /** * Initialize Conda */ -function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) { +function condaInit(inputs, options) { return __awaiter(this, void 0, void 0, function* () { let ownPath; - const isValidActivate = activateEnvironment !== "base" && - activateEnvironment !== "root" && - activateEnvironment !== ""; - const autoActivateBase = condaConfig["auto_activate_base"] === "true"; + const isValidActivate = inputs.activateEnvironment !== "base" && + inputs.activateEnvironment !== "root" && + inputs.activateEnvironment !== ""; + const autoActivateBase = options.condaConfig["auto_activate_base"] === "true"; // Fix ownership of folders - if (useBundled) { + if (options.useBundled) { if (constants_1.IS_MAC) { core.startGroup("Fixing conda folders ownership"); const userName = process.env.USER; @@ -12359,7 +12363,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) "chown", "-R", `${userName}:staff`, - minicondaPath(useBundled), + minicondaPath(options), ]); core.endGroup(); } @@ -12371,7 +12375,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) "etc/profile.d/", "/Lib/site-packages/xonsh/", ]) { - ownPath = path.join(minicondaPath(useBundled), folder); + ownPath = path.join(minicondaPath(options), folder); if (fs.existsSync(ownPath)) { core.startGroup(`Fixing ${folder} ownership`); yield utils_1.execute(["takeown", "/f", ownPath, "/r", "/d", "y"]); @@ -12381,7 +12385,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) } } // Remove profile files - if (removeProfiles == "true") { + if (inputs.removeProfiles == "true") { for (let rc of [ ".bashrc", ".bash_profile", @@ -12408,7 +12412,12 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) } // Run conda init for (let cmd of ["--all"]) { - yield utils_1.execute([condaExecutable(useBundled, false), "init", cmd]); + // TODO: determine when it's safe to use mamba + yield utils_1.execute([ + condaExecutable(Object.assign(Object.assign({}, options), { useMamba: false })), + "init", + cmd, + ]); } // Rename files if (constants_1.IS_LINUX) { @@ -12429,7 +12438,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) if (isValidActivate) { powerExtraText += ` # Conda Setup Action: Custom activation - conda activate ${activateEnvironment}`; + conda activate ${inputs.activateEnvironment}`; } powerExtraText += ` # ----------------------------------------------------------------------------`; @@ -12441,7 +12450,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) if (isValidActivate) { bashExtraText += ` # Conda Setup Action: Custom activation - conda activate ${activateEnvironment}`; + conda activate ${inputs.activateEnvironment}`; bashExtraText += ` # ----------------------------------------------------------------------------`; } @@ -12456,7 +12465,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) if (isValidActivate) { batchExtraText += ` :: Conda Setup Action: Custom activation - @CALL "%CONDA_BAT%" activate ${activateEnvironment}`; + @CALL "%CONDA_BAT%" activate ${inputs.activateEnvironment}`; } batchExtraText += ` :: Conda Setup Action: Basic configuration @@ -12475,7 +12484,7 @@ function condaInit(activateEnvironment, useBundled, condaConfig, removeProfiles) "~/Documents/PowerShell/profile.ps1": powerExtraText, "~/Documents/WindowsPowerShell/profile.ps1": powerExtraText, }; - if (useBundled) { + if (options.useBundled) { extraShells = { "C:/Miniconda/etc/profile.d/conda.sh": bashExtraText, "C:/Miniconda/etc/fish/conf.d/conda.fish": bashExtraText, @@ -21442,39 +21451,40 @@ const env_1 = __webpack_require__(166); */ function setupMiniconda(inputs) { return __awaiter(this, void 0, void 0, function* () { - // TODO: derive runtime condaConfig from immutable input below - const condaConfig = Object.assign({}, inputs.condaConfig); - let useBundled = true; - let useMamba = false; + let options = { + useBundled: true, + useMamba: false, + condaConfig: Object.assign({}, inputs.condaConfig), + }; core.startGroup(`Creating bootstrap condarc file in ${constants_1.CONDARC_PATH}...`); yield fs.promises.writeFile(constants_1.CONDARC_PATH, constants_1.BOOTSTRAP_CONDARC); core.endGroup(); if (inputs.installerUrl !== "") { - useBundled = false; - const installerPath = yield installer_1.downloadCustomInstaller(inputs.installerUrl); + options.useBundled = false; + const installerPath = yield installer_1.downloadCustomInstaller(inputs); core.startGroup("Installing Custom Installer..."); - yield installer_1.runInstaller(installerPath, useBundled); + yield installer_1.runInstaller(installerPath, options); core.endGroup(); } else if (inputs.minicondaVersion !== "" || inputs.architecture !== "x64") { core.startGroup("Downloading Miniconda..."); - useBundled = false; - const installerPath = yield installer_1.downloadMiniconda(3, inputs.minicondaVersion, inputs.architecture); + options.useBundled = false; + const installerPath = yield installer_1.downloadMiniconda(3, inputs); core.endGroup(); core.startGroup("Installing Miniconda..."); - yield installer_1.runInstaller(installerPath, useBundled); + yield installer_1.runInstaller(installerPath, options); core.endGroup(); } else { core.startGroup("Locating Miniconda..."); - core.info(conda_1.minicondaPath()); - if (!fs.existsSync(conda_1.minicondaPath())) { + core.info(conda_1.minicondaPath(options)); + if (!fs.existsSync(conda_1.minicondaPath(options))) { throw new Error("Bundled Miniconda not found!"); } core.endGroup(); } core.startGroup("Setup environment variables..."); - yield vars_1.setVariables(useBundled); + yield vars_1.setVariables(options); core.endGroup(); if (inputs.condaConfigFile) { core.startGroup("Copying condarc file..."); @@ -21502,42 +21512,42 @@ function setupMiniconda(inputs) { environmentExplicit = false; } const cacheFolder = utils.cacheFolder(); - yield conda_1.condaCommand(["config", "--add", "pkgs_dirs", cacheFolder], useBundled, useMamba); + yield conda_1.condaCommand(["config", "--add", "pkgs_dirs", cacheFolder], options); core.exportVariable(constants_1.ENV_VAR_CONDA_PKGS, cacheFolder); - if (condaConfig) { + if (options.condaConfig) { if (inputs.environmentFile) { let channels; channels = environmentYaml["channels"]; - if (condaConfig["channels"] === "" && channels !== undefined) { + if (options.condaConfig["channels"] === "" && channels !== undefined) { // TODO: avoid mutating state - condaConfig["channels"] = channels.join(","); + options.condaConfig["channels"] = channels.join(","); } else if (!environmentExplicit) { core.warning('"channels" set on the "environment-file" do not match "channels" set on the action!'); } } core.startGroup("Applying conda configuration..."); - yield conda_1.applyCondaConfiguration(condaConfig, useBundled); + yield conda_1.applyCondaConfiguration(options); core.endGroup(); } core.startGroup("Setup Conda basic configuration..."); - yield conda_1.condaCommand(["config", "--set", "always_yes", "yes", "--set", "changeps1", "no"], useBundled, useMamba); + yield conda_1.condaCommand(["config", "--set", "always_yes", "yes", "--set", "changeps1", "no"], options); core.endGroup(); core.startGroup("Initialize Conda and fix ownership..."); - yield conda_1.condaInit(inputs.activateEnvironment, useBundled, condaConfig, inputs.removeProfiles); + yield conda_1.condaInit(inputs, options); core.endGroup(); if (inputs.condaVersion) { core.startGroup("Installing Conda..."); - yield conda_1.condaCommand(["install", "--name", "base", `conda=${inputs.condaVersion}`], useBundled, useMamba); + yield conda_1.condaCommand(["install", "--name", "base", `conda=${inputs.condaVersion}`], options); core.endGroup(); } - if (condaConfig["auto_update_conda"] == "true") { + if (options.condaConfig["auto_update_conda"] == "true") { core.startGroup("Updating conda..."); - yield conda_1.condaCommand(["update", "conda"], useBundled, useMamba); + yield conda_1.condaCommand(["update", "conda"], options); core.endGroup(); - if (condaConfig) { + if (options.condaConfig) { core.startGroup("Applying conda configuration after update..."); - yield conda_1.applyCondaConfiguration(condaConfig, useBundled); + yield conda_1.applyCondaConfiguration(options); core.endGroup(); } } @@ -21545,26 +21555,26 @@ function setupMiniconda(inputs) { if (inputs.mambaVersion) { core.startGroup("Installing Mamba..."); core.warning(`Mamba support is still experimental and can result in differently solved environments!`); - yield conda_1.condaCommand(["install", "--name", "base", `mamba=${inputs.mambaVersion}`], useBundled, useMamba); + yield conda_1.condaCommand(["install", "--name", "base", `mamba=${inputs.mambaVersion}`], options); if (constants_1.IS_WINDOWS) { // add bat-less forwarder for bash users on Windows - const mambaBat = conda_1.condaExecutable(useBundled, true).replace("\\", "/"); + const mambaBat = conda_1.condaExecutable(Object.assign(Object.assign({}, options), { useMamba: true })).replace("\\", "/"); const contents = `bash.exe -c "exec '${mambaBat}' $*"`; fs.writeFileSync(mambaBat.slice(0, -4), contents); } - useMamba = true; + options.useMamba = true; } if (inputs.condaBuildVersion) { core.startGroup("Installing Conda Build..."); - yield conda_1.condaCommand(["install", "--name", "base", `conda-build=${inputs.condaBuildVersion}`], useBundled, useMamba); + yield conda_1.condaCommand(["install", "--name", "base", `conda-build=${inputs.condaBuildVersion}`], options); core.endGroup(); } if (inputs.activateEnvironment) { - yield env_1.createTestEnvironment(inputs.activateEnvironment, useBundled, useMamba); + yield env_1.createTestEnvironment(inputs, options); } if (inputs.pythonVersion && inputs.activateEnvironment) { core.startGroup(`Installing Python="${inputs.pythonVersion}" on "${inputs.activateEnvironment}" environment...`); - yield tools_1.setupPython(inputs.activateEnvironment, inputs.pythonVersion, useBundled, useMamba); + yield tools_1.setupPython(inputs, options); core.endGroup(); } if (inputs.environmentFile) { @@ -21615,7 +21625,7 @@ function setupMiniconda(inputs) { inputs.environmentFile, "--name", activateEnvironmentToUse, - ], useBundled, useMamba); + ], options); core.endGroup(); } }); @@ -22277,9 +22287,14 @@ const conda_1 = __webpack_require__(259); /** * Setup python test environment */ -function setupPython(activateEnvironment, pythonVersion, useBundled, useMamba) { +function setupPython(inputs, options) { return __awaiter(this, void 0, void 0, function* () { - return yield conda_1.condaCommand(["install", "--name", activateEnvironment, `python=${pythonVersion}`], useBundled, useMamba); + return yield conda_1.condaCommand([ + "install", + "--name", + inputs.activateEnvironment, + `python=${inputs.pythonVersion}`, + ], options); }); } exports.setupPython = setupPython; @@ -29409,16 +29424,16 @@ function minicondaVersions(arch) { * @param minicondaVersion * @param architecture */ -function downloadMiniconda(pythonMajorVersion, minicondaVersion, architecture) { +function downloadMiniconda(pythonMajorVersion, inputs) { return __awaiter(this, void 0, void 0, function* () { // Check valid arch - const arch = constants_1.ARCHITECTURES[architecture]; + const arch = constants_1.ARCHITECTURES[inputs.architecture]; if (!arch) { - throw new Error(`Invalid arch "${architecture}"!`); + throw new Error(`Invalid arch "${inputs.architecture}"!`); } let extension = constants_1.IS_UNIX ? "sh" : "exe"; let osName = constants_1.OS_NAMES[process.platform]; - const minicondaInstallerName = `Miniconda${pythonMajorVersion}-${minicondaVersion}-${osName}-${arch}.${extension}`; + const minicondaInstallerName = `Miniconda${pythonMajorVersion}-${inputs.minicondaVersion}-${osName}-${arch}.${extension}`; core.info(minicondaInstallerName); // Check version name let versions = yield minicondaVersions(arch); @@ -29430,7 +29445,7 @@ function downloadMiniconda(pythonMajorVersion, minicondaVersion, architecture) { return yield base_1.ensureLocalInstaller({ url: constants_1.MINICONDA_BASE_URL + minicondaInstallerName, tool: `Miniconda${pythonMajorVersion}`, - version: minicondaVersion, + version: inputs.minicondaVersion, arch: arch, }); }); @@ -30803,9 +30818,9 @@ const base_1 = __webpack_require__(122); /** * @param url A URL for a file with the CLI of a `constructor`-built artifact */ -function downloadCustomInstaller(url) { +function downloadCustomInstaller(inputs) { return __awaiter(this, void 0, void 0, function* () { - return yield base_1.ensureLocalInstaller({ url }); + return yield base_1.ensureLocalInstaller({ url: inputs.installerUrl }); }); } exports.downloadCustomInstaller = downloadCustomInstaller; @@ -34187,14 +34202,14 @@ const conda_1 = __webpack_require__(259); /** * Add Conda executable to PATH */ -function setVariables(useBundled) { +function setVariables(options) { return __awaiter(this, void 0, void 0, function* () { // Set environment variables - const condaBin = path.join(conda_1.minicondaPath(useBundled), "condabin"); - const conda = conda_1.minicondaPath(useBundled); + const condaBin = path.join(conda_1.minicondaPath(options), "condabin"); + const conda = conda_1.minicondaPath(options); core.info(`Add "${condaBin}" to PATH`); core.addPath(condaBin); - if (!useBundled) { + if (!options.useBundled) { core.info(`Set 'CONDA="${conda}"'`); core.exportVariable("CONDA", conda); } diff --git a/src/conda.ts b/src/conda.ts index a5dde596..a4780591 100644 --- a/src/conda.ts +++ b/src/conda.ts @@ -10,15 +10,15 @@ import * as core from "@actions/core"; import * as io from "@actions/io"; import { IS_LINUX, IS_MAC, IS_WINDOWS, MINICONDA_DIR_PATH } from "./constants"; -import { IShells, TCondaConfig } from "./types"; import { execute } from "./utils"; +import * as types from "./types"; /** * Provide current location of miniconda or location where it will be installed */ -export function minicondaPath(useBundled: boolean = true): string { +export function minicondaPath(options: types.IDynamicOptions): string { let condaPath: string = MINICONDA_DIR_PATH; - if (!useBundled) { + if (!options.useBundled) { if (IS_MAC) { condaPath = "/Users/runner/miniconda3"; } else { @@ -31,14 +31,11 @@ export function minicondaPath(useBundled: boolean = true): string { /** * Provide cross platform location of conda/mamba executable */ -export function condaExecutable( - useBundled: boolean, - useMamba: boolean = false -): string { - const dir: string = minicondaPath(useBundled); +export function condaExecutable(options: types.IDynamicOptions): string { + const dir: string = minicondaPath(options); let condaExe: string; let commandName: string; - commandName = useMamba ? "mamba" : "conda"; + commandName = options.useMamba ? "mamba" : "conda"; commandName = IS_WINDOWS ? commandName + ".bat" : commandName; condaExe = path.join(dir, "condabin", commandName); return condaExe; @@ -49,10 +46,9 @@ export function condaExecutable( */ export async function condaCommand( cmd: string[], - useBundled: boolean, - useMamba: boolean = false + options: types.IDynamicOptions ): Promise { - const command = [condaExecutable(useBundled, useMamba), ...cmd]; + const command = [condaExecutable(options), ...cmd]; return await execute(command); } @@ -60,30 +56,32 @@ export async function condaCommand( * Setup Conda configuration */ export async function applyCondaConfiguration( - condaConfig: TCondaConfig, - useBundled: boolean + options: types.IDynamicOptions ): Promise { - for (const key of Object.keys(condaConfig)) { - core.info(`"${key}": "${condaConfig[key]}"`); - if (condaConfig[key].length !== 0) { + // TODO: figure out a way to know a-priori if we have mamba for initial commands + const notMambaOptions = { ...options, useMamba: false }; + + for (const key of Object.keys(options.condaConfig)) { + core.info(`"${key}": "${options.condaConfig[key]}"`); + if (options.condaConfig[key].length !== 0) { if (key === "channels") { // Split by comma and reverse order to preserve higher priority // as listed in the option - let channels: Array = condaConfig[key].split(",").reverse(); + let channels: Array = options.condaConfig[key] + .split(",") + .reverse(); let channel: string; for (channel of channels) { await condaCommand( ["config", "--add", key, channel], - useBundled, - false + notMambaOptions ); } } else { try { await condaCommand( - ["config", "--set", key, condaConfig[key]], - useBundled, - false + ["config", "--set", key, options.condaConfig[key]], + options ); } catch (err) { core.warning(`Couldn't set conda configuration '${key}'`); @@ -92,30 +90,28 @@ export async function applyCondaConfiguration( } } - await condaCommand(["config", "--show-sources"], useBundled, false); + await condaCommand(["config", "--show-sources"], notMambaOptions); - await condaCommand(["config", "--show"], useBundled, false); + await condaCommand(["config", "--show"], notMambaOptions); } /** * Initialize Conda */ export async function condaInit( - activateEnvironment: string, - useBundled: boolean, - condaConfig: TCondaConfig, - removeProfiles: string + inputs: types.IActionInputs, + options: types.IDynamicOptions ): Promise { let ownPath: string; const isValidActivate: boolean = - activateEnvironment !== "base" && - activateEnvironment !== "root" && - activateEnvironment !== ""; + inputs.activateEnvironment !== "base" && + inputs.activateEnvironment !== "root" && + inputs.activateEnvironment !== ""; const autoActivateBase: boolean = - condaConfig["auto_activate_base"] === "true"; + options.condaConfig["auto_activate_base"] === "true"; // Fix ownership of folders - if (useBundled) { + if (options.useBundled) { if (IS_MAC) { core.startGroup("Fixing conda folders ownership"); const userName: string = process.env.USER as string; @@ -124,7 +120,7 @@ export async function condaInit( "chown", "-R", `${userName}:staff`, - minicondaPath(useBundled), + minicondaPath(options), ]); core.endGroup(); } else if (IS_WINDOWS) { @@ -135,7 +131,7 @@ export async function condaInit( "etc/profile.d/", "/Lib/site-packages/xonsh/", ]) { - ownPath = path.join(minicondaPath(useBundled), folder); + ownPath = path.join(minicondaPath(options), folder); if (fs.existsSync(ownPath)) { core.startGroup(`Fixing ${folder} ownership`); await execute(["takeown", "/f", ownPath, "/r", "/d", "y"]); @@ -146,7 +142,7 @@ export async function condaInit( } // Remove profile files - if (removeProfiles == "true") { + if (inputs.removeProfiles == "true") { for (let rc of [ ".bashrc", ".bash_profile", @@ -173,7 +169,12 @@ export async function condaInit( // Run conda init for (let cmd of ["--all"]) { - await execute([condaExecutable(useBundled, false), "init", cmd]); + // TODO: determine when it's safe to use mamba + await execute([ + condaExecutable({ ...options, useMamba: false }), + "init", + cmd, + ]); } // Rename files @@ -195,7 +196,7 @@ export async function condaInit( if (isValidActivate) { powerExtraText += ` # Conda Setup Action: Custom activation - conda activate ${activateEnvironment}`; + conda activate ${inputs.activateEnvironment}`; } powerExtraText += ` # ----------------------------------------------------------------------------`; @@ -208,7 +209,7 @@ export async function condaInit( if (isValidActivate) { bashExtraText += ` # Conda Setup Action: Custom activation - conda activate ${activateEnvironment}`; + conda activate ${inputs.activateEnvironment}`; bashExtraText += ` # ----------------------------------------------------------------------------`; } @@ -224,7 +225,7 @@ export async function condaInit( if (isValidActivate) { batchExtraText += ` :: Conda Setup Action: Custom activation - @CALL "%CONDA_BAT%" activate ${activateEnvironment}`; + @CALL "%CONDA_BAT%" activate ${inputs.activateEnvironment}`; } batchExtraText += ` :: Conda Setup Action: Basic configuration @@ -232,8 +233,8 @@ export async function condaInit( @SETLOCAL DisableDelayedExpansion :: ---------------------------------------------------------------------------`; - let extraShells: IShells; - const shells: IShells = { + let extraShells: types.IShells; + const shells: types.IShells = { "~/.bash_profile": bashExtraText, "~/.profile": bashExtraText, "~/.zshrc": bashExtraText, @@ -244,7 +245,7 @@ export async function condaInit( "~/Documents/PowerShell/profile.ps1": powerExtraText, "~/Documents/WindowsPowerShell/profile.ps1": powerExtraText, }; - if (useBundled) { + if (options.useBundled) { extraShells = { "C:/Miniconda/etc/profile.d/conda.sh": bashExtraText, "C:/Miniconda/etc/fish/conf.d/conda.fish": bashExtraText, @@ -257,7 +258,7 @@ export async function condaInit( "C:/Miniconda3/condabin/conda_hook.bat": batchExtraText, }; } - const allShells: IShells = { ...shells, ...extraShells }; + const allShells: types.IShells = { ...shells, ...extraShells }; Object.keys(allShells).forEach((key) => { let filePath: string = key.replace("~", os.homedir()); const text = allShells[key]; diff --git a/src/constants.ts b/src/constants.ts index 2fd11edb..23540080 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ import * as os from "os"; import * as path from "path"; -import { IArchitectures, IOperatingSystems } from "./types"; +import * as types from "./types"; //----------------------------------------------------------------------- // Constants @@ -14,14 +14,14 @@ export const IS_UNIX: boolean = IS_MAC || IS_LINUX; export const MINICONDA_BASE_URL: string = "https://repo.anaconda.com/miniconda/"; -export const ARCHITECTURES: IArchitectures = { +export const ARCHITECTURES: types.IArchitectures = { x64: "x86_64", x86: "x86", ARM64: "aarch64", // To be supported by github runners ARM32: "armv7l", // To be supported by github runners }; -export const OS_NAMES: IOperatingSystems = { +export const OS_NAMES: types.IOperatingSystems = { win32: "Windows", darwin: "MacOSX", linux: "Linux", diff --git a/src/env.ts b/src/env.ts index 71ab65ed..fc27a7b7 100644 --- a/src/env.ts +++ b/src/env.ts @@ -4,15 +4,19 @@ import * as path from "path"; import * as core from "@actions/core"; import { minicondaPath, condaCommand } from "./conda"; +import * as types from "./types"; /** * Check if a given conda environment exists */ -export function environmentExists(name: string, useBundled: boolean): boolean { +export function environmentExists( + inputs: types.IActionInputs, + options: types.IDynamicOptions +): boolean { const condaMetaPath: string = path.join( - minicondaPath(useBundled), + minicondaPath(options), "envs", - name, + inputs.activateEnvironment, "conda-meta" ); return fs.existsSync(condaMetaPath); @@ -22,21 +26,19 @@ export function environmentExists(name: string, useBundled: boolean): boolean { * Create test environment */ export async function createTestEnvironment( - activateEnvironment: string, - useBundled: boolean, - useMamba: boolean + inputs: types.IActionInputs, + options: types.IDynamicOptions ): Promise { if ( - activateEnvironment !== "root" && - activateEnvironment !== "base" && - activateEnvironment !== "" + inputs.activateEnvironment !== "root" && + inputs.activateEnvironment !== "base" && + inputs.activateEnvironment !== "" ) { - if (!environmentExists(activateEnvironment, useBundled)) { + if (!environmentExists(inputs, options)) { core.startGroup("Create test environment..."); await condaCommand( - ["create", "--name", activateEnvironment], - useBundled, - useMamba + ["create", "--name", inputs.activateEnvironment], + options ); core.endGroup(); } diff --git a/src/installer/base.ts b/src/installer/base.ts index c1c6b0ee..d59b4273 100644 --- a/src/installer/base.ts +++ b/src/installer/base.ts @@ -6,9 +6,9 @@ import * as core from "@actions/core"; import * as io from "@actions/io"; import * as tc from "@actions/tool-cache"; -import { ILocalInstallerOpts } from "../types"; import { minicondaPath } from "../conda"; import { execute } from "../utils"; +import * as types from "../types"; /** Get the path for a locally-executable installer from cache, or as downloaded * @@ -22,7 +22,7 @@ import { execute } from "../utils"; * - or has been renamed during a build process */ export async function ensureLocalInstaller( - options: ILocalInstallerOpts + options: types.ILocalInstallerOpts ): Promise { core.startGroup("Ensuring Installer..."); @@ -88,9 +88,9 @@ export async function ensureLocalInstaller( */ export async function runInstaller( installerPath: string, - useBundled: boolean + options: types.IDynamicOptions ): Promise { - const outputPath: string = minicondaPath(useBundled); + const outputPath: string = minicondaPath(options); const installerExtension = path.extname(installerPath); let command: string[]; diff --git a/src/installer/download-miniconda.ts b/src/installer/download-miniconda.ts index d8eb28fa..88c3f233 100644 --- a/src/installer/download-miniconda.ts +++ b/src/installer/download-miniconda.ts @@ -13,6 +13,8 @@ import { OS_NAMES, } from "../constants"; +import * as types from "../types"; + /** * List available Miniconda versions * @@ -45,18 +47,17 @@ async function minicondaVersions(arch: string): Promise { */ export async function downloadMiniconda( pythonMajorVersion: number, - minicondaVersion: string, - architecture: string + inputs: types.IActionInputs ): Promise { // Check valid arch - const arch: string = ARCHITECTURES[architecture]; + const arch: string = ARCHITECTURES[inputs.architecture]; if (!arch) { - throw new Error(`Invalid arch "${architecture}"!`); + throw new Error(`Invalid arch "${inputs.architecture}"!`); } let extension: string = IS_UNIX ? "sh" : "exe"; let osName: string = OS_NAMES[process.platform]; - const minicondaInstallerName: string = `Miniconda${pythonMajorVersion}-${minicondaVersion}-${osName}-${arch}.${extension}`; + const minicondaInstallerName: string = `Miniconda${pythonMajorVersion}-${inputs.minicondaVersion}-${osName}-${arch}.${extension}`; core.info(minicondaInstallerName); // Check version name @@ -72,7 +73,7 @@ export async function downloadMiniconda( return await ensureLocalInstaller({ url: MINICONDA_BASE_URL + minicondaInstallerName, tool: `Miniconda${pythonMajorVersion}`, - version: minicondaVersion, + version: inputs.minicondaVersion, arch: arch, }); } diff --git a/src/installer/download-url.ts b/src/installer/download-url.ts index ae431ce4..1f270c9b 100644 --- a/src/installer/download-url.ts +++ b/src/installer/download-url.ts @@ -1,8 +1,11 @@ import { ensureLocalInstaller } from "./base"; +import * as types from "../types"; /** * @param url A URL for a file with the CLI of a `constructor`-built artifact */ -export async function downloadCustomInstaller(url: string): Promise { - return await ensureLocalInstaller({ url }); +export async function downloadCustomInstaller( + inputs: types.IActionInputs +): Promise { + return await ensureLocalInstaller({ url: inputs.installerUrl }); } diff --git a/src/setup.ts b/src/setup.ts index 7ece00e3..eefad0bd 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -16,7 +16,7 @@ import { downloadCustomInstaller, } from "./installer"; -import { IActionInputs, TEnvironment } from "./types"; +import * as types from "./types"; import { BOOTSTRAP_CONDARC, @@ -40,46 +40,43 @@ import { createTestEnvironment } from "./env"; /** * Main conda setup method to handle all configuration options */ -async function setupMiniconda(inputs: IActionInputs): Promise { - // TODO: derive runtime condaConfig from immutable input below - const condaConfig = { ...inputs.condaConfig }; - let useBundled: boolean = true; - let useMamba: boolean = false; +async function setupMiniconda(inputs: types.IActionInputs): Promise { + let options: types.IDynamicOptions = { + useBundled: true, + useMamba: false, + condaConfig: { ...inputs.condaConfig }, + }; core.startGroup(`Creating bootstrap condarc file in ${CONDARC_PATH}...`); await fs.promises.writeFile(CONDARC_PATH, BOOTSTRAP_CONDARC); core.endGroup(); if (inputs.installerUrl !== "") { - useBundled = false; - const installerPath = await downloadCustomInstaller(inputs.installerUrl); + options.useBundled = false; + const installerPath = await downloadCustomInstaller(inputs); core.startGroup("Installing Custom Installer..."); - await runInstaller(installerPath, useBundled); + await runInstaller(installerPath, options); core.endGroup(); } else if (inputs.minicondaVersion !== "" || inputs.architecture !== "x64") { core.startGroup("Downloading Miniconda..."); - useBundled = false; - const installerPath = await downloadMiniconda( - 3, - inputs.minicondaVersion, - inputs.architecture - ); + options.useBundled = false; + const installerPath = await downloadMiniconda(3, inputs); core.endGroup(); core.startGroup("Installing Miniconda..."); - await runInstaller(installerPath, useBundled); + await runInstaller(installerPath, options); core.endGroup(); } else { core.startGroup("Locating Miniconda..."); - core.info(minicondaPath()); - if (!fs.existsSync(minicondaPath())) { + core.info(minicondaPath(options)); + if (!fs.existsSync(minicondaPath(options))) { throw new Error("Bundled Miniconda not found!"); } core.endGroup(); } core.startGroup("Setup environment variables..."); - await setVariables(useBundled); + await setVariables(options); core.endGroup(); if (inputs.condaConfigFile) { @@ -116,21 +113,17 @@ async function setupMiniconda(inputs: IActionInputs): Promise { } const cacheFolder = utils.cacheFolder(); - await condaCommand( - ["config", "--add", "pkgs_dirs", cacheFolder], - useBundled, - useMamba - ); + await condaCommand(["config", "--add", "pkgs_dirs", cacheFolder], options); core.exportVariable(ENV_VAR_CONDA_PKGS, cacheFolder); - if (condaConfig) { + if (options.condaConfig) { if (inputs.environmentFile) { let channels: Array | undefined; channels = environmentYaml["channels"]; - if (condaConfig["channels"] === "" && channels !== undefined) { + if (options.condaConfig["channels"] === "" && channels !== undefined) { // TODO: avoid mutating state - condaConfig["channels"] = channels.join(","); + options.condaConfig["channels"] = channels.join(","); } else if (!environmentExplicit) { core.warning( '"channels" set on the "environment-file" do not match "channels" set on the action!' @@ -138,45 +131,38 @@ async function setupMiniconda(inputs: IActionInputs): Promise { } } core.startGroup("Applying conda configuration..."); - await applyCondaConfiguration(condaConfig, useBundled); + await applyCondaConfiguration(options); core.endGroup(); } core.startGroup("Setup Conda basic configuration..."); await condaCommand( ["config", "--set", "always_yes", "yes", "--set", "changeps1", "no"], - useBundled, - useMamba + options ); core.endGroup(); core.startGroup("Initialize Conda and fix ownership..."); - await condaInit( - inputs.activateEnvironment, - useBundled, - condaConfig, - inputs.removeProfiles - ); + await condaInit(inputs, options); core.endGroup(); if (inputs.condaVersion) { core.startGroup("Installing Conda..."); await condaCommand( ["install", "--name", "base", `conda=${inputs.condaVersion}`], - useBundled, - useMamba + options ); core.endGroup(); } - if (condaConfig["auto_update_conda"] == "true") { + if (options.condaConfig["auto_update_conda"] == "true") { core.startGroup("Updating conda..."); - await condaCommand(["update", "conda"], useBundled, useMamba); + await condaCommand(["update", "conda"], options); core.endGroup(); - if (condaConfig) { + if (options.condaConfig) { core.startGroup("Applying conda configuration after update..."); - await applyCondaConfiguration(condaConfig, useBundled); + await applyCondaConfiguration(options); core.endGroup(); } } @@ -190,53 +176,45 @@ async function setupMiniconda(inputs: IActionInputs): Promise { await condaCommand( ["install", "--name", "base", `mamba=${inputs.mambaVersion}`], - useBundled, - useMamba + options ); if (IS_WINDOWS) { // add bat-less forwarder for bash users on Windows - const mambaBat = condaExecutable(useBundled, true).replace("\\", "/"); + const mambaBat = condaExecutable({ ...options, useMamba: true }).replace( + "\\", + "/" + ); const contents = `bash.exe -c "exec '${mambaBat}' $*"`; fs.writeFileSync(mambaBat.slice(0, -4), contents); } - useMamba = true; + options.useMamba = true; } if (inputs.condaBuildVersion) { core.startGroup("Installing Conda Build..."); await condaCommand( ["install", "--name", "base", `conda-build=${inputs.condaBuildVersion}`], - useBundled, - useMamba + options ); core.endGroup(); } if (inputs.activateEnvironment) { - await createTestEnvironment( - inputs.activateEnvironment, - useBundled, - useMamba - ); + await createTestEnvironment(inputs, options); } if (inputs.pythonVersion && inputs.activateEnvironment) { core.startGroup( `Installing Python="${inputs.pythonVersion}" on "${inputs.activateEnvironment}" environment...` ); - await setupPython( - inputs.activateEnvironment, - inputs.pythonVersion, - useBundled, - useMamba - ); + await setupPython(inputs, options); core.endGroup(); } if (inputs.environmentFile) { - let environmentYaml: TEnvironment; + let environmentYaml: types.TEnvironment; let condaAction: string[]; let activateEnvironmentToUse: string; @@ -300,8 +278,7 @@ async function setupMiniconda(inputs: IActionInputs): Promise { "--name", activateEnvironmentToUse, ], - useBundled, - useMamba + options ); core.endGroup(); } diff --git a/src/tools.ts b/src/tools.ts index 91eef9d1..56185874 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -1,17 +1,21 @@ import { condaCommand } from "./conda"; +import * as types from "./types"; + /** * Setup python test environment */ export async function setupPython( - activateEnvironment: string, - pythonVersion: string, - useBundled: boolean, - useMamba: boolean + inputs: types.IActionInputs, + options: types.IDynamicOptions ): Promise { return await condaCommand( - ["install", "--name", activateEnvironment, `python=${pythonVersion}`], - useBundled, - useMamba + [ + "install", + "--name", + inputs.activateEnvironment, + `python=${inputs.pythonVersion}`, + ], + options ); } diff --git a/src/types.ts b/src/types.ts index b649955a..0c38358d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,6 +27,27 @@ export interface ILocalInstallerOpts { arch?: string; } +/** + * Special case `dependencies` member for pip in `environment.yml` + */ +export interface IPipSpec { + pip: string[]; +} + +/** + * Any valid member of `dependencies` in `environment.yml`. + */ +export type TYamlDependencies = (string | IPipSpec)[]; + +/** + * A (partial) `environment.yml` + */ +export interface IEnvironment { + name?: string; + channels?: string[]; + dependencies?: TYamlDependencies; +} + /** * A subset of the .condarc file options available as action inputs * https://docs.conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html @@ -63,3 +84,21 @@ export interface IActionInputs { readonly pythonVersion: string; readonly removeProfiles: string; } + +/** + * Options that may change during the course of discovery/installation/configuration + */ +export interface IDynamicOptions { + useBundled: boolean; + useMamba: boolean; + envSpec?: IEnvSpec; + condaConfig: TCondaConfig; +} + +/** + * File contents describing an environment + */ +export interface IEnvSpec { + yaml?: IEnvironment; + explicit?: string; +} diff --git a/src/vars.ts b/src/vars.ts index 5e77750f..41818253 100644 --- a/src/vars.ts +++ b/src/vars.ts @@ -3,17 +3,20 @@ import * as path from "path"; import * as core from "@actions/core"; import { minicondaPath } from "./conda"; +import * as types from "./types"; /** * Add Conda executable to PATH */ -export async function setVariables(useBundled: boolean): Promise { +export async function setVariables( + options: types.IDynamicOptions +): Promise { // Set environment variables - const condaBin: string = path.join(minicondaPath(useBundled), "condabin"); - const conda: string = minicondaPath(useBundled); + const condaBin: string = path.join(minicondaPath(options), "condabin"); + const conda: string = minicondaPath(options); core.info(`Add "${condaBin}" to PATH`); core.addPath(condaBin); - if (!useBundled) { + if (!options.useBundled) { core.info(`Set 'CONDA="${conda}"'`); core.exportVariable("CONDA", conda); }