diff --git a/.github/workflows/example-10.yml b/.github/workflows/example-10.yml index bbbdb1f4..73fc859a 100644 --- a/.github/workflows/example-10.yml +++ b/.github/workflows/example-10.yml @@ -82,8 +82,24 @@ jobs: mamba-version: ${{ matrix.mamba-version }} use-mamba: true python-version: ${{ matrix.python-version }} + clean-patched-environment-file: false + - run: | mamba info mamba list python -VV printenv | sort + - name: verify unpatched environment.yml is reported and not cleaned + if: contains(matrix.environment-file, '.yml') && !matrix.python-version + run: | + set -eux + ls '${{ steps.setup-miniconda.outputs.environment-file }}' + ls '${{ matrix.environment-file }}' + diff -s '${{ steps.setup-miniconda.outputs.environment-file }}' '${{ matrix.environment-file }}' | grep 'are identical' + - name: verify patched output is reported, correct, and not cleaned + if: contains(matrix.environment-file, '.yml') && matrix.python-version + run: | + set -eux + if [ "$(diff '${{ steps.setup-miniconda.outputs.environment-file }}' '${{ matrix.environment-file }}' | grep -c 'python=${{ matrix.python-version }}')" -ge 1 ] ; then echo ok ; else exit 1 ; fi + python --version | grep "Python ${{ matrix.python-version }}" + awk '/- conda-forge/,/- defaults/' '${{ steps.setup-miniconda.outputs.environment-file }}' diff --git a/CHANGELOG.md b/CHANGELOG.md index 628c8eb4..ad432554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # CHANGELOG +## [v2.1.1] (unreleased) + +### Features + +- [#163] leaves the patched `setup-miniconda-patched-{environment.yml}` in-place + if `clean-patched-environment-file: false` is given (otherwise cleans up after + itself) +- [#163] adds action outputs `environment-file`, `environment-file-content` and + `environment-file-was-patched` + +### Fixes + +- [#161] restores proper ordering of `channels` when `environment-file` is + patched +- [#163] if necessary, writes `setup-miniconda-patched-environment.yml` to the + same location to work with relative paths, e.g. `pip: ["-r requirements.txt"]` + +[v2.1.1]: https://github.com/conda-incubator/setup-miniconda/releases/tag/v2.1.0 +[#161]: https://github.com/conda-incubator/setup-miniconda/pull/161 +[#163]: https://github.com/conda-incubator/setup-miniconda/pull/163 + ## [v2.1.0] (2021-03-29) ### Features diff --git a/README.md b/README.md index 13611a62..f8f84946 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,15 @@ A `conda-build-version` or `mamba-version` may be provided to install into The base `condabin/` folder is added to `$PATH` and shell integration is initialized across all platforms. -By default, this action will then create, and activate an environment by one of: +By default, this action will then create, and _activate_, an environment by one +of: - creating a mostly-empty `test` environment, containing only the latest `python-version` and its dependencies - creating an `test` environment described in a given `environment-file`: - an `environment.yml`-like file (which can be patched with `python-version`) + - the patched environment will be cleaned up unless + `clean-patched-environment-file: false` is given - a [lockfile](#example-7-explicit-specification) This action correctly handles activation of environments and offers the @@ -118,9 +121,9 @@ activate the `base` environment. This encourages the practice of not using the `base` environment to install packages used for the workflow and leave the `base` environment untouched, with only `conda` (and/or `mamba`) in it. -## Inputs +## Inputs and outputs -For a full list of available inputs for this action see +For a full list of available _inputs_ and _outputs_ for this action see [action.yml](action.yml). ### Use a different environment name or path diff --git a/action.yml b/action.yml index b034a447..a8e01657 100644 --- a/action.yml +++ b/action.yml @@ -1,6 +1,25 @@ name: "Setup Miniconda" author: Gonzalo Peña-Castellanos (@goanpeca) description: "Set up Conda package and environment manager with Miniconda." + +runs: + using: "node12" + main: "dist/setup/index.js" + post: "dist/delete/index.js" + post-if: "success()" + +branding: + icon: "code" + color: "green" + +outputs: + environment-file: + description: "The full path to the environment-file used" + environment-file-content: + description: "The content of the environment-file used" + environment-file-was-patched: + description: "Whether a patched environment-file was made due to inputs" + inputs: installer-url: description: @@ -220,11 +239,8 @@ inputs: GitHub-hosted runners are "x86" and "x64". Default is "x64".' required: false default: "x64" -runs: - using: "node12" - main: "dist/setup/index.js" - post: "dist/delete/index.js" - post-if: "success()" -branding: - icon: "code" - color: "green" + clean-patched-environment-file: + description: + "Whether a patched environment-file (if created) should be cleaned" + required: false + default: "true" diff --git a/dist/delete/index.js b/dist/delete/index.js index 32ea07bf..825ee801 100644 --- a/dist/delete/index.js +++ b/dist/delete/index.js @@ -1161,7 +1161,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.PYTHON_SPEC = exports.WIN_PERMS_FOLDERS = exports.PROFILES = exports.ENV_VAR_CONDA_PKGS = exports.CONDA_CACHE_FOLDER = exports.CONDARC_PATH = exports.BOOTSTRAP_CONDARC = exports.FORCED_ERRORS = exports.IGNORED_WARNINGS = exports.MAMBA_SUBCOMMANDS = exports.KNOWN_EXTENSIONS = exports.BASE_ENV_NAMES = exports.MINIFORGE_DEFAULT_VERSION = exports.MINIFORGE_DEFAULT_VARIANT = exports.MINIFORGE_URL_PREFIX = exports.OS_NAMES = exports.MINIFORGE_ARCHITECTURES = exports.MINICONDA_ARCHITECTURES = exports.MINICONDA_BASE_URL = exports.IS_UNIX = exports.IS_LINUX = exports.IS_MAC = exports.IS_WINDOWS = exports.MINICONDA_DIR_PATH = void 0; +exports.OUTPUT_ENV_FILE_WAS_PATCHED = exports.OUTPUT_ENV_FILE_CONTENT = exports.OUTPUT_ENV_FILE_PATH = exports.PYTHON_SPEC = exports.WIN_PERMS_FOLDERS = exports.PROFILES = exports.ENV_VAR_CONDA_PKGS = exports.CONDA_CACHE_FOLDER = exports.CONDARC_PATH = exports.BOOTSTRAP_CONDARC = exports.FORCED_ERRORS = exports.IGNORED_WARNINGS = exports.MAMBA_SUBCOMMANDS = exports.KNOWN_EXTENSIONS = exports.BASE_ENV_NAMES = exports.MINIFORGE_DEFAULT_VERSION = exports.MINIFORGE_DEFAULT_VARIANT = exports.MINIFORGE_URL_PREFIX = exports.OS_NAMES = exports.MINIFORGE_ARCHITECTURES = exports.MINICONDA_ARCHITECTURES = exports.MINICONDA_BASE_URL = exports.IS_UNIX = exports.IS_LINUX = exports.IS_MAC = exports.IS_WINDOWS = exports.MINICONDA_DIR_PATH = void 0; const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); //----------------------------------------------------------------------- @@ -1288,6 +1288,18 @@ exports.WIN_PERMS_FOLDERS = [ * @see https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#package-match-specifications */ exports.PYTHON_SPEC = /^(.*::)?python($|\s\=\<\>\!\|)/i; +/** + * Output name for the effective environment-file path used. + */ +exports.OUTPUT_ENV_FILE_PATH = "environment-file"; +/** + * Output name for the effective environment-file file content used. + */ +exports.OUTPUT_ENV_FILE_CONTENT = "environment-file-content"; +/** + * Output name for whether the effective environment-file file was patched. + */ +exports.OUTPUT_ENV_FILE_WAS_PATCHED = "environment-file-was-patched"; /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index dba2a8cd..efc29f33 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -9358,7 +9358,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.PYTHON_SPEC = exports.WIN_PERMS_FOLDERS = exports.PROFILES = exports.ENV_VAR_CONDA_PKGS = exports.CONDA_CACHE_FOLDER = exports.CONDARC_PATH = exports.BOOTSTRAP_CONDARC = exports.FORCED_ERRORS = exports.IGNORED_WARNINGS = exports.MAMBA_SUBCOMMANDS = exports.KNOWN_EXTENSIONS = exports.BASE_ENV_NAMES = exports.MINIFORGE_DEFAULT_VERSION = exports.MINIFORGE_DEFAULT_VARIANT = exports.MINIFORGE_URL_PREFIX = exports.OS_NAMES = exports.MINIFORGE_ARCHITECTURES = exports.MINICONDA_ARCHITECTURES = exports.MINICONDA_BASE_URL = exports.IS_UNIX = exports.IS_LINUX = exports.IS_MAC = exports.IS_WINDOWS = exports.MINICONDA_DIR_PATH = void 0; +exports.OUTPUT_ENV_FILE_WAS_PATCHED = exports.OUTPUT_ENV_FILE_CONTENT = exports.OUTPUT_ENV_FILE_PATH = exports.PYTHON_SPEC = exports.WIN_PERMS_FOLDERS = exports.PROFILES = exports.ENV_VAR_CONDA_PKGS = exports.CONDA_CACHE_FOLDER = exports.CONDARC_PATH = exports.BOOTSTRAP_CONDARC = exports.FORCED_ERRORS = exports.IGNORED_WARNINGS = exports.MAMBA_SUBCOMMANDS = exports.KNOWN_EXTENSIONS = exports.BASE_ENV_NAMES = exports.MINIFORGE_DEFAULT_VERSION = exports.MINIFORGE_DEFAULT_VARIANT = exports.MINIFORGE_URL_PREFIX = exports.OS_NAMES = exports.MINIFORGE_ARCHITECTURES = exports.MINICONDA_ARCHITECTURES = exports.MINICONDA_BASE_URL = exports.IS_UNIX = exports.IS_LINUX = exports.IS_MAC = exports.IS_WINDOWS = exports.MINICONDA_DIR_PATH = void 0; const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); //----------------------------------------------------------------------- @@ -9485,6 +9485,18 @@ exports.WIN_PERMS_FOLDERS = [ * @see https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#package-match-specifications */ exports.PYTHON_SPEC = /^(.*::)?python($|\s\=\<\>\!\|)/i; +/** + * Output name for the effective environment-file path used. + */ +exports.OUTPUT_ENV_FILE_PATH = "environment-file"; +/** + * Output name for the effective environment-file file content used. + */ +exports.OUTPUT_ENV_FILE_CONTENT = "environment-file-content"; +/** + * Output name for whether the effective environment-file file was patched. + */ +exports.OUTPUT_ENV_FILE_WAS_PATCHED = "environment-file-was-patched"; /***/ }), @@ -13633,6 +13645,7 @@ function parseInputs() { always_yes: "true", changeps1: "false", }), + cleanPatchedEnvironmentFile: core.getInput("clean-patched-environment-file"), }); const errors = RULES.reduce((errors, rule) => { const msg = rule(inputs, inputs.condaConfig); @@ -15871,6 +15884,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureExplicit = void 0; const conda = __importStar(__webpack_require__(259)); +const outputs = __importStar(__webpack_require__(405)); /** * Install an environment from an explicit file generated `conda list --explicit` * or `conda-lock` @@ -15879,9 +15893,13 @@ exports.ensureExplicit = { label: "conda create (from explicit)", provides: (inputs, options) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; return !!((_b = (_a = options.envSpec) === null || _a === void 0 ? void 0 : _a.explicit) === null || _b === void 0 ? void 0 : _b.length); }), condaArgs: (inputs, options) => __awaiter(void 0, void 0, void 0, function* () { + var _c; if (inputs.pythonVersion) { throw Error(`'python-version: ${inputs.pythonVersion}' is incompatible with an explicit 'environmentFile`); } + if ((_c = options.envSpec) === null || _c === void 0 ? void 0 : _c.explicit) { + outputs.setEnvironmentFileOutputs(inputs.environmentFile, options.envSpec.explicit); + } return [ "create", ...conda.envCommandFlag(inputs), @@ -18015,7 +18033,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.setCacheVariable = exports.setPathVariables = void 0; +exports.setEnvironmentFileOutputs = exports.setCacheVariable = exports.setPathVariables = void 0; /** * Modify environment variables and action outputs. */ @@ -18051,6 +18069,16 @@ function setCacheVariable(options) { }); } exports.setCacheVariable = setCacheVariable; +/** + * Export the effective environment-file path + */ +function setEnvironmentFileOutputs(envFile, envContent, patched = false) { + core.setOutput(constants.OUTPUT_ENV_FILE_PATH, path.resolve(envFile)); + core.setOutput(constants.OUTPUT_ENV_FILE_CONTENT, envContent); + core.setOutput(constants.OUTPUT_ENV_FILE_WAS_PATCHED, patched ? "true" : "false"); + core.saveState(constants.OUTPUT_ENV_FILE_WAS_PATCHED, patched); +} +exports.setEnvironmentFileOutputs = setEnvironmentFileOutputs; /***/ }), @@ -20558,6 +20586,18 @@ function setupMiniconda(inputs) { if (inputs.activateEnvironment) { yield core.group("Ensuring environment...", () => env.ensureEnvironment(inputs, options)); } + if (core.getState(constants.OUTPUT_ENV_FILE_WAS_PATCHED)) { + yield core.group("Maybe cleaning up patched environment-file...", () => __awaiter(this, void 0, void 0, function* () { + const patchedEnv = core.getState(constants.OUTPUT_ENV_FILE_PATH); + if (inputs.cleanPatchedEnvironmentFile === "true") { + fs.unlinkSync(patchedEnv); + core.info(`Cleaned ${patchedEnv}`); + } + else { + core.info(`Leaving ${patchedEnv} in place`); + } + })); + } core.info("setup-miniconda ran successfully"); }); } @@ -20617,13 +20657,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureYaml = void 0; const fs = __importStar(__webpack_require__(747)); -const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); const yaml = __importStar(__webpack_require__(414)); const core = __importStar(__webpack_require__(470)); const constants = __importStar(__webpack_require__(211)); const conda = __importStar(__webpack_require__(259)); const utils = __importStar(__webpack_require__(163)); +const outputs = __importStar(__webpack_require__(405)); /** * The current known providers of patches to `environment.yml` * @@ -20690,13 +20730,17 @@ exports.ensureYaml = { } if (patchesApplied.length) { const patchedYaml = yaml.safeDump(Object.assign(Object.assign({}, yamlData), { dependencies })); - envFile = path.join(os.tmpdir(), "environment-patched.yml"); + const origPath = path.resolve(inputs.environmentFile); + const origParent = path.dirname(origPath); + envFile = path.join(origParent, `setup-miniconda-patched-${path.basename(origPath)}`); core.info(`Making patched copy of 'environment-file: ${inputs.environmentFile}'`); - core.info(patchedYaml); + core.info(`Using: ${envFile}\n${patchedYaml}`); fs.writeFileSync(envFile, patchedYaml, "utf8"); + outputs.setEnvironmentFileOutputs(envFile, yaml.safeDump(patchedYaml), true); } else { core.info(`Using 'environment-file: ${inputs.environmentFile}' as-is`); + outputs.setEnvironmentFileOutputs(envFile, fs.readFileSync(inputs.environmentFile, "utf-8")); } return [ "env", diff --git a/etc/example-environment-no-name.yml b/etc/example-environment-no-name.yml index 227056d9..43c277aa 100644 --- a/etc/example-environment-no-name.yml +++ b/etc/example-environment-no-name.yml @@ -1,3 +1,6 @@ +channels: + - conda-forge + - defaults dependencies: - black - anaconda-client diff --git a/etc/example-environment.yml b/etc/example-environment.yml index faf9c889..25b62284 100644 --- a/etc/example-environment.yml +++ b/etc/example-environment.yml @@ -3,5 +3,7 @@ channels: - conda-forge - defaults dependencies: - - black - anaconda-client + - pip + - pip: + - -r requirements.txt diff --git a/etc/requirements.txt b/etc/requirements.txt new file mode 100644 index 00000000..2a792435 --- /dev/null +++ b/etc/requirements.txt @@ -0,0 +1 @@ +isort diff --git a/src/constants.ts b/src/constants.ts index 70e278ec..1e65b614 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -148,3 +148,18 @@ export const WIN_PERMS_FOLDERS = [ * @see https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#package-match-specifications */ export const PYTHON_SPEC = /^(.*::)?python($|\s\=\<\>\!\|)/i; + +/** + * Output name for the effective environment-file path used. + */ +export const OUTPUT_ENV_FILE_PATH = "environment-file"; + +/** + * Output name for the effective environment-file file content used. + */ +export const OUTPUT_ENV_FILE_CONTENT = "environment-file-content"; + +/** + * Output name for whether the effective environment-file file was patched. + */ +export const OUTPUT_ENV_FILE_WAS_PATCHED = "environment-file-was-patched"; diff --git a/src/env/explicit.ts b/src/env/explicit.ts index f6275f5f..5f16eea7 100644 --- a/src/env/explicit.ts +++ b/src/env/explicit.ts @@ -1,5 +1,6 @@ import * as types from "../types"; import * as conda from "../conda"; +import * as outputs from "../outputs"; /** * Install an environment from an explicit file generated `conda list --explicit` @@ -15,6 +16,13 @@ export const ensureExplicit: types.IEnvProvider = { ); } + if (options.envSpec?.explicit) { + outputs.setEnvironmentFileOutputs( + inputs.environmentFile, + options.envSpec.explicit + ); + } + return [ "create", ...conda.envCommandFlag(inputs), diff --git a/src/env/yaml.ts b/src/env/yaml.ts index a8e762f2..c562aa9d 100644 --- a/src/env/yaml.ts +++ b/src/env/yaml.ts @@ -1,5 +1,4 @@ import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import * as yaml from "js-yaml"; @@ -9,6 +8,7 @@ import * as types from "../types"; import * as constants from "../constants"; import * as conda from "../conda"; import * as utils from "../utils"; +import * as outputs from "../outputs"; /** * Describes whether and how a YAML env should be patched to add a specific package @@ -108,14 +108,28 @@ export const ensureYaml: types.IEnvProvider = { if (patchesApplied.length) { const patchedYaml = yaml.safeDump({ ...yamlData, dependencies }); - envFile = path.join(os.tmpdir(), "environment-patched.yml"); + const origPath = path.resolve(inputs.environmentFile); + const origParent = path.dirname(origPath); + envFile = path.join( + origParent, + `setup-miniconda-patched-${path.basename(origPath)}` + ); core.info( `Making patched copy of 'environment-file: ${inputs.environmentFile}'` ); - core.info(patchedYaml); + core.info(`Using: ${envFile}\n${patchedYaml}`); fs.writeFileSync(envFile, patchedYaml, "utf8"); + outputs.setEnvironmentFileOutputs( + envFile, + yaml.safeDump(patchedYaml), + true + ); } else { core.info(`Using 'environment-file: ${inputs.environmentFile}' as-is`); + outputs.setEnvironmentFileOutputs( + envFile, + fs.readFileSync(inputs.environmentFile, "utf-8") + ); } return [ diff --git a/src/input.ts b/src/input.ts index 08811625..89439a77 100644 --- a/src/input.ts +++ b/src/input.ts @@ -99,6 +99,9 @@ export async function parseInputs(): Promise { always_yes: "true", changeps1: "false", }), + cleanPatchedEnvironmentFile: core.getInput( + "clean-patched-environment-file" + ), }); const errors = RULES.reduce((errors, rule) => { diff --git a/src/outputs.ts b/src/outputs.ts index 20f0cb7f..a7a3d3f5 100644 --- a/src/outputs.ts +++ b/src/outputs.ts @@ -34,3 +34,20 @@ export async function setCacheVariable(options: types.IDynamicOptions) { await conda.condaCommand(["config", "--add", "pkgs_dirs", folder], options); core.exportVariable(constants.ENV_VAR_CONDA_PKGS, folder); } + +/** + * Export the effective environment-file path + */ +export function setEnvironmentFileOutputs( + envFile: string, + envContent: string, + patched = false +): void { + core.setOutput(constants.OUTPUT_ENV_FILE_PATH, path.resolve(envFile)); + core.setOutput(constants.OUTPUT_ENV_FILE_CONTENT, envContent); + core.setOutput( + constants.OUTPUT_ENV_FILE_WAS_PATCHED, + patched ? "true" : "false" + ); + core.saveState(constants.OUTPUT_ENV_FILE_WAS_PATCHED, patched); +} diff --git a/src/setup.ts b/src/setup.ts index c4de86e4..60412d08 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -87,6 +87,21 @@ async function setupMiniconda(inputs: types.IActionInputs): Promise { ); } + if (core.getState(constants.OUTPUT_ENV_FILE_WAS_PATCHED)) { + await core.group( + "Maybe cleaning up patched environment-file...", + async () => { + const patchedEnv = core.getState(constants.OUTPUT_ENV_FILE_PATH); + if (inputs.cleanPatchedEnvironmentFile === "true") { + fs.unlinkSync(patchedEnv); + core.info(`Cleaned ${patchedEnv}`); + } else { + core.info(`Leaving ${patchedEnv} in place`); + } + } + ); + } + core.info("setup-miniconda ran successfully"); } diff --git a/src/types.ts b/src/types.ts index 0b153d18..4c86afce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -86,6 +86,7 @@ export interface IActionInputs { readonly pythonVersion: string; readonly removeProfiles: string; readonly useMamba: string; + readonly cleanPatchedEnvironmentFile: string; } /**