-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
conda_internal.ts
181 lines (158 loc) · 6.54 KB
/
conda_internal.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import * as fs from 'fs';
import * as path from 'path';
import * as task from 'vsts-task-lib/task';
import { ToolRunner } from 'vsts-task-lib/toolrunner';
import { Platform } from './taskutil';
import { prependPathSafe } from './toolutil';
/**
* Whether `searchDir` has a `conda` executable for `platform`.
* @param searchDir Absolute path to a directory to look for a `conda` executable in.
* @param platform Platform whose executable type we want to use (i.e. `conda` vs `conda.exe`)
* @returns Whether `searchDir` has a `conda` executable for `platform`.
*/
function hasConda(searchDir: string, platform: Platform): boolean {
let conda = path.join(binaryDir(searchDir, platform), 'conda');
if (platform === Platform.Windows) {
conda += '.exe';
}
return fs.existsSync(conda) && fs.statSync(conda).isFile();
}
/**
* Get the platform-dependent path where binaries are located in an environment.
* Windows: environmentRoot\Scripts
* Linux / macOS: environmentRoot/bin
*/
function binaryDir(environmentRoot: string, platform: Platform): string {
if (platform === Platform.Windows) {
return path.join(environmentRoot, 'Scripts');
} else {
return path.join(environmentRoot, 'bin');
}
}
/**
* Run a tool with `sudo` on Linux and macOS
* Precondition: `toolName` executable is in PATH
*/
function sudo(toolName: string, platform: Platform): ToolRunner {
if (platform === Platform.Windows) {
return task.tool(toolName);
} else {
const toolPath = task.which(toolName);
return task.tool('sudo').line(toolPath);
}
}
/**
* Search the system for an existing Conda installation.
* This function will check, in order:
* - the `CONDA` environment variable
* - `PATH`
* - The directory where the agent will install Conda if missing
*/
export function findConda(platform: Platform): string | null {
const condaFromPath: string | undefined = task.which('conda');
if (condaFromPath) {
// On all platforms, the `conda` executable lives in a directory off the root of the installation
return path.dirname(path.dirname(condaFromPath));
}
const condaFromEnvironment: string | undefined = task.getVariable('CONDA');
if (condaFromEnvironment && hasConda(condaFromEnvironment, platform)) {
return condaFromEnvironment;
}
return null;
}
/**
* Add Conda's `python` and `conda` executables to PATH.
* Precondition: Conda is installed at `condaRoot`
* @param condaRoot Root directory or "prefix" of the Conda installation
* @param platform Platform for which Conda is installed
*/
export function prependCondaToPath(condaRoot: string, platform: Platform): void {
prependPathSafe(binaryDir(condaRoot, platform));
if (platform === Platform.Windows) {
// Windows: `python` lives in `condaRoot` and `conda` lives in `condaRoot\Scripts`
// Linux and macOS: `python` and `conda` both live in the `bin` directory
prependPathSafe(condaRoot);
}
}
/**
* Update the `conda` installation
* Precondition: `conda` executable is in PATH
*/
export async function updateConda(condaRoot: string, platform: Platform): Promise<void> {
try {
// Need to sudo since Miniconda is installed in /usr on our hosted Ubuntu 16.04 and macOS agents
const conda = sudo('conda', platform);
conda.line('update --name base conda --yes');
await conda.exec();
} catch (e) {
task.debug('Failed to update conda. This is best effort. Continuing ...');
}
}
/**
* Create a Conda environment by running `conda create`.
* Preconditions:
* `conda` executable is in PATH
* Agent user has write access to `environmentPath`
* @param environmentPath Absolute path of the directory in which to create the environment. Will be created if it does not exist.
* @param packageSpecs Optional list of Conda packages and versions to preinstall in the environment.
* @param otherOptions Optional list of other options to pass to the `conda create` command.
*/
export async function createEnvironment(environmentPath: string, packageSpecs?: string, otherOptions?: string): Promise<void> {
const conda = task.tool('conda');
conda.line(`create --quiet --prefix ${environmentPath} --mkdir --yes`);
if (packageSpecs) {
conda.line(packageSpecs);
}
if (otherOptions) {
conda.line(otherOptions);
}
try {
await conda.exec();
} catch (e) {
// vsts-task-lib 2.5.0: `ToolRunner` does not localize its error messages
throw new Error(task.loc('CreateFailed', environmentPath, e));
}
}
/**
* Manually activate the environment by setting the variables touched by `conda activate` and prepending the environment to PATH.
* This allows the environment to remain activated in subsequent build steps.
*/
export function activateEnvironment(environmentsDir: string, environmentName: string, platform: Platform): void {
const environmentPath = path.join(environmentsDir, environmentName);
prependCondaToPath(environmentPath, platform);
// If Conda ever changes the names of the environment variables it uses to find its environment, this task will break.
// For now we will assume these names are stable.
// If we ever get broken, we should write code to run the activation script, diff the environment before and after,
// and surface up the new environment variables as build variables.
task.setVariable('CONDA_DEFAULT_ENV', environmentName);
task.setVariable('CONDA_PREFIX', environmentPath);
}
/**
* Install the packages given by `packageSpecs` to the `base` environment.
*/
export async function installPackagesGlobally(packageSpecs: string, platform: Platform, otherOptions?: string): Promise<void> {
// Need to sudo since Miniconda is installed in /usr on our hosted Ubuntu 16.04 and macOS agents
const conda = sudo('conda', platform);
conda.line(`install ${packageSpecs} --quiet --yes`);
if (otherOptions) {
conda.line(otherOptions);
}
try {
await conda.exec();
} catch (e) {
// vsts-task-lib 2.5.0: `ToolRunner` does not localize its error messages
throw new Error(task.loc('InstallFailed', e));
}
}
/**
* Look up the path to the base environment and add its binary directory to PATH.
* Precondition: `conda` executable is in PATH
*/
export function addBaseEnvironmentToPath(platform: Platform): void {
const execResult = task.execSync('conda', 'info --base');
if (execResult.error) {
throw execResult.error;
}
const baseEnv = execResult.stdout.trim();
prependPathSafe(binaryDir(baseEnv, platform));
}