Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API Overview

This document maps HPM’s crate layout to its key public types. For full API docs with signatures and examples, generate rustdoc locally:

cargo doc --workspace --no-deps --open

Crate graph

hpm-cli         binary; clap dispatch, output formatting, exit codes
  ├── hpm-core         storage, discovery, lock, registry, fetch, pack, python
  │     ├── hpm-config
  │     ├── hpm-package
  │     └── hpm-assets
  ├── hpm-config       layered config loading and merging
  │     └── hpm-package
  ├── hpm-package      manifest parsing, Houdini integration, deps  (leaf)
  └── hpm-assets       operator asset-index model for `hpm pack`     (leaf)

Python tooling (bundled uv, content-addressable venvs, Houdini→Python mapping) lives in the hpm_core::python submodule. It was a separate crate through 0.16 but collapsed into hpm-core since it had no external consumers.

Leaves are hpm-package and hpm-assets — neither depends on anything in the workspace, so they can be embedded by external tools without dragging in the rest of HPM. hpm-config is not a leaf: it depends on hpm-package for the shared registry and dependency types.

Each crate defines its own error type via thiserror (e.g. StorageError, ConfigError). hpm-cli converts these into a single CliError with exit codes and help hints.

Key types by crate

hpm-core

TypePurpose
StorageManagerGlobal package install/remove/list. Backed by ~/.hpm/packages/.
ProjectDiscoveryScans [projects] paths to enumerate active HPM projects.
DependencyGraphDependency graph over PackageIds; mark_reachable_from_roots powers orphan detection for hpm clean (the set difference itself lives in StorageManager::find_orphaned_packages).
LockFile, LockedDependency, LockedPythonDependency, LockMetadatahpm.lock types.
LockedSourceOrigin recorded in the lockfile for each dep — Url { url, version } or Path { path }.
LockErrorRead/Parse/Write/Serialize plus ChecksumMismatch and PackageMissing { package, expected_dir }.
PackageSourceURL-only struct { url, version } — what ArchiveFetcher downloads. Path deps bypass the fetcher and use LockedSource::Path in the lockfile.
cas_install_dir(packages_dir, name, version)Canonical install path <packages_dir>/<slug>@<version> for a lockfile dep name. Used by LockFile::verify_checksums and any consumer that needs to find an installed package off the lockfile alone.
fetcher_install_dir(packages_dir, name, version)Staging path <packages_dir>/<safe_name>-<version> used by ArchiveFetcher while extracting; the result is then copied into the canonical CAS via install_into_cas.
Registry (async trait)Registry abstraction. ApiRegistry and GitRegistry implement it.
RegistrySetComposite that fans requests out to every configured registry. *_in variants (resolve_in, get_version_in, get_versions_in) restrict the lookup to one named registry, honouring a dependency’s registry pin; an unconfigured name is UnknownRegistry rather than a fallback to the rest of the set.
global, global::ledgerhpm global installs into Houdini’s user preferences. build_houdini_package is shared with the project emitter; Ledger records what was installed per Houdini version and doubles as a hpm clean GC root.
houdini_prefsPer-platform Houdini user-preferences directory resolution, including HOUDINI_USER_PREF_DIR and its __HVER__ placeholder. The only hpm module that knows a Houdini installation’s conventions.
ArchiveFetcherDownloads and extracts registry-hosted archives.
fetch_manifestFree function: returns the parsed PackageManifest for (name, version) without project context. CAS hit reads from disk; CAS miss resolves+fetches+installs. Pass "" or "latest" to pick the highest semver.
PackageRunEnvResolved runtime environment for a package-env script, produced by ProjectManager::resolve_package_env(extra_requirements): the merged venv (venv_bin, virtual_env) plus python_paths (each package’s python/ then the venv site-packages) for PYTHONPATH. Read-only — built from hpm.lock + the global store, mirroring what sync_dependencies resolves for Houdini. hpm run applies it for package-env = true scripts.
script_runThe shared [scripts] runner — the single place the script-env contract (HPM_PACKAGE_ROOT, caller extra_env, and per-script-venv or package-env PATH/VIRTUAL_ENV/PYTHONPATH) is composed. Embedders implement ScriptSink (spawn + diagnostics) and drive run_prepack(manifest, names, package_root, extra_env, sink) (a [stage].prepack sequence, fail-fast) or run_script(manifest, name, package_root, extra_args, extra_env, sink) (one entry, returns the exit code). prepare_script(...) exposes just the env+command-line composition as a PreparedScript { command_line, working_dir, env } for embedders that own their spawn loop. hpm run and hpm build’s prepack both route through here, so a manifest feature picked up by one is picked up by all — no per-embedder drift. Prefer this over re-composing prepare_script_env by hand: the lower-level handle is easy to half-wire (e.g. forgetting to put the venv interpreter on PATH).
packerProduces signed/unsigned .zip archives for hpm pack. Public helpers: pack, create_archive, compute_archive_checksum/compute_bytes_checksum (SHA-256), sign_archive/sign_bytes (Ed25519, returns (base64_signature, hex_key_id)), load_signing_key / load_signing_key_from_pem. The byte-based variants are for tooling that mutates archive bytes after pack (e.g. third-party hosting that requires reshaping) and needs to recompute the hash + re-sign without round-tripping through disk. SigningKey is re-exported so callers don’t need a direct ed25519-dalek dep.
collect_assets, AssetIndex, AssetIndexErrorBuilds the hpm pack operator asset index from a manifest’s [[operators]] declarations for a target platform (asset_index module). Maps each declaration to an hpm_assets::Asset (deriving namespace/version from the type name), resolving per-platform source tables to the target platform and dropping operators not shipped there. Verifies each resolved source exists in the produced archive — any that don’t are returned in AssetIndex::missing_sources for the caller to warn on or, with hpm pack --verify-assets, treat as fatal.

hpm-package

TypePurpose
PackageManifestParsed hpm.toml (every section). PackageManifest::from_path reads + parses, returning ManifestLoadError with the offending path. validate() returns the first structural error; validate_with(ValidationLevel) returns a ValidationReport { errors, warnings } separating hard errors from publish-quality advisories.
ValidationLevel, ValidationReportStrict runs structural checks only; Publish adds advisory warnings on missing description / authors / keywords / [compat].houdini. hpm check consumes the report; a future hpm publish can promote warnings to errors.
ManifestLoadErrorNotFound { path } / Read { path, source } / Parse { path, source }. Re-exported by hpm-core’s StorageError, ProjectError, DiscoveryError, and FetchManifestError.
IoOpShared IO-error shape { op: &'static str, path: PathBuf, source: io::Error } used as the single Io(IoOp) variant in StorageError, ProjectError, and DiscoveryError. Construct via IoOp::wrap("read directory", &path, e) at call sites.
PackagePath, PackagePathErrorValidated creator/slug newtype. Kebab-case enforced at deserialization, so creator() and slug() return &str — no Option.
PackageInfoContents of [package]. path: PackagePath is the canonical identifier; name, version, etc. are user-facing metadata.
CompatConfigContents of [compat]. houdini: Option<HoudiniRange> parses and validates at deserialize time; houdini_min() extracts its lower bound for Python ABI selection. platforms: Vec<Platform> declares the native platforms this package supports — unknown identifiers fail at TOML parse via Platform::TryFrom<String>. Must reference platforms used in [stage.platform.*] entries.
DependencySpecEnum: Registry {..} | Url {..} | Path {..}. The bare-string shorthand pkg = "1.0.0" round-trips as Registry { registry: None, optional: false }.
PythonDependencySpecUntagged enum: Simple(String) | Detailed {..}.
ManifestEnvEntry, EnvMethod[runtime] entries (method, optional value, required flag) and methods (set/prepend/append). value is Option<EnvValue> — flat string or ordered variant list. lower(substitutions, is_dev) is the single emit path; install-source-gated variants are filtered before lowering.
EnvValue, EnvValueBranch, ConditionConditional [runtime] value support. Variants compile to Houdini’s expression-object array via compile_condition / lower_conditional. Axes: houdini (Cargo-style req), os, python (all runtime-evaluated by Houdini), plus install_source (typed InstallSource: dev / registry, filtered by hpm at install time). os is a typed OsKey; unknown axis values fail at manifest load.
PackageScripts[scripts] table. commands: IndexMap<String, ScriptEntry> — one entry per script, with per-host variation expressed inside ScriptEntry::cmd via the same when-grammar that [runtime] uses.
ScriptEntry, ScriptEnvUntagged enum: Plain(String) for the shorthand form, WithEnv { cmd: EnvValue, python?, requirements?, label?, description?, package_env } for the table form. resolve_cmd(host_os) picks the matching variant when cmd is conditional. Accessors python(), requirements(), label(), description(), needs_venv(), uses_package_env() work on both arms. package_env (package-env in TOML) opts the script into the project’s full resolved environment — see ProjectManager::resolve_package_env. label/description are optional, consumer-agnostic display metadata that HPM itself never acts on.
StageConfig, PlatformStaging, StagePlatformRules, PlaceRule, ProfileStaging, StageProfileRules[stage] table. output_dir (default "dist"), prepack script list, workspace include / exclude globs, and per-platform place = [{ from, to }] rules. from is a workspace-relative glob; to is either a directory (ends with /) or a literal archive path. [stage.profile.<name>] tables (StageProfileRules) layer per-profile prepack/include/exclude/place overrides onto the base; StageConfig::resolved_for_profile(name) returns the merged config a build uses.
PlatformCanonical platform enum, mirroring the TumbleTrove API: LinuxX86_64, LinuxAarch64, MacosX86_64, MacosAarch64, WindowsX86_64, WindowsAarch64, Universal. os_key() returns Option<&str> (None for Universal).
RegistryConfig, RegistryType[[registries]] entries in manifests.
OperatorDecl, OperatorKind, OperatorSource, SourceResolution[[operators]] entries — the operators (node types) a package bundles, declared by the author so hpm pack can emit a searchable asset index. kind (Hda/Dso), type_name, and category are required; label, tab_submenu, icon, and source are optional. OperatorSource is either a single archive path or a per-platform table; OperatorDecl::resolved_source(platform) resolves it to a SourceResolution (Path / Unspecified / NotForPlatform).
PackageTemplateScaffolding for hpm init (standard and --bare).
HoudiniPackage, HoudiniNativePackage, HoudiniEnvValueHoudini package.json output types.

hpm-assets

TypePurpose
Asset, AssetKindThe wire model hpm pack --json emits in its assets array. One flat object per bundled operator with a kind discriminator (hda_operator/dso_operator); None fields are omitted. Built from [[operators]] declarations by hpm_core::collect_assets.
split_type_name(type_name)Best-effort split of a namespaced operator type name into (namespace, base, op_version) following Houdini’s namespace::name::version grammar (version detected as digits-and-dots). Used to populate an Asset’s namespace/op_version from the declared type_name.

hpm-core::python

TypePurpose
VenvManagerCreates and reuses content-addressable venvs.
PythonVersionHoudini-to-Python version value.
ResolvedDependencySetUV-resolved dependency set (BTreeMap<name, version> plus the Python version) with a hash() content hash.
PythonDependencies, PythonDependency, VersionSpecDeclared (pre-resolution) Python dependency types.
VenvMetadata, OrphanedVenvPer-venv metadata.json model and the orphan-analysis result type.
PythonCleanupAnalyzerDetects orphan venvs for hpm clean --python-only. A venv that records owners is orphaned when none is still installed; one with no recorded owners (a [scripts] venv, or one predating owner recording) is collected only after 30 days idle. Owners are recorded by VenvManager::ensure_virtual_environment_for.
prepare_script_env(entry)Per-script-venv building block: bootstraps bundled uv, materializes the venv if needed, and returns a ScriptEnvHandle carrying the env-var mutations to apply before spawning. Plain string entries return a default (no-op) handle. Covers only the python/requirements venv path — not package-env, HPM_PACKAGE_ROOT, or the command line. Most embedders should reach for hpm_core::script_run (above), which wires this plus the rest of the contract; use prepare_script_env directly only when you specifically need just the venv handle.
ScriptEnvHandleSpawn-strategy-agnostic env-var bundle (path_prepend, env). apply_to(&mut HashMap<String,String>) folds it into a caller-staged env map ready for Command::envs or any other spawn primitive.
ensure_script_venv(python, requirements)Lower-level free function: resolves raw PEP-508 requirement strings via uv pip compile and defers to VenvManager, returning the venv root. Prefer prepare_script_env for the full flow.
venv_bin_dir(path)Returns the executable directory inside a uv-created venv (bin/ on Unix, Scripts/ on Windows). prepare_script_env already prepends this to PATH; use directly only when bypassing the handle.
DEFAULT_PYTHON_VERSION"3.11". The Python version ensure_script_venv uses when a script omits python.

hpm-config

TypePurpose
ConfigTop-level config (defaults < ~/.hpm/config.toml < project config < env < flags).
StorageConfighome_dir, cache_dir, packages_dir, registry_cache_dir.
InstallConfigpath, parallel_downloads.
ProjectsConfigexplicit_paths, search_roots, max_search_depth, ignore_patterns.
RegistrySourceConfig, RegistryTypeRegistry entries from [[registries]].
SigningConfigkey_path fallback for hpm pack.
ProjectPathsPer-project paths (.hpm/packages/, hpm.lock, hpm.toml).

hpm-cli

TypePurpose
CliErrorCLI-facing error enum with categorised variants (Config, Package, Network, Io). Carries optional help text.
ExitStatusProcess-exit code abstraction; converts &CliError -> ExitStatus -> ExitCode.

Exit codes used by hpm-cli:

CodeMeaning
0Success.
1Any hpm-originated failure — bad configuration, bad input, missing manifest, network or I/O error.
NPass-through exit code from an invoked external tool (ExitStatus::External).

Design principles

  • Minimal coupling. Each crate owns one concern; no upward dependencies.
  • Async by default. Everything I/O-bound runs on Tokio.
  • Content-addressable storage. Both HPM packages (<slug>@<version> under ~/.hpm/packages/, with path-installed packages isolated in _dev/) and Python venvs (SHA-256 over the resolved set) deduplicate globally.
  • Safety by construction. Cleanup never removes a package an active project needs; lock file checksums are verified before every install; signing is opt-in but standard when used.
  • TOML-first configuration. Every persistent config — manifest, lock file, global config — is TOML and hand-editable.