Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6 of #107] move installer detection logic to implementation #126

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean @bollwyvl ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so with the bundled installer, we do nothing, as it's already installed... just update the options.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

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