Skip to content

Commit

Permalink
move installer detection logic to implementation (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
bollwyvl authored Dec 22, 2020
1 parent 9ed7b04 commit 00ca3ac
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 217 deletions.
367 changes: 266 additions & 101 deletions dist/setup/index.js

Large diffs are not rendered by default.

44 changes: 27 additions & 17 deletions src/conda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import * as os from "os";
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 { execute } from "./utils";
import * as types from "./types";
import * as constants from "./constants";
import * as utils from "./utils";

/**
* Provide current location of miniconda or location where it will be installed
*/
export function minicondaPath(options: types.IDynamicOptions): string {
let condaPath: string = MINICONDA_DIR_PATH;
export function condaBasePath(options: types.IDynamicOptions): string {
let condaPath: string = constants.MINICONDA_DIR_PATH;
if (!options.useBundled) {
if (IS_MAC) {
if (constants.IS_MAC) {
condaPath = "/Users/runner/miniconda3";
} else {
condaPath += "3";
Expand All @@ -32,11 +32,11 @@ export function minicondaPath(options: types.IDynamicOptions): string {
* Provide cross platform location of conda/mamba executable
*/
export function condaExecutable(options: types.IDynamicOptions): string {
const dir: string = minicondaPath(options);
const dir: string = condaBasePath(options);
let condaExe: string;
let commandName: string;
commandName = options.useMamba ? "mamba" : "conda";
commandName = IS_WINDOWS ? commandName + ".bat" : commandName;
commandName = constants.IS_WINDOWS ? commandName + ".bat" : commandName;
condaExe = path.join(dir, "condabin", commandName);
return condaExe;
}
Expand All @@ -49,7 +49,17 @@ export async function condaCommand(
options: types.IDynamicOptions
): Promise<void> {
const command = [condaExecutable(options), ...cmd];
return await execute(command);
return await utils.execute(command);
}

/**
* Create a baseline .condarc
*/
export async function bootstrapConfig(): Promise<void> {
await fs.promises.writeFile(
constants.CONDARC_PATH,
constants.BOOTSTRAP_CONDARC
);
}

/**
Expand Down Expand Up @@ -112,29 +122,29 @@ export async function condaInit(

// Fix ownership of folders
if (options.useBundled) {
if (IS_MAC) {
if (constants.IS_MAC) {
core.startGroup("Fixing conda folders ownership");
const userName: string = process.env.USER as string;
await execute([
await utils.execute([
"sudo",
"chown",
"-R",
`${userName}:staff`,
minicondaPath(options),
condaBasePath(options),
]);
core.endGroup();
} else if (IS_WINDOWS) {
} else if (constants.IS_WINDOWS) {
for (let folder of [
"condabin/",
"Scripts/",
"shell/",
"etc/profile.d/",
"/Lib/site-packages/xonsh/",
]) {
ownPath = path.join(minicondaPath(options), folder);
ownPath = path.join(condaBasePath(options), folder);
if (fs.existsSync(ownPath)) {
core.startGroup(`Fixing ${folder} ownership`);
await execute(["takeown", "/f", ownPath, "/r", "/d", "y"]);
await utils.execute(["takeown", "/f", ownPath, "/r", "/d", "y"]);
core.endGroup();
}
}
Expand Down Expand Up @@ -170,20 +180,20 @@ export async function condaInit(
// Run conda init
for (let cmd of ["--all"]) {
// TODO: determine when it's safe to use mamba
await execute([
await utils.execute([
condaExecutable({ ...options, useMamba: false }),
"init",
cmd,
]);
}

// Rename files
if (IS_LINUX) {
if (constants.IS_LINUX) {
let source: string = "~/.bashrc".replace("~", os.homedir());
let dest: string = "~/.profile".replace("~", os.homedir());
core.info(`Renaming "${source}" to "${dest}"\n`);
await io.mv(source, dest);
} else if (IS_MAC) {
} else if (constants.IS_MAC) {
let source: string = "~/.bash_profile".replace("~", os.homedir());
let dest: string = "~/.profile".replace("~", os.homedir());
core.info(`Renaming "${source}" to "${dest}"\n`);
Expand Down
6 changes: 3 additions & 3 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as path from "path";

import * as core from "@actions/core";

import { minicondaPath, condaCommand } from "./conda";
import * as types from "./types";
import * as conda from "./conda";

/**
* Check if a given conda environment exists
Expand All @@ -14,7 +14,7 @@ export function environmentExists(
options: types.IDynamicOptions
): boolean {
const condaMetaPath: string = path.join(
minicondaPath(options),
conda.condaBasePath(options),
"envs",
inputs.activateEnvironment,
"conda-meta"
Expand All @@ -36,7 +36,7 @@ export async function createTestEnvironment(
) {
if (!environmentExists(inputs, options)) {
core.startGroup("Create test environment...");
await condaCommand(
await conda.condaCommand(
["create", "--name", inputs.activateEnvironment],
options
);
Expand Down
8 changes: 4 additions & 4 deletions src/installer/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import * as core from "@actions/core";
import * as io from "@actions/io";
import * as tc from "@actions/tool-cache";

import { minicondaPath } from "../conda";
import { execute } from "../utils";
import * as types from "../types";
import * as utils from "../utils";
import * as conda from "../conda";

/** Get the path for a locally-executable installer from cache, or as downloaded
*
Expand Down Expand Up @@ -90,7 +90,7 @@ export async function runInstaller(
installerPath: string,
options: types.IDynamicOptions
): Promise<void> {
const outputPath: string = minicondaPath(options);
const outputPath: string = conda.condaBasePath(options);
const installerExtension = path.extname(installerPath);
let command: string[];

Expand All @@ -116,5 +116,5 @@ export async function runInstaller(

core.info(`Install Command:\n\t${command}`);

return await execute(command);
return await utils.execute(command);
}
29 changes: 29 additions & 0 deletions src/installer/bundled-miniconda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as types from "../types";

/**
* Provide a path to the pre-bundled (but probably old) Miniconda base installation
*
* ### Note
* This is the "cheapest" provider: if miniconda is already on disk, it can be
* fastest to avoid the download/install and use what's already on the image.
*/
export const bundledMinicondaUser: types.IInstallerProvider = {
label: "use bundled Miniconda",
provides: async (inputs, options) => {
return (
inputs.minicondaVersion === "" &&
inputs.architecture === "x64" &&
inputs.installerUrl === ""
);
},
installerPath: async (inputs, options) => {
// no-op
return {
localInstallerPath: "",
options: {
...options,
useBundled: true,
},
};
},
};
50 changes: 35 additions & 15 deletions src/installer/download-miniconda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@ import * as tc from "@actions/tool-cache";

import getHrefs from "get-hrefs";

import { ensureLocalInstaller } from "./base";
import {
ARCHITECTURES,
IS_UNIX,
MINICONDA_BASE_URL,
OS_NAMES,
} from "../constants";

import * as types from "../types";
import * as constants from "../constants";

import * as base from "./base";

/**
* List available Miniconda versions
Expand All @@ -22,8 +17,10 @@ import * as types from "../types";
*/
async function minicondaVersions(arch: string): Promise<string[]> {
try {
let extension: string = IS_UNIX ? "sh" : "exe";
const downloadPath: string = await tc.downloadTool(MINICONDA_BASE_URL);
let extension: string = constants.IS_UNIX ? "sh" : "exe";
const downloadPath: string = await tc.downloadTool(
constants.MINICONDA_BASE_URL
);
const content: string = fs.readFileSync(downloadPath, "utf8");
let hrefs: string[] = getHrefs(content);
hrefs = hrefs.filter((item: string) => item.startsWith("/Miniconda3"));
Expand All @@ -50,13 +47,13 @@ export async function downloadMiniconda(
inputs: types.IActionInputs
): Promise<string> {
// Check valid arch
const arch: string = ARCHITECTURES[inputs.architecture];
const arch: string = constants.ARCHITECTURES[inputs.architecture];
if (!arch) {
throw new Error(`Invalid arch "${inputs.architecture}"!`);
}

let extension: string = IS_UNIX ? "sh" : "exe";
let osName: string = OS_NAMES[process.platform];
let extension: string = constants.IS_UNIX ? "sh" : "exe";
let osName: string = constants.OS_NAMES[process.platform];
const minicondaInstallerName: string = `Miniconda${pythonMajorVersion}-${inputs.minicondaVersion}-${osName}-${arch}.${extension}`;
core.info(minicondaInstallerName);

Expand All @@ -70,10 +67,33 @@ export async function downloadMiniconda(
}
}

return await ensureLocalInstaller({
url: MINICONDA_BASE_URL + minicondaInstallerName,
return await base.ensureLocalInstaller({
url: constants.MINICONDA_BASE_URL + minicondaInstallerName,
tool: `Miniconda${pythonMajorVersion}`,
version: inputs.minicondaVersion,
arch: arch,
});
}

/**
* Provide a path to a Miniconda downloaded from repo.anaconda.com.
*
* ### Note
* Uses the well-known structure of the repo.anaconda.com to resolve and download
* a particular Miniconda installer.
*/
export const minicondaDownloader: types.IInstallerProvider = {
label: "download Miniconda",
provides: async (inputs, options) => {
return inputs.minicondaVersion !== "" && inputs.installerUrl === "";
},
installerPath: async (inputs, options) => {
return {
localInstallerPath: await downloadMiniconda(3, inputs),
options: {
...options,
useBundled: false,
},
};
},
};
29 changes: 22 additions & 7 deletions src/installer/download-url.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { ensureLocalInstaller } from "./base";
import * as types from "../types";

import * as base from "./base";

/**
* @param url A URL for a file with the CLI of a `constructor`-built artifact
* Provide a path to a `constructor`-compatible installer downloaded from
* any URL, including `file://` URLs.
*
* ### Note
* The entire local URL is used as the cache key.
*/
export async function downloadCustomInstaller(
inputs: types.IActionInputs
): Promise<string> {
return await ensureLocalInstaller({ url: inputs.installerUrl });
}
export const urlDownloader: types.IInstallerProvider = {
label: "download a custom installer by URL",
provides: async (inputs, options) => !!inputs.installerUrl,
installerPath: async (inputs, options) => {
return {
localInstallerPath: await base.ensureLocalInstaller({
url: inputs.installerUrl,
}),
options: {
...options,
useBundled: false,
},
};
},
};
Loading

0 comments on commit 00ca3ac

Please sign in to comment.