# Design Document: mache.deploy ## Summary `mache.deploy` is a subpackage of `mache` that provides a unified, documented, and extensible mechanism for deploying combined **pixi** (conda packages) and **spack** environments for E3SM-supported software (e.g. Polaris, Compass, E3SM-Unified) on E3SM-supported HPC systems. The primary motivation is to replace the redundant, subtly divergent, and poorly documented deployment logic currently embedded independently in these packages with a single, shared implementation. This improves maintainability, ensures feature parity across target software, and provides a scalable model for future E3SM software with mixed pixi/spack dependencies. In this document, software such as Polaris or E3SM-Unified that uses `mache.deploy` is referred to as the **target software**. --- ## Requirements *Date last modified: Dec 29, 2025* *Contributors: Xylar Asay-Davis, Althea Denlinger* --- ### Requirement: A mechanism to begin deployment The target software must provide a user-facing entry point to begin deployment. This mechanism cannot depend on `mache` already being installed, because deployment is precisely how `mache` is introduced. **Design resolution** Each target software provides a small, stable `deploy.py` script at repository root. This script: - Parses command-line arguments defined declaratively - Downloads a standalone `bootstrap.py` script from the `mache` repository - Executes `bootstrap.py` to install pixi (if needed) and `mache` --- ### Requirement: A mechanism to install pixi If pixi is not already installed, the deployment process must be able to install it automatically. **Design resolution** The standalone `bootstrap.py` script (downloaded from `mache`) handles: - Installing pixi into a user-specified or inferred location - Using pixi in a non-interactive, non-login context (no reliance on shell init) This logic is intentionally duplicated only in `bootstrap.py`, which runs before `mache` is available. --- ### Requirement: A mechanism to install mache The target software must support installing: - A released version of `mache` from conda-forge (via pixi), or - A developer-specified fork and branch (for testing and development) **Design resolution** - `bootstrap.py` creates a minimal *bootstrap* pixi environment - `mache` is installed into that environment either: - via a pixi manifest that depends on `mache==` (from conda-forge), or - via cloning and installing from a fork/branch (installed into the pixi environment) --- ### Requirement: A way for target software to specify pixi packages The target software must define: - Which conda packages are installed (via pixi) - Version constraints - Variants across machines, compilers, MPI, etc. **Design resolution** The target software provides two inputs: - `deploy/pixi.toml.j2`: a Jinja2-templated pixi manifest that declaratively defines the pixi environment dependencies - `deploy/config.yaml.j2`: a Jinja2-templated YAML configuration file that defines deployment options and variants (channels, prefix, MPI selection, feature toggles, etc.) `mache.deploy` renders and interprets these files to construct a pixi environment. The pixi manifest (`deploy/pixi.toml.j2`) is the authoritative specification of the pixi environment dependencies. --- ### Requirement: A way for target software to specify spack packages The target software must define: - Spack packages - Versions, variants, and compiler/MPI combinations **Design resolution** - Spack specifications are included in `deploy/config.yaml.j2` - `mache.deploy` interprets and realizes these specs using spack - Spack logic lives entirely inside `mache`, not in the target software --- ### Requirement: Support for environment variants The target software must support multiple deployment variants, including: - Different machines - Different compilers and MPI stacks - Optional components (e.g., Trilinos, Albany, PETSc) **Design resolution** - Variants are declared declaratively in `deploy/config.yaml.j2` - Users select variants via command-line options to `deploy.py` / `mache deploy` - Variant selection is propagated consistently to pixi, spack, modules, and environment variables --- ### Requirement: Provide environment variables Some target software requires environment variables to be set to function correctly. **Design resolution** - Environment variables are specified declaratively in `deploy/config.yaml.j2` - `mache.deploy` generates shell *load* scripts that: - activate pixi environments - load spack environments - load system modules - export required environment variables --- ### Requirement: Provide a mechanism to load environments After deployment, users need a simple way to load the environment. **Design resolution** `mache.deploy` generates load scripts, for example: - `load_.sh` (no toolchain-specific Spack environment) - `load____.sh` (toolchain-specific) Including `` avoids filename collisions on shared filesystems where multiple machines (or machine partitions exposed as distinct machine names) share compiler and MPI naming. These scripts encapsulate: - Pixi activation - Spack environment activation - Module loads - Environment variables Target software documentation points users to these scripts. --- ### Requirement: Conditional spack deployment Allow day-to-day developers to avoid rebuilding spack, while allowing maintainers to do full shared-environment deployments when needed. **Design resolution** **Mechanisms** - `deploy/config.yaml.j2` includes a default such as: - `spack.deploy: true | false` (deploy all supported spack envs, or none) - `spack.spack_path: ` (default checkout path for spack) - `spack.supported: true | false` (whether the target supports a “library” spack env) - `spack.software.supported: true | false` (whether the target supports a “software” spack env) - `deploy.py` exposes CLI flags such as: - `--deploy-spack` (force-enable spack deployment) - `--no-spack` (disable all spack use for a single run) - `--spack-path ` (temporary override for `spack.spack_path`) **Precedence** 1. If the user passes `--no-spack`, all spack use is disabled for that run. 2. Otherwise, if the user passes `--deploy-spack`, spack deployment is enabled regardless of the config default. 3. Otherwise, fall back to the `config.yaml.j2` default for deployment and reuse any supported pre-existing spack environments in load scripts. For spack checkout path resolution: 1. If the user passes `--spack-path`, it takes highest priority. 2. Otherwise, a deployment hook may set `ctx.runtime['spack']['spack_path']`. 3. Otherwise, fall back to `spack.spack_path` in `config.yaml.j2`. **Rationale** - *E3SM-Unified maintainer workflow*: default `spack.deploy: true` - *Polaris developer workflow*: default `spack.deploy: false` - *Polaris maintainer workflow*: run `./deploy.py --deploy-spack ...` when producing or updating a shared spack environment - *Temporary testing workflow*: run `./deploy.py --spack-path /tmp/ ...` to avoid editing and accidentally committing `deploy/config.yaml.j2` --- ### Desired: Skip pixi deployment (future) Some deployments may want to reuse a shared pixi environment. **Design resolution (future)** - Supported declaratively - `mache.deploy` would still provide load scripts and environment management --- ### Desired: Testing target software (future) It should be possible to validate that deployment succeeded. **Design resolution (future)** - Target software may optionally provide post-deployment tests - `mache.deploy` can expose hooks for running these tests --- ## Conceptual Design *Date last modified: Dec 29, 2025* *Contributors: Xylar Asay-Davis, Althea Denlinger* --- ### Three-stage deployment model Deployment proceeds in three clearly separated stages: 1. **Target software entrypoint (`deploy.py`)** - Runs with system Python (minimal assumptions) - Parses CLI from a declarative spec - Downloads `bootstrap.py` - Invokes `bootstrap.py` 2. **Bootstrap stage (`bootstrap.py`)** - Standalone script, not part of the `mache` package - Installs pixi if needed - Creates a bootstrap pixi environment - Installs `mache` 3. **Deployment stage (`mache deploy`)** - Runs with full `mache` installed - Interprets `deploy/config.yaml.j2` - Creates pixi and spack environments - Generates load scripts This separation is intentional and strictly enforced. --- ## CLI Design and Sharing *Date last modified: Jan 26, 2026* *Contributor: Xylar Asay-Davis* ### Problem - `deploy.py`, `bootstrap.py`, and `mache deploy` all need overlapping CLI arguments - Duplicating argparse definitions across repositories is fragile - Users expect `./deploy.py --help` to be authoritative ### Design resolution: `cli_spec.json(.j2)` Each target software includes `deploy/cli_spec.json` rendered from the packaged template, plus an optional downstream-owned `deploy/custom_cli_spec.json`, which declaratively define: - Command-line flags - Help text - Destinations - Routing (`deploy`, `bootstrap`, `run`) **Usage** - `deploy.py` - Builds its argparse interface from `deploy/cli_spec.json` plus optional `deploy/custom_cli_spec.json` - Forwards appropriate arguments to `bootstrap.py` and `mache deploy run` - `mache deploy run` - Builds its CLI from the packaged `mache/deploy/templates/cli_spec.json.j2` plus optional `deploy/custom_cli_spec.json` - Uses the same source template as `mache deploy init/update` for the generated portion **Benefits** - Single source of truth for user-facing CLI - No duplication across repositories - Consistent `--help` output - Safe downstream extension point for repo-specific flags --- ## Starter Templates and Initialization *Date last modified: Jan 26, 2026* *Contributor: Xylar Asay-Davis* ### Templates `mache.deploy` provides templates for: - `deploy.py` - `cli_spec.json.j2` - `custom_cli_spec.json` - `pins.cfg` - `config.yaml.j2.j2` (renders to `deploy/config.yaml.j2`) - `pixi.toml.j2.j2` (renders to `deploy/pixi.toml.j2`) - `spack.yaml.j2` - `hooks.py.j2` - `load.sh.j2` - `spack_install.bash.j2` These templates include placeholders for: - Software name - Pinned `mache` version - Minimal configuration scaffolding --- ### `mache deploy init` A command that: - Copies templates into a target software repository - Fills in required placeholders - Creates a minimal, working deployment setup - Writes: `deploy.py`, `deploy/cli_spec.json`, `deploy/pins.cfg`, `deploy/custom_cli_spec.json`, `deploy/config.yaml.j2`, `deploy/pixi.toml.j2`, `deploy/spack.yaml.j2`, `deploy/hooks.py`, and a placeholder `deploy/load.sh` --- ### Updating mache versions When a target software updates its pinned `mache` version: - `deploy.py` and `deploy/cli_spec.json` should be updated from the matching `mache` release - `deploy/custom_cli_spec.json`, `deploy/pins.cfg`, `deploy/config.yaml.j2`, and `deploy/pixi.toml.j2` remain software-owned The `mache deploy update` command updates only `deploy.py` and `deploy/cli_spec.json`. The intended upgrade workflow is therefore: 1. `./deploy.py --bootstrap-only --mache-version ` 2. `mache deploy update --software --mache-version ` inside the bootstrap environment 3. manual edit of `deploy/pins.cfg` so the pinned `mache` version matches --- ## Package Organization *Date last modified: Jan 26, 2026* *Contributor: Xylar Asay-Davis* All deployment-related assets live under: ``` mache/deploy/ ├── bootstrap.py # standalone script ├── cli.py # mache deploy CLI ├── cli_spec.py # cli_spec parsing helpers ├── conda.py # conda/pixi helpers ├── hooks.py # deployment hook framework ├── init_update.py # init/update logic for target repos ├── jinja.py # Jinja helpers ├── machine.py # machine/deploy config helpers ├── run.py # deployment runner (called by `mache deploy run`) ├── spack.py # spack helpers └── templates/ ├── deploy.py.j2 ├── cli_spec.json.j2 ├── pins.cfg.j2 ├── config.yaml.j2.j2 ├── pixi.toml.j2.j2 ├── spack.yaml.j2 ├── hooks.py.j2 ├── load.sh.j2 └── spack_install.bash.j2 ``` Reusable JIGSAW build/install logic now lives outside `mache.deploy` in: ``` mache/jigsaw/ ├── __init__.py # backend selection + build/install orchestration ├── recipe.yaml.j2 # rattler-build recipe template ├── linux-64.yaml.j2 # linux variant template ├── osx-64.yaml.j2 # macOS variant template └── build.sh # build script used by the recipe ``` This keeps deployment concerns clearly separated from the rest of `mache`. --- ## Closing Notes The current design: - Satisfies all original requirements - Clarifies responsibilities across stages - Minimizes redundancy - Supports both stable users and developers - Provides a clear path for future growth (init, update, testing) Most importantly, it turns deployment from an ad hoc per-project liability into a shared, documented, and evolvable capability. --- ## Testing *Date last modified: Jan 26, 2026* *Contributor: Xylar Asay-Davis* ### Polaris - Full deployment on Chrysalis - Intel and GNU compilers - MPAS-Ocean `pr` suite (both compilers) - Omega `omega_pr` suite (both compilers) ### Planned test deployment of E3SM-Unified - Not yet started - Full test deployment on Chrysalis - GNU compilers - Test run of MPAS-Analysis