Address mamba v2 incompatibilities (#368)

* look for mamba in more places and guarantee existence

* parameterize mamba versions

* debug locations

* pass options

* Pass MAMBA_ROOT_PREFIX for mamba v2

* mamba v2 doesn't create env with update

* copy to condabin?

* use basename

* fix extension

* fix path under Windows

* extra verbose

* is it pip?

* it wasn't pip

* do not compile pyc on win

* temporarily remove pip section

* bash wrapper goes in condabin

* compile

* debug a bit

* fix workflow

* amend paths

* mark as executable?

* remove which?

* debug and disablew a few workflows

* activate before forward?

* slashes?

* refactor forwarders

* bash forwarder only on 1.x

* fwd via cmd?

* which

* move to the bottom

* this one too

* fix executable names

* revert

* clean up a bit

* mark as generated

* restore bash forwarder

* hide from local diff too

* do we still need MAMBA_COMPILE_PYC?
This commit is contained in:
jaimergp
2024-10-04 00:44:50 +02:00
committed by GitHub
parent 8f65dda967
commit 899c78d77f
8 changed files with 228 additions and 64 deletions
+2 -1
View File
@@ -1 +1,2 @@
*.js -diff
dist/setup/index.js linguist-generated=true -diff
dist/delete/index.js linguist-generated=true -diff
+7 -4
View File
@@ -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'
Generated Vendored
+2 -1
View File
@@ -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) {
Generated Vendored
+102 -28
View File
@@ -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) {
+54 -10
View File
@@ -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
const contents = `bash.exe -c "exec '${mambaBat}' $*" || exit 1`;
fs.writeFileSync(mambaBat.slice(0, -4), contents);
core.info(`... wrote ${mambaBat}:\n${contents}`);
@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}`);
}
}
},
};
+47 -11
View File
@@ -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<void> {
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);
}
/**
+12 -8
View File
@@ -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];
},
};
+2 -1
View File
@@ -21,7 +21,7 @@ export function isBaseEnv(envName: string) {
/**
* Run exec.exec with error handling
*/
export async function execute(command: string[]): Promise<void> {
export async function execute(command: string[], env = {}): Promise<void> {
let options: exec.ExecOptions = {
errStream: new stream.Writable(),
listeners: {
@@ -44,6 +44,7 @@ export async function execute(command: string[]): Promise<void> {
core.warning(stringData);
},
},
env: { ...process.env, ...env },
};
const rc = await exec.exec(command[0], command.slice(1), options);