docs/architecture.md
This document provides a comprehensive overview of mise's architecture, designed primarily for contributors and those interested in understanding how mise works internally.
For practical development guidance, see the Contributing Guide.
mise is a Rust-based tool with a modular architecture centered around three core concepts:
These three pillars work together to provide a unified development environment management experience.
src/cli/)The CLI layer provides the user interface and delegates to core functionality:
install.rs, use.rs, run.rs, etc.)clap for robust CLI parsing and validationKey Commands Architecture:
install - Tool installation coordinationuse - Tool activation and configuration managementrun - Task execution with dependency resolutionenv - Environment variable managementshell - Shell integration and activationsrc/backend/)The backend system is mise's core abstraction for tool management, implementing a trait-based architecture:
pub trait Backend: Debug + Send + Sync {
async fn list_remote_versions(&self, config: &Arc<Config>) -> Result<Vec<String>>;
async fn install_version(&self, ctx: &InstallContext, tv: ToolVersion) -> Result<ToolVersion>;
async fn uninstall_version(&self, tv: &ToolVersion) -> Result<()>;
// ... additional methods for lifecycle management
}
Backend Categories:
For guidance on implementing new backends, see the Contributing Guide. For detailed backend system design, see Backend Architecture.
src/config/)A hierarchical configuration system that merges settings from multiple config files:
Config Trait Architecture:
pub trait ConfigFile: Debug + Send + Sync {
fn get_path(&self) -> &Path;
fn to_tool_request_set(&self) -> Result<ToolRequestSet>;
fn env_entries(&self) -> Result<Vec<EnvDirective>>;
fn tasks(&self) -> Vec<&Task>;
// ... additional configuration methods
}
Concrete Implementations:
MiseToml - Primary configuration format with full feature supportToolVersions - asdf compatibility layerIdiomaticVersion - Language-specific version files (.node-version, etc.)Configuration Hierarchy: See Configuration Documentation for the complete hierarchy and precedence rules.
src/toolset/)Coordinates tool resolution, installation, and environment setup:
Core Components:
Toolset - Immutable collection of resolved tools for a contextToolVersion - Represents a specific, resolved tool version (e.g., node@latest becomes [email protected])ToolRequest - User's tool specification (e.g., node@18, python@latest)ToolsetBuilder - Constructs toolsets from configuration with dependency resolutionTool Resolution Pipeline:
latest, prefix:1.2, sub-1:latest, etc.) to concrete versionssrc/task/)Sophisticated task execution with dependency graph management:
Architecture Components:
Task - Task definition with metadata, dependencies, and execution configurationDeps - Dependency graph manager using petgraph for DAG operationsTaskFileProvider - Discovers tasks from files and configurationTask Discovery:
Dependency Resolution:
depends, depends_post, wait_forSee the Task Documentation for complete usage details and configuration options, and Task Architecture for detailed system design.
src/plugins/)Extensibility layer supporting multiple plugin architectures:
Plugin Trait:
pub trait Plugin: Debug + Send {
fn name(&self) -> &str;
fn path(&self) -> PathBuf;
async fn install(&self, config: &Arc<Config>, pr: &dyn SingleReport) -> Result<()>;
async fn update(&self, pr: &dyn SingleReport, gitref: Option<String>) -> Result<()>;
// ... lifecycle management methods
}
Plugin Types:
For complete plugin documentation, see Plugin Guide.
src/shell/)Shell-specific code generation that abstracts commands like mise env and contains all shell differences in one place:
Shell Trait:
pub trait Shell: Display {
fn activate(&self, opts: ActivateOptions) -> String;
fn set_env(&self, k: &str, v: &str) -> String;
fn unset_env(&self, k: &str) -> String;
// ... shell-specific methods
}
Supported Shells: See mise activate documentation for the complete list
Shell Abstractions: Environment variable setting, PATH modification, command execution
src/env*.rs)Helpers for working with environment variables:
EnvDiff - Tracks and applies environment changesEnvDirective - Configuration-based environment variable managementPathEnv - Intelligent PATH manipulation with precedence rulesFor environment setup and configuration, see Environment Documentation.
src/cache.rs)Generic caching backed by files, using msgpack serialization with zstd compression:
CacheManager<T> - Generic caching with TTL supportmise employs a multi-layered testing strategy that combines different testing approaches for thorough validation across its complex feature set.
Testing Strategy Overview:
#[test] functions embedded in source filesinsta crate for complex output validation::: tip Testing Philosophy Most tests in mise are end-to-end tests, and this is generally the preferred approach for new functionality. E2E tests provide thorough validation of real-world usage scenarios and catch integration issues that unit tests might miss. However, E2E tests can be challenging to run locally due to environment dependencies and setup complexity. For development and CI purposes, it's often easier to run tests on GitHub Actions where the environment is consistent and properly configured.
See the Contributing Guide for detailed testing setup and guidelines. :::
src/ modules)Structure and Characteristics:
mod tests blockscargo testpretty_assertions, insta, test-log, ctormod tests {
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
use crate::config::Config;
use super::*;
#[tokio::test]
async fn test_hash_to_str() {
let _config = Config::get().await.unwrap();
assert_eq!(hash_to_str(&"foo"), "e1b19adfb2e348a2");
}
}
Test Environment Setup:
ctor::ctor in src/test.rs for test environment initializationHOME, cache, and config directories#[tokio::test] for async testinge2e/)Architecture:
e2e/
├── run_test # Single test executor with environment isolation
├── run_all_tests # Test orchestrator with parallel execution
├── assert.sh # Rich assertion library
├── cli/ # CLI command tests
│ ├── test_use # Testing tool activation and configuration
│ ├── test_install # Testing tool installation
│ ├── test_upgrade # Testing tool upgrades
│ ├── test_uninstall # Testing tool removal
│ └── test_version # Testing version commands
├── backend/ # Backend-specific tests
│ ├── test_aqua # Testing aqua package manager
│ ├── test_asdf # Testing asdf plugin compatibility
│ └── test_npm # Testing npm backend
├── tasks/ # Task system tests
│ ├── test_task_deps # Testing task dependencies
│ ├── test_task_run_depends # Testing task execution order
│ ├── test_task_ls # Testing task listing
│ └── test_task_info # Testing task metadata
├── config/ # Configuration tests
│ ├── test_config_ls # Testing configuration listing
│ └── test_config_set # Testing configuration updates
└── [other domains]/ # Additional test categories
Environment Isolation System:
Each test runs in complete isolation with temporary directories:
setup_isolated_env() {
TEST_ISOLATED_DIR="$(mktemp --tmpdir --directory "$(basename "$TEST").XXXXXX")"
TEST_HOME="$TEST_ISOLATED_DIR/home"
MISE_DATA_DIR="$TEST_HOME/.local/share/mise"
MISE_CACHE_DIR="$TEST_HOME/.cache/mise"
# ... complete environment isolation
}
Rich Assertion Framework:
The assert.sh provides rich test utilities:
# Basic assertions
assert "command" "expected_output"
assert_contains "command" "substring"
assert_fail "command" "error_message"
# JSON testing
assert_json "command" '{"key": "value"}'
assert_json_partial_object "command" "field1,field2" '{"field1": "value1"}'
# File system assertions
assert_directory_exists "path"
assert_directory_empty "path"
Test Categories:
Windows-Specific Tests (e2e-win/):
.ps1)Describe "go" {
It "installs go" {
mise install go@latest
go version | Should -Match "go version"
}
}
src/snapshots/)Implementation:
insta for snapshot testing with 11 snapshot files.snap files#[tokio::test]
async fn test_parse() {
let diff = DirenvDiff::parse(input).unwrap();
assert_snapshot!(diff); // Creates/validates snapshot
}
Performance and Utility Tests (xtasks/test/):
perf script for benchmarkingcoverage script for test coverage analysise2e script with filtering capabilitiesTest Data Management (test/):
test/
├── config/ # Test-specific configs
├── cwd/ # Test working directories
├── data/ # Test plugins and mock data
├── fixtures/ # Sample configuration files
├── plugins/ # Test plugin definitions
└── state/ # Test state directory
Test Execution Modes:
_slow suffix, skipped unless TEST_ALL=1TEST_TRANCHE_COUNTDeveloper Experience Features:
Running Tests:
# Run all unit tests
cargo test
# Run all E2E tests
./e2e/run_all_tests
# Run specific E2E test
./e2e/run_test test_install
# Run with coverage
./xtasks/test/coverage
# Performance testing
./xtasks/test/perf
For complete development setup and testing procedures, see the Contributing Guide.
This robust test architecture ensures mise's reliability across its complex feature set, including tool management, environment configuration, task execution, and multi-platform support.
For deeper understanding of specific subsystems: