diff --git a/docs/how-to/customize-runtime.md b/docs/how-to/customize-runtime.md index 29223af..46e568f 100644 --- a/docs/how-to/customize-runtime.md +++ b/docs/how-to/customize-runtime.md @@ -67,6 +67,12 @@ runtime-name = "demo" installer = "homebrew" ``` +During automatic bootstrap, that stamp writes Constructor-compatible +`.installer.info` JSON into the managed prefix. Its `name`, `version`, and +`platform` fields identify the stamped distribution, while `type` contains the +configured `installer` value. This metadata identifies the distribution and +installer. It is not a launcher ownership record. + ## Choose Runtime Packages The selected source environment is the complete runtime package set. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index c530397..b751314 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -176,6 +176,12 @@ For the naming model behind `runtime-name`, `artifact-name`, `install-name`, and Release workflows can override this with `cs build --installer INSTALLER` or the GitHub Action `installer` input. + When configured, automatic bootstrap writes Constructor-compatible + `/.installer.info` JSON with the exact fields `name`, `version`, + `platform`, and `type`. The configured `installer` value becomes `type`. + This metadata records how the prefix was distributed. Update and uninstall + code must not use it as a launcher ownership record. + `condarc-file` : Optional path to a YAML condarc file. Relative paths are resolved from the selected project manifest. The builder requires a YAML mapping and stamps diff --git a/src/constructor_metadata.rs b/src/constructor_metadata.rs index 53667e2..23202c5 100644 --- a/src/constructor_metadata.rs +++ b/src/constructor_metadata.rs @@ -10,6 +10,15 @@ use rattler_conda_types::{Platform, RepoDataRecord}; use crate::{install, policy}; +#[derive(serde::Serialize)] +struct InstallerInfo<'a> { + name: &'a str, + version: &'a str, + platform: String, + #[serde(rename = "type")] + installer_type: &'a str, +} + pub(crate) fn write_prefix_metadata( prefix: &Path, lock_content: &str, @@ -63,6 +72,44 @@ fn write_prefix_metadata_from_records( })?; eprintln!(" Wrote {}", policy::path_for_display(&explicit_path)); + write_configured_installer_info(prefix, platform)?; + + Ok(()) +} + +fn write_configured_installer_info(prefix: &Path, platform: Platform) -> miette::Result<()> { + let Some(installer_type) = policy::installer() else { + return Ok(()); + }; + write_installer_info( + prefix, + policy::runtime_name(), + policy::runtime_version(), + platform, + installer_type, + ) +} + +fn write_installer_info( + prefix: &Path, + name: &str, + version: &str, + platform: Platform, + installer_type: &str, +) -> miette::Result<()> { + let path = prefix.join(".installer.info"); + let contents = serde_json::to_string(&InstallerInfo { + name, + version, + platform: platform.to_string(), + installer_type, + }) + .into_diagnostic() + .context("failed to render Constructor installer metadata")?; + std::fs::write(&path, contents) + .into_diagnostic() + .with_context(|| format!("failed to write {}", policy::path_for_display(&path)))?; + eprintln!(" Wrote {}", policy::path_for_display(&path)); Ok(()) } @@ -324,6 +371,27 @@ https://conda.anaconda.org/conda-forge/noarch/conda-spawn-1.0-0.conda#sha256:{SH )), "{explicit}" ); + assert!(!tmp.path().join(".installer.info").exists()); + } + + #[test] + fn test_write_installer_info_matches_constructor_json() { + let tmp = TempDir::new().unwrap(); + + write_installer_info( + tmp.path(), + "Demo Distribution", + "1.2.3", + Platform::Linux64, + "homebrew", + ) + .unwrap(); + + let contents = std::fs::read_to_string(tmp.path().join(".installer.info")).unwrap(); + assert_eq!( + contents, + r#"{"name":"Demo Distribution","version":"1.2.3","platform":"linux-64","type":"homebrew"}"# + ); } #[test] diff --git a/src/policy.rs b/src/policy.rs index c8b157a..ee8a3c0 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -28,6 +28,10 @@ pub(crate) fn delegate_executable() -> &'static str { &runtime_data::current().header.delegate_executable } +pub(crate) fn installer() -> Option<&'static str> { + runtime_data::current().header.installer.as_deref() +} + pub(crate) fn display_name() -> &'static str { runtime_name() }