diff --git a/.gitattributes b/.gitattributes index fdced425..58223050 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -*.js -diff +dist/setup/index.js linguist-generated=true -diff +dist/delete/index.js linguist-generated=true -diff diff --git a/.github/workflows/example-6.yml b/.github/workflows/example-6.yml index febfd613..8bd0b677 100644 --- a/.github/workflows/example-6.yml +++ b/.github/workflows/example-6.yml @@ -24,19 +24,22 @@ jobs: if: (github.event_name == 'schedule' && github.repository == 'conda-incubator/setup-miniconda') || (github.event_name != 'schedule') - name: Ex6 (${{ matrix.os }}) + name: + Ex6 (${{ matrix.os }}, mamba ${{ matrix.mamba-version }}, ${{ + matrix.activate-environment }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: ["ubuntu-latest", "macos-latest", "windows-latest"] + mamba-version: ["1.5.10", "2"] include: - os: ubuntu-latest activate-environment: anaconda-client-env - os: macos-latest activate-environment: /tmp/anaconda-client-env - os: windows-latest - activate-environment: c:\ace + activate-environment: C:\ace defaults: run: shell: bash -el {0} @@ -47,7 +50,7 @@ jobs: with: miniforge-variant: Miniforge3 python-version: "3.11" - mamba-version: "*" + mamba-version: ${{ matrix.mamba-version }} channels: conda-forge,nodefaults channel-priority: true activate-environment: ${{ matrix.activate-environment }} @@ -69,7 +72,7 @@ jobs: - name: Windows, .bat, from Cmd shell: cmd /C call {0} if: matrix.os == 'windows-latest' - run: mamba --version + run: mamba.bat --version - name: Windows, no .bat, from Cmd shell: cmd /C call {0} if: matrix.os == 'windows-latest' diff --git a/dist/delete/index.js b/dist/delete/index.js index 4dbd3cba..3aced3b0 100644 --- a/dist/delete/index.js +++ b/dist/delete/index.js @@ -26473,7 +26473,7 @@ exports.isBaseEnv = isBaseEnv; /** * Run exec.exec with error handling */ -function execute(command) { +function execute(command, env = {}) { return __awaiter(this, void 0, void 0, function* () { let options = { errStream: new stream.Writable(), @@ -26497,6 +26497,7 @@ function execute(command) { core.warning(stringData); }, }, + env: Object.assign(Object.assign({}, process.env), env), }; const rc = yield exec.exec(command[0], command.slice(1), options); if (rc !== 0) { diff --git a/dist/setup/index.js b/dist/setup/index.js index 4f209440..500406ae 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -47043,6 +47043,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge Object.defineProperty(exports, "__esModule", ({ value: true })); exports.updateMamba = void 0; const fs = __importStar(__nccwpck_require__(7147)); +const path = __importStar(__nccwpck_require__(1017)); const core = __importStar(__nccwpck_require__(2186)); const constants = __importStar(__nccwpck_require__(9042)); const utils = __importStar(__nccwpck_require__(1314)); @@ -47060,16 +47061,57 @@ exports.updateMamba = { }; }), postInstall: (inputs, options) => __awaiter(void 0, void 0, void 0, function* () { - if (!constants.IS_WINDOWS) { - core.info("`mamba` is already executable"); - return; + const mambaExec = conda.condaExecutable(options); + const condabinLocation = path.join(conda.condaBasePath(options), "condabin", path.basename(mambaExec)); + if (constants.IS_UNIX) { + if (!fs.existsSync(condabinLocation)) { + // This is mamba 2.x with only $PREFIX/bin/mamba, + // we just need a symlink in condabin + core.info(`Symlinking ${mambaExec} to ${condabinLocation}...`); + fs.symlinkSync(mambaExec, condabinLocation); + } + } + else { + core.info(`Creating bash wrapper for 'mamba'...`); + const mambaBat = condabinLocation.slice(0, -4) + ".bat"; + // Add bat-less forwarder for bash users on Windows + const forwarderContents = `cmd.exe /C CALL "${mambaBat}" $* || exit 1`; + fs.writeFileSync(condabinLocation.slice(0, -4), forwarderContents); + core.info(`... wrote ${mambaExec.slice(0, -4)}:\n${forwarderContents}`); + if (!fs.existsSync(mambaBat)) { + // This is Windows and mamba 2.x, we need a mamba.bat like 1.x used to have + const contents = ` +@REM Copyright (C) 2012 Anaconda, Inc +@REM SPDX-License-Identifier: BSD-3-Clause + +@REM echo _CE_CONDA is %_CE_CONDA% +@REM echo _CE_M is %_CE_M% +@REM echo CONDA_EXE is %CONDA_EXE% + +@IF NOT DEFINED _CE_CONDA ( + @SET _CE_M= + @SET "CONDA_EXE=%~dp0..\\Scripts\\conda.exe" +) +@IF [%1]==[activate] "%~dp0_conda_activate" %* +@IF [%1]==[deactivate] "%~dp0_conda_activate" %* + +@SET MAMBA_EXES="%~dp0..\\Library\\bin\\mamba.exe" +@CALL %MAMBA_EXES% %* + +@IF %errorlevel% NEQ 0 EXIT /B %errorlevel% + +@IF [%1]==[install] "%~dp0_conda_activate" reactivate +@IF [%1]==[update] "%~dp0_conda_activate" reactivate +@IF [%1]==[upgrade] "%~dp0_conda_activate" reactivate +@IF [%1]==[remove] "%~dp0_conda_activate" reactivate +@IF [%1]==[uninstall] "%~dp0_conda_activate" reactivate + +@EXIT /B %errorlevel%`; + core.info(`Creating .bat wrapper for 'mamba 2.x'...`); + fs.writeFileSync(mambaBat, contents); + core.info(`... wrote ${mambaBat}`); + } } - core.info("Creating bash wrapper for `mamba`..."); - // Add bat-less forwarder for bash users on Windows - const mambaBat = conda.condaExecutable(options).replace(/\\/g, "/"); - const contents = `bash.exe -c "exec '${mambaBat}' $*" || exit 1`; - fs.writeFileSync(mambaBat.slice(0, -4), contents); - core.info(`... wrote ${mambaBat}:\n${contents}`); }), }; @@ -47177,7 +47219,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.condaInit = exports.applyCondaConfiguration = exports.copyConfig = exports.bootstrapConfig = exports.condaCommand = exports.isMambaInstalled = exports.condaExecutable = exports.envCommandFlag = exports.condaBasePath = void 0; +exports.condaInit = exports.applyCondaConfiguration = exports.copyConfig = exports.bootstrapConfig = exports.condaCommand = exports.isMambaInstalled = exports.condaExecutable = exports.condaExecutableLocations = exports.envCommandFlag = exports.condaBasePath = void 0; const fs = __importStar(__nccwpck_require__(7147)); const path = __importStar(__nccwpck_require__(1017)); const os = __importStar(__nccwpck_require__(2037)); @@ -47213,25 +47255,47 @@ function envCommandFlag(inputs) { } exports.envCommandFlag = envCommandFlag; /** - * Provide cross platform location of conda/mamba executable + * Provide cross platform location of conda/mamba executable in condabin and bin */ -function condaExecutable(options, subcommand) { +function condaExecutableLocations(options, subcommand) { const dir = condaBasePath(options); - let condaExe; + let condaExes = []; let commandName = "conda"; if (options.useMamba && (subcommand == null || constants.MAMBA_SUBCOMMANDS.includes(subcommand))) { commandName = "mamba"; } - commandName = constants.IS_WINDOWS ? commandName + ".bat" : commandName; - condaExe = path.join(dir, "condabin", commandName); - return condaExe; + condaExes.push(path.join(dir, "condabin", constants.IS_WINDOWS ? commandName + ".bat" : commandName)); + if (constants.IS_WINDOWS) { + condaExes.push(path.join(dir, "Library", "bin", commandName + ".exe")); + } + else { + condaExes.push(path.join(dir, "bin", commandName)); + } + return condaExes; +} +exports.condaExecutableLocations = condaExecutableLocations; +/** + * Return existing conda or mamba executable + */ +function condaExecutable(options, subcommand) { + const locations = condaExecutableLocations(options, subcommand); + for (const exe of locations) { + if (fs.existsSync(exe)) + return exe; + } + throw Error(`No existing ${options.useMamba ? "mamba" : "conda"} executable found at any of ${locations}`); } exports.condaExecutable = condaExecutable; -/** Detect the presence of mamba */ +/** + * Detect the presence of mamba + */ function isMambaInstalled(options) { - const mamba = condaExecutable(Object.assign(Object.assign({}, options), { useMamba: true })); - return fs.existsSync(mamba); + for (const exe of condaExecutableLocations(Object.assign(Object.assign({}, options), { useMamba: true }))) { + if (fs.existsSync(exe)) + return true; + } + return false; } exports.isMambaInstalled = isMambaInstalled; /** @@ -47240,7 +47304,11 @@ exports.isMambaInstalled = isMambaInstalled; function condaCommand(cmd, options) { return __awaiter(this, void 0, void 0, function* () { const command = [condaExecutable(options, cmd[0]), ...cmd]; - return yield utils.execute(command); + let env = {}; + if (options.useMamba) { + env.MAMBA_ROOT_PREFIX = condaBasePath(options); + } + return yield utils.execute(command, env); }); } exports.condaCommand = condaCommand; @@ -48007,13 +48075,18 @@ exports.ensureYaml = { core.info(`Using 'environment-file: ${inputs.environmentFile}' as-is`); outputs.setEnvironmentFileOutputs(envFile, fs.readFileSync(inputs.environmentFile, "utf-8")); } - return [ - "env", - "update", - ...conda.envCommandFlag(inputs), - "--file", - envFile, - ]; + const [flag, nameOrPath] = conda.envCommandFlag(inputs); + let subcommand; + if (options.useMamba) { + const envPath = flag === "--name" + ? path.join(conda.condaBasePath(options), "envs", nameOrPath) + : nameOrPath; + subcommand = fs.existsSync(envPath) ? "update" : "create"; + } + else { + subcommand = "update"; + } + return ["env", subcommand, flag, nameOrPath, "--file", envFile]; }), }; @@ -49009,7 +49082,7 @@ exports.isBaseEnv = isBaseEnv; /** * Run exec.exec with error handling */ -function execute(command) { +function execute(command, env = {}) { return __awaiter(this, void 0, void 0, function* () { let options = { errStream: new stream.Writable(), @@ -49033,6 +49106,7 @@ function execute(command) { core.warning(stringData); }, }, + env: Object.assign(Object.assign({}, process.env), env), }; const rc = yield exec.exec(command[0], command.slice(1), options); if (rc !== 0) { diff --git a/src/base-tools/update-mamba.ts b/src/base-tools/update-mamba.ts index d866eec9..feb40321 100644 --- a/src/base-tools/update-mamba.ts +++ b/src/base-tools/update-mamba.ts @@ -1,4 +1,5 @@ import * as fs from "fs"; +import * as path from "path"; import * as core from "@actions/core"; @@ -23,16 +24,59 @@ export const updateMamba: types.IToolProvider = { }; }, postInstall: async (inputs, options) => { - if (!constants.IS_WINDOWS) { - core.info("`mamba` is already executable"); - return; - } - core.info("Creating bash wrapper for `mamba`..."); - // Add bat-less forwarder for bash users on Windows - const mambaBat = conda.condaExecutable(options).replace(/\\/g, "/"); + const mambaExec = conda.condaExecutable(options); + const condabinLocation = path.join( + conda.condaBasePath(options), + "condabin", + path.basename(mambaExec), + ); + if (constants.IS_UNIX) { + if (!fs.existsSync(condabinLocation)) { + // This is mamba 2.x with only $PREFIX/bin/mamba, + // we just need a symlink in condabin + core.info(`Symlinking ${mambaExec} to ${condabinLocation}...`); + fs.symlinkSync(mambaExec, condabinLocation); + } + } else { + core.info(`Creating bash wrapper for 'mamba'...`); + const mambaBat = condabinLocation.slice(0, -4) + ".bat"; + // Add bat-less forwarder for bash users on Windows + const forwarderContents = `cmd.exe /C CALL "${mambaBat}" $* || exit 1`; + fs.writeFileSync(condabinLocation.slice(0, -4), forwarderContents); + core.info(`... wrote ${mambaExec.slice(0, -4)}:\n${forwarderContents}`); + if (!fs.existsSync(mambaBat)) { + // This is Windows and mamba 2.x, we need a mamba.bat like 1.x used to have + const contents = ` +@REM Copyright (C) 2012 Anaconda, Inc +@REM SPDX-License-Identifier: BSD-3-Clause + +@REM echo _CE_CONDA is %_CE_CONDA% +@REM echo _CE_M is %_CE_M% +@REM echo CONDA_EXE is %CONDA_EXE% + +@IF NOT DEFINED _CE_CONDA ( + @SET _CE_M= + @SET "CONDA_EXE=%~dp0..\\Scripts\\conda.exe" +) +@IF [%1]==[activate] "%~dp0_conda_activate" %* +@IF [%1]==[deactivate] "%~dp0_conda_activate" %* + +@SET MAMBA_EXES="%~dp0..\\Library\\bin\\mamba.exe" +@CALL %MAMBA_EXES% %* - const contents = `bash.exe -c "exec '${mambaBat}' $*" || exit 1`; - fs.writeFileSync(mambaBat.slice(0, -4), contents); - core.info(`... wrote ${mambaBat}:\n${contents}`); +@IF %errorlevel% NEQ 0 EXIT /B %errorlevel% + +@IF [%1]==[install] "%~dp0_conda_activate" reactivate +@IF [%1]==[update] "%~dp0_conda_activate" reactivate +@IF [%1]==[upgrade] "%~dp0_conda_activate" reactivate +@IF [%1]==[remove] "%~dp0_conda_activate" reactivate +@IF [%1]==[uninstall] "%~dp0_conda_activate" reactivate + +@EXIT /B %errorlevel%`; + core.info(`Creating .bat wrapper for 'mamba 2.x'...`); + fs.writeFileSync(mambaBat, contents); + core.info(`... wrote ${mambaBat}`); + } + } }, }; diff --git a/src/conda.ts b/src/conda.ts index 3cf527a9..2359d56e 100644 --- a/src/conda.ts +++ b/src/conda.ts @@ -40,14 +40,14 @@ export function envCommandFlag(inputs: types.IActionInputs): string[] { } /** - * Provide cross platform location of conda/mamba executable + * Provide cross platform location of conda/mamba executable in condabin and bin */ -export function condaExecutable( +export function condaExecutableLocations( options: types.IDynamicOptions, subcommand?: string, -): string { +): string[] { const dir: string = condaBasePath(options); - let condaExe: string; + let condaExes: string[] = []; let commandName = "conda"; if ( options.useMamba && @@ -55,15 +55,47 @@ export function condaExecutable( ) { commandName = "mamba"; } - commandName = constants.IS_WINDOWS ? commandName + ".bat" : commandName; - condaExe = path.join(dir, "condabin", commandName); - return condaExe; + condaExes.push( + path.join( + dir, + "condabin", + constants.IS_WINDOWS ? commandName + ".bat" : commandName, + ), + ); + if (constants.IS_WINDOWS) { + condaExes.push(path.join(dir, "Library", "bin", commandName + ".exe")); + } else { + condaExes.push(path.join(dir, "bin", commandName)); + } + return condaExes; } -/** Detect the presence of mamba */ +/** + * Return existing conda or mamba executable + */ +export function condaExecutable( + options: types.IDynamicOptions, + subcommand?: string, +) { + const locations = condaExecutableLocations(options, subcommand); + for (const exe of locations) { + if (fs.existsSync(exe)) return exe; + } + throw Error( + `No existing ${ + options.useMamba ? "mamba" : "conda" + } executable found at any of ${locations}`, + ); +} + +/** + * Detect the presence of mamba + */ export function isMambaInstalled(options: types.IDynamicOptions) { - const mamba = condaExecutable({ ...options, useMamba: true }); - return fs.existsSync(mamba); + for (const exe of condaExecutableLocations({ ...options, useMamba: true })) { + if (fs.existsSync(exe)) return true; + } + return false; } /** @@ -74,7 +106,11 @@ export async function condaCommand( options: types.IDynamicOptions, ): Promise { const command = [condaExecutable(options, cmd[0]), ...cmd]; - return await utils.execute(command); + let env: { [key: string]: string } = {}; + if (options.useMamba) { + env.MAMBA_ROOT_PREFIX = condaBasePath(options); + } + return await utils.execute(command, env); } /** diff --git a/src/env/yaml.ts b/src/env/yaml.ts index b383a75b..b7e1ae85 100644 --- a/src/env/yaml.ts +++ b/src/env/yaml.ts @@ -127,13 +127,17 @@ export const ensureYaml: types.IEnvProvider = { fs.readFileSync(inputs.environmentFile, "utf-8"), ); } - - return [ - "env", - "update", - ...conda.envCommandFlag(inputs), - "--file", - envFile, - ]; + const [flag, nameOrPath] = conda.envCommandFlag(inputs); + let subcommand: string; + if (options.useMamba) { + const envPath = + flag === "--name" + ? path.join(conda.condaBasePath(options), "envs", nameOrPath) + : nameOrPath; + subcommand = fs.existsSync(envPath) ? "update" : "create"; + } else { + subcommand = "update"; + } + return ["env", subcommand, flag, nameOrPath, "--file", envFile]; }, }; diff --git a/src/utils.ts b/src/utils.ts index db010bc3..7232878c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,7 +21,7 @@ export function isBaseEnv(envName: string) { /** * Run exec.exec with error handling */ -export async function execute(command: string[]): Promise { +export async function execute(command: string[], env = {}): Promise { let options: exec.ExecOptions = { errStream: new stream.Writable(), listeners: { @@ -44,6 +44,7 @@ export async function execute(command: string[]): Promise { core.warning(stringData); }, }, + env: { ...process.env, ...env }, }; const rc = await exec.exec(command[0], command.slice(1), options);