diff --git a/.github/workflows/example-10.yml b/.github/workflows/example-10.yml index 1da773cf..3dc12837 100644 --- a/.github/workflows/example-10.yml +++ b/.github/workflows/example-10.yml @@ -46,6 +46,34 @@ jobs: python -VV printenv | sort + example-10-miniforge-installation-dir: + # prevent cronjobs from running on forks + if: + (github.event_name == 'schedule' && github.repository == + 'conda-incubator/setup-miniconda') || (github.event_name != 'schedule') + name: Ex10 (${{ matrix.os }}, Miniforge) + runs-on: ${{ matrix.os }}-latest + defaults: + run: + shell: bash -el {0} + strategy: + fail-fast: false + matrix: + os: ["ubuntu", "macos", "windows"] + steps: + - uses: actions/checkout@v4 + - uses: ./ + id: setup-miniconda + with: + environment-file: etc/example-environment.yml + miniforge-version: latest + installation-dir: ${{ github.workspace }}/miniforge-latest + - run: | + conda info + conda list + python -VV + printenv | sort + example-10-mambaforge: # NOTE: Mambaforge is now equivalent to Miniforge. # We are only testing this to make sure there's a smooth transition. diff --git a/README.md b/README.md index ff6909a1..e9c72640 100644 --- a/README.md +++ b/README.md @@ -521,6 +521,9 @@ jobs: In addition to `Miniforge3` with `conda`, `mamba` and `CPython`, you can also install `Miniforge-pypy3`, which replaces `CPython` with `PyPy. +> [!TIP] You can customize the installation directory via the `installation-dir` +> option. + ### Example 11: Alternative Architectures In addition to the default 64-bit builds of Miniconda, 32-bit versions are diff --git a/action.yml b/action.yml index 82079de5..a2c557d4 100644 --- a/action.yml +++ b/action.yml @@ -29,6 +29,11 @@ inputs: installers" required: false default: "" + installation-dir: + description: + "If provided, the installer will be installed in the given directory." + required: false + default: "" miniconda-version: description: "If provided, this version of Miniconda3 will be downloaded and installed. diff --git a/dist/setup/index.js b/dist/setup/index.js index b574b5a1..956f1f19 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -46861,7 +46861,7 @@ function installBaseTools(inputs, options) { } } if (tools.length) { - yield conda.condaCommand(["install", "--name", "base", ...tools], options); + yield conda.condaCommand(["install", "--name", "base", ...tools], inputs, options); // *Now* use the new options, as we may have a new conda/mamba with more supported // options that previously failed yield conda.applyCondaConfiguration(inputs, postInstallOptions); @@ -47061,8 +47061,8 @@ exports.updateMamba = { }; }), postInstall: (inputs, options) => __awaiter(void 0, void 0, void 0, function* () { - const mambaExec = conda.condaExecutable(options); - const condabinLocation = path.join(conda.condaBasePath(options), "condabin", path.basename(mambaExec)); + const mambaExec = conda.condaExecutable(inputs, options); + const condabinLocation = path.join(conda.condaBasePath(inputs, options), "condabin", path.basename(mambaExec)); if (constants.IS_UNIX) { if (!fs.existsSync(condabinLocation)) { // This is mamba 2.x with only $PREFIX/bin/mamba, @@ -47230,11 +47230,16 @@ const utils = __importStar(__nccwpck_require__(1314)); /** * Provide current location of miniconda or location where it will be installed */ -function condaBasePath(options) { +function condaBasePath(inputs, options) { let condaPath; if (options.useBundled) { condaPath = constants.MINICONDA_DIR_PATH; } + else if (inputs.installationDir) { + condaPath = constants.IS_WINDOWS + ? inputs.installationDir.replace("/", "\\") + : inputs.installationDir; + } else { condaPath = path.join(os.homedir(), "miniconda3"); } @@ -47257,8 +47262,8 @@ exports.envCommandFlag = envCommandFlag; /** * Provide cross platform location of conda/mamba executable in condabin and bin */ -function condaExecutableLocations(options, subcommand) { - const dir = condaBasePath(options); +function condaExecutableLocations(inputs, options, subcommand) { + const dir = condaBasePath(inputs, options); let condaExes = []; let commandName = "conda"; if (options.useMamba && @@ -47278,8 +47283,8 @@ exports.condaExecutableLocations = condaExecutableLocations; /** * Return existing conda or mamba executable */ -function condaExecutable(options, subcommand) { - const locations = condaExecutableLocations(options, subcommand); +function condaExecutable(inputs, options, subcommand) { + const locations = condaExecutableLocations(inputs, options, subcommand); for (const exe of locations) { if (fs.existsSync(exe)) return exe; @@ -47290,8 +47295,8 @@ exports.condaExecutable = condaExecutable; /** * Detect the presence of mamba */ -function isMambaInstalled(options) { - for (const exe of condaExecutableLocations(Object.assign(Object.assign({}, options), { useMamba: true }))) { +function isMambaInstalled(inputs, options) { + for (const exe of condaExecutableLocations(inputs, Object.assign(Object.assign({}, options), { useMamba: true }))) { if (fs.existsSync(exe)) return true; } @@ -47301,12 +47306,12 @@ exports.isMambaInstalled = isMambaInstalled; /** * Run Conda command */ -function condaCommand(cmd, options) { +function condaCommand(cmd, inputs, options) { return __awaiter(this, void 0, void 0, function* () { - const command = [condaExecutable(options, cmd[0]), ...cmd]; + const command = [condaExecutable(inputs, options, cmd[0]), ...cmd]; let env = {}; if (options.useMamba) { - env.MAMBA_ROOT_PREFIX = condaBasePath(options); + env.MAMBA_ROOT_PREFIX = condaBasePath(inputs, options); } return yield utils.execute(command, env); }); @@ -47362,13 +47367,13 @@ function applyCondaConfiguration(inputs, options) { continue; } core.info(`Adding channel '${channel}'`); - yield condaCommand(["config", "--add", "channels", channel], options); + yield condaCommand(["config", "--add", "channels", channel], inputs, options); } if (!channels.includes("defaults")) { if (removeDefaults) { core.info("Removing implicitly added 'defaults' channel"); try { - yield condaCommand(["config", "--remove", "channels", "defaults"], options); + yield condaCommand(["config", "--remove", "channels", "defaults"], inputs, options); } catch (err) { core.info("Removing defaults raised an error -- it was probably not present."); @@ -47387,15 +47392,15 @@ function applyCondaConfiguration(inputs, options) { } core.info(`${key}: ${value}`); try { - yield condaCommand(["config", "--set", key, value], options); + yield condaCommand(["config", "--set", key, value], inputs, options); } catch (err) { core.warning(err); } } // Log all configuration information - yield condaCommand(["config", "--show-sources"], options); - yield condaCommand(["config", "--show"], options); + yield condaCommand(["config", "--show-sources"], inputs, options); + yield condaCommand(["config", "--show"], inputs, options); }); } exports.applyCondaConfiguration = applyCondaConfiguration; @@ -47417,12 +47422,12 @@ function condaInit(inputs, options) { "chown", "-R", `${userName}:staff`, - condaBasePath(options), + condaBasePath(inputs, options), ]); } else if (constants.IS_WINDOWS) { for (let folder of constants.WIN_PERMS_FOLDERS) { - ownPath = path.join(condaBasePath(options), folder); + ownPath = path.join(condaBasePath(inputs, options), folder); if (fs.existsSync(ownPath)) { core.info(`Fixing ${folder} ownership`); yield utils.execute(["takeown", "/f", ownPath, "/r", "/d", "y"]); @@ -47447,7 +47452,7 @@ function condaInit(inputs, options) { } // Run conda init for (let cmd of ["--all"]) { - yield condaCommand(["init", cmd], options); + yield condaCommand(["init", cmd], inputs, options); } if (inputs.removeProfiles == "true") { // Rename files @@ -47868,7 +47873,7 @@ function ensureEnvironment(inputs, options) { if (yield provider.provides(inputs, options)) { core.info(`... will ${provider.label}.`); const args = yield provider.condaArgs(inputs, options); - return yield core.group(`Updating '${inputs.activateEnvironment}' env from ${provider.label}...`, () => conda.condaCommand(args, options)); + return yield core.group(`Updating '${inputs.activateEnvironment}' env from ${provider.label}...`, () => conda.condaCommand(args, inputs, options)); } } throw Error(`'activate-environment: ${inputs.activateEnvironment}' could not be created`); @@ -48104,7 +48109,7 @@ exports.ensureYaml = { let subcommand; if (options.useMamba) { const envPath = flag === "--name" - ? path.join(conda.condaBasePath(options), "envs", nameOrPath) + ? path.join(conda.condaBasePath(inputs, options), "envs", nameOrPath) : nameOrPath; subcommand = fs.existsSync(envPath) ? "update" : "create"; } @@ -48208,6 +48213,7 @@ function parseInputs() { condaVersion: core.getInput("conda-version"), environmentFile: core.getInput("environment-file"), installerUrl: core.getInput("installer-url"), + installationDir: core.getInput("installation-dir"), mambaVersion: core.getInput("mamba-version"), useMamba: core.getInput("use-mamba"), minicondaVersion: core.getInput("miniconda-version"), @@ -48824,7 +48830,7 @@ function runInstaller(installerPath, outputPath, inputs, options) { } yield utils.execute(command); // The installer may have provisioned `mamba` in `base`: use now if requested - const mambaInInstaller = conda.isMambaInstalled(options); + const mambaInInstaller = conda.isMambaInstalled(inputs, options); if (mambaInInstaller) { core.info("Mamba was found in the `base` env"); options = Object.assign(Object.assign({}, options), { mambaInInstaller, useMamba: mambaInInstaller && inputs.useMamba === "true" }); @@ -48887,10 +48893,10 @@ const utils = __importStar(__nccwpck_require__(1314)); /** * Add Conda executable to PATH environment variable */ -function setPathVariables(options) { +function setPathVariables(inputs, options) { return __awaiter(this, void 0, void 0, function* () { - const condaBin = path.join(conda.condaBasePath(options), "condabin"); - const condaPath = conda.condaBasePath(options); + const condaBin = path.join(conda.condaBasePath(inputs, options), "condabin"); + const condaPath = conda.condaBasePath(inputs, options); core.info(`Add "${condaBin}" to PATH`); core.addPath(condaBin); if (!options.useBundled) { @@ -48903,10 +48909,10 @@ exports.setPathVariables = setPathVariables; /** * Ensure the conda cache path is available as an environment variable */ -function setCacheVariable(options) { +function setCacheVariable(inputs, options) { return __awaiter(this, void 0, void 0, function* () { const folder = utils.cacheFolder(); - yield conda.condaCommand(["config", "--add", "pkgs_dirs", folder], options); + yield conda.condaCommand(["config", "--add", "pkgs_dirs", folder], inputs, options); core.exportVariable(constants.ENV_VAR_CONDA_PKGS, folder); }); } @@ -48987,7 +48993,7 @@ function setupMiniconda(inputs) { const installerInfo = yield core.group("Ensuring installer...", () => installer.getLocalInstallerPath(inputs, options)); // The desired installer may change the options options = Object.assign(Object.assign({}, options), installerInfo.options); - const basePath = conda.condaBasePath(options); + const basePath = conda.condaBasePath(inputs, options); if (installerInfo.localInstallerPath && !options.useBundled) { options = yield core.group("Running installer...", () => installer.runInstaller(installerInfo.localInstallerPath, basePath, inputs, options)); } @@ -48999,13 +49005,13 @@ function setupMiniconda(inputs) { 'Miniforge for you, add `miniconda-version: "latest"` or `miniforge-version: "latest"`, ' + "respectively, to the parameters for this action."); } - yield core.group("Setup environment variables...", () => outputs.setPathVariables(options)); + yield core.group("Setup environment variables...", () => outputs.setPathVariables(inputs, options)); if (inputs.condaConfigFile) { yield core.group("Copying condarc file...", () => conda.copyConfig(inputs)); } // For potential 'channels' that may alter configuration options.envSpec = yield core.group("Parsing environment...", () => env.getEnvSpec(inputs)); - yield core.group("Configuring conda package cache...", () => outputs.setCacheVariable(options)); + yield core.group("Configuring conda package cache...", () => outputs.setCacheVariable(inputs, options)); yield core.group("Applying initial configuration...", () => conda.applyCondaConfiguration(inputs, options)); yield core.group("Initializing conda shell integration...", () => conda.condaInit(inputs, options)); // New base tools may change options diff --git a/src/base-tools/index.ts b/src/base-tools/index.ts index 5d41a6bf..9ddecca4 100644 --- a/src/base-tools/index.ts +++ b/src/base-tools/index.ts @@ -55,7 +55,11 @@ export async function installBaseTools( } if (tools.length) { - await conda.condaCommand(["install", "--name", "base", ...tools], options); + await conda.condaCommand( + ["install", "--name", "base", ...tools], + inputs, + options, + ); // *Now* use the new options, as we may have a new conda/mamba with more supported // options that previously failed diff --git a/src/base-tools/update-mamba.ts b/src/base-tools/update-mamba.ts index feb40321..05c3d8d1 100644 --- a/src/base-tools/update-mamba.ts +++ b/src/base-tools/update-mamba.ts @@ -24,9 +24,9 @@ export const updateMamba: types.IToolProvider = { }; }, postInstall: async (inputs, options) => { - const mambaExec = conda.condaExecutable(options); + const mambaExec = conda.condaExecutable(inputs, options); const condabinLocation = path.join( - conda.condaBasePath(options), + conda.condaBasePath(inputs, options), "condabin", path.basename(mambaExec), ); diff --git a/src/conda.ts b/src/conda.ts index 2c46db36..3655f500 100644 --- a/src/conda.ts +++ b/src/conda.ts @@ -16,10 +16,17 @@ import * as utils from "./utils"; /** * Provide current location of miniconda or location where it will be installed */ -export function condaBasePath(options: types.IDynamicOptions): string { +export function condaBasePath( + inputs: types.IActionInputs, + options: types.IDynamicOptions, +): string { let condaPath: string; if (options.useBundled) { condaPath = constants.MINICONDA_DIR_PATH; + } else if (inputs.installationDir) { + condaPath = constants.IS_WINDOWS + ? inputs.installationDir.replace("/", "\\") + : inputs.installationDir; } else { condaPath = path.join(os.homedir(), "miniconda3"); } @@ -43,10 +50,11 @@ export function envCommandFlag(inputs: types.IActionInputs): string[] { * Provide cross platform location of conda/mamba executable in condabin and bin */ export function condaExecutableLocations( + inputs: types.IActionInputs, options: types.IDynamicOptions, subcommand?: string, ): string[] { - const dir: string = condaBasePath(options); + const dir: string = condaBasePath(inputs, options); let condaExes: string[] = []; let commandName = "conda"; if ( @@ -74,10 +82,11 @@ export function condaExecutableLocations( * Return existing conda or mamba executable */ export function condaExecutable( + inputs: types.IActionInputs, options: types.IDynamicOptions, subcommand?: string, ) { - const locations = condaExecutableLocations(options, subcommand); + const locations = condaExecutableLocations(inputs, options, subcommand); for (const exe of locations) { if (fs.existsSync(exe)) return exe; } @@ -91,8 +100,14 @@ export function condaExecutable( /** * Detect the presence of mamba */ -export function isMambaInstalled(options: types.IDynamicOptions) { - for (const exe of condaExecutableLocations({ ...options, useMamba: true })) { +export function isMambaInstalled( + inputs: types.IActionInputs, + options: types.IDynamicOptions, +) { + for (const exe of condaExecutableLocations(inputs, { + ...options, + useMamba: true, + })) { if (fs.existsSync(exe)) return true; } return false; @@ -103,12 +118,13 @@ export function isMambaInstalled(options: types.IDynamicOptions) { */ export async function condaCommand( cmd: string[], + inputs: types.IActionInputs, options: types.IDynamicOptions, ): Promise { - const command = [condaExecutable(options, cmd[0]), ...cmd]; + const command = [condaExecutable(inputs, options, cmd[0]), ...cmd]; let env: { [key: string]: string } = {}; if (options.useMamba) { - env.MAMBA_ROOT_PREFIX = condaBasePath(options); + env.MAMBA_ROOT_PREFIX = condaBasePath(inputs, options); } return await utils.execute(command, env); } @@ -175,7 +191,11 @@ export async function applyCondaConfiguration( continue; } core.info(`Adding channel '${channel}'`); - await condaCommand(["config", "--add", "channels", channel], options); + await condaCommand( + ["config", "--add", "channels", channel], + inputs, + options, + ); } if (!channels.includes("defaults")) { @@ -184,6 +204,7 @@ export async function applyCondaConfiguration( try { await condaCommand( ["config", "--remove", "channels", "defaults"], + inputs, options, ); } catch (err) { @@ -207,15 +228,15 @@ export async function applyCondaConfiguration( } core.info(`${key}: ${value}`); try { - await condaCommand(["config", "--set", key, value], options); + await condaCommand(["config", "--set", key, value], inputs, options); } catch (err) { core.warning(err as Error); } } // Log all configuration information - await condaCommand(["config", "--show-sources"], options); - await condaCommand(["config", "--show"], options); + await condaCommand(["config", "--show-sources"], inputs, options); + await condaCommand(["config", "--show"], inputs, options); } /** @@ -240,11 +261,11 @@ export async function condaInit( "chown", "-R", `${userName}:staff`, - condaBasePath(options), + condaBasePath(inputs, options), ]); } else if (constants.IS_WINDOWS) { for (let folder of constants.WIN_PERMS_FOLDERS) { - ownPath = path.join(condaBasePath(options), folder); + ownPath = path.join(condaBasePath(inputs, options), folder); if (fs.existsSync(ownPath)) { core.info(`Fixing ${folder} ownership`); await utils.execute(["takeown", "/f", ownPath, "/r", "/d", "y"]); @@ -270,7 +291,7 @@ export async function condaInit( // Run conda init for (let cmd of ["--all"]) { - await condaCommand(["init", cmd], options); + await condaCommand(["init", cmd], inputs, options); } if (inputs.removeProfiles == "true") { diff --git a/src/env/index.ts b/src/env/index.ts index 06db6ae5..8b188022 100644 --- a/src/env/index.ts +++ b/src/env/index.ts @@ -42,7 +42,7 @@ export async function ensureEnvironment( const args = await provider.condaArgs(inputs, options); return await core.group( `Updating '${inputs.activateEnvironment}' env from ${provider.label}...`, - () => conda.condaCommand(args, options), + () => conda.condaCommand(args, inputs, options), ); } } diff --git a/src/env/yaml.ts b/src/env/yaml.ts index b7e1ae85..79c0e3d1 100644 --- a/src/env/yaml.ts +++ b/src/env/yaml.ts @@ -132,7 +132,7 @@ export const ensureYaml: types.IEnvProvider = { if (options.useMamba) { const envPath = flag === "--name" - ? path.join(conda.condaBasePath(options), "envs", nameOrPath) + ? path.join(conda.condaBasePath(inputs, options), "envs", nameOrPath) : nameOrPath; subcommand = fs.existsSync(envPath) ? "update" : "create"; } else { diff --git a/src/input.ts b/src/input.ts index b75c7633..72a44512 100644 --- a/src/input.ts +++ b/src/input.ts @@ -87,6 +87,7 @@ export async function parseInputs(): Promise { condaVersion: core.getInput("conda-version"), environmentFile: core.getInput("environment-file"), installerUrl: core.getInput("installer-url"), + installationDir: core.getInput("installation-dir"), mambaVersion: core.getInput("mamba-version"), useMamba: core.getInput("use-mamba"), minicondaVersion: core.getInput("miniconda-version"), diff --git a/src/installer/index.ts b/src/installer/index.ts index 8ba30670..d7108090 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -85,7 +85,7 @@ export async function runInstaller( await utils.execute(command); // The installer may have provisioned `mamba` in `base`: use now if requested - const mambaInInstaller = conda.isMambaInstalled(options); + const mambaInInstaller = conda.isMambaInstalled(inputs, options); if (mambaInInstaller) { core.info("Mamba was found in the `base` env"); options = { diff --git a/src/outputs.ts b/src/outputs.ts index 16066f6b..93c41e22 100644 --- a/src/outputs.ts +++ b/src/outputs.ts @@ -14,10 +14,14 @@ import * as utils from "./utils"; * Add Conda executable to PATH environment variable */ export async function setPathVariables( + inputs: types.IActionInputs, options: types.IDynamicOptions, ): Promise { - const condaBin: string = path.join(conda.condaBasePath(options), "condabin"); - const condaPath: string = conda.condaBasePath(options); + const condaBin: string = path.join( + conda.condaBasePath(inputs, options), + "condabin", + ); + const condaPath: string = conda.condaBasePath(inputs, options); core.info(`Add "${condaBin}" to PATH`); core.addPath(condaBin); if (!options.useBundled) { @@ -29,9 +33,16 @@ export async function setPathVariables( /** * Ensure the conda cache path is available as an environment variable */ -export async function setCacheVariable(options: types.IDynamicOptions) { +export async function setCacheVariable( + inputs: types.IActionInputs, + options: types.IDynamicOptions, +) { const folder = utils.cacheFolder(); - await conda.condaCommand(["config", "--add", "pkgs_dirs", folder], options); + await conda.condaCommand( + ["config", "--add", "pkgs_dirs", folder], + inputs, + options, + ); core.exportVariable(constants.ENV_VAR_CONDA_PKGS, folder); } diff --git a/src/setup.ts b/src/setup.ts index 3ff1b0c5..be3376ef 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -34,7 +34,7 @@ async function setupMiniconda(inputs: types.IActionInputs): Promise { // The desired installer may change the options options = { ...options, ...installerInfo.options }; - const basePath = conda.condaBasePath(options); + const basePath = conda.condaBasePath(inputs, options); if (installerInfo.localInstallerPath && !options.useBundled) { options = await core.group("Running installer...", () => @@ -59,7 +59,7 @@ async function setupMiniconda(inputs: types.IActionInputs): Promise { } await core.group("Setup environment variables...", () => - outputs.setPathVariables(options), + outputs.setPathVariables(inputs, options), ); if (inputs.condaConfigFile) { @@ -72,7 +72,7 @@ async function setupMiniconda(inputs: types.IActionInputs): Promise { ); await core.group("Configuring conda package cache...", () => - outputs.setCacheVariable(options), + outputs.setCacheVariable(inputs, options), ); await core.group("Applying initial configuration...", () => diff --git a/src/types.ts b/src/types.ts index d505319d..f619134a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -80,6 +80,7 @@ export interface IActionInputs { readonly condaVersion: string; readonly environmentFile: string; readonly installerUrl: string; + readonly installationDir: string; readonly mambaVersion: string; readonly minicondaVersion: string; readonly miniforgeVariant: string;