feat: enhance package dependency handling and signing process

- Updated `lib32_dependencies` method to ensure the package name is included in runtime dependencies if not already present.
- Modified the signing key loading process to allow for multiple password attempts, improving user experience during key access.
- Introduced functions for staging licenses of split packages, ensuring proper symlink creation for matching licenses and copying distinct ones.
- Added tests for verifying the correct functionality of manpage compression and license staging.
This commit is contained in:
2026-03-13 08:52:42 -05:00
parent 8d9e13ed71
commit 0c06be6b6b
9 changed files with 1130 additions and 399 deletions
Generated
+19 -19
View File
@@ -39,9 +39,9 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.21" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@@ -60,9 +60,9 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.7" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
@@ -236,9 +236,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.60" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -246,9 +246,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.60" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -258,18 +258,18 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.66" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.55" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -285,9 +285,9 @@ checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]] [[package]]
name = "clap_mangen" name = "clap_mangen"
version = "0.2.31" version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301" checksum = "7e30ffc187e2e3aeafcd1c6e2aa416e29739454c0ccaa419226d5ecd181f2d78"
dependencies = [ dependencies = [
"clap", "clap",
"roff", "roff",
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
[[package]] [[package]]
name = "depot" name = "depot"
version = "0.20.1" version = "0.21.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar", "ar",
@@ -1606,9 +1606,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.44" version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1703,9 +1703,9 @@ dependencies = [
[[package]] [[package]]
name = "roff" name = "roff"
version = "0.2.2" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" checksum = "dbf2048e0e979efb2ca7b91c4f1a8d77c91853e9b987c94c555668a8994915ad"
[[package]] [[package]]
name = "rpassword" name = "rpassword"
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "depot" name = "depot"
version = "0.20.1" version = "0.21.0"
edition = "2024" edition = "2024"
[lints.rust] [lints.rust]
@@ -10,7 +10,7 @@ warnings = "deny"
anyhow = "1.0.102" anyhow = "1.0.102"
ar = "0.9.0" ar = "0.9.0"
bzip2 = "0.6.1" bzip2 = "0.6.1"
clap = { version = "4.5.60", features = ["derive"] } clap = { version = "4.6.0", features = ["derive"] }
flate2 = "1.1.9" flate2 = "1.1.9"
git2 = "0.20.4" git2 = "0.20.4"
indicatif = "0.18.4" indicatif = "0.18.4"
@@ -37,8 +37,8 @@ petgraph = "0.8.3"
fd-lock = "4.0.4" fd-lock = "4.0.4"
reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "native-tls"] } reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "native-tls"] }
filetime = "0.2.27" filetime = "0.2.27"
clap_complete = "4.5.66" clap_complete = "4.6.0"
clap_mangen = "0.2.31" clap_mangen = "0.2.33"
sys-mount = { version = "3.1.0", default-features = false } sys-mount = { version = "3.1.0", default-features = false }
time = { version = "0.3.47", features = ["formatting", "parsing"] } time = { version = "0.3.47", features = ["formatting", "parsing"] }
b2sum-rust = "0.3.0" b2sum-rust = "0.3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
project( project(
'depot', 'depot',
version: '0.20.1', version: '0.21.0',
meson_version: '>=0.60.0', meson_version: '>=0.60.0',
default_options: ['buildtype=release'], default_options: ['buildtype=release'],
) )
+28
View File
@@ -110,9 +110,21 @@ pub fn build(
mod tests { mod tests {
use super::*; use super::*;
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo}; use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo};
use crate::test_support::TestEnv;
use std::fs; use std::fs;
use tempfile::tempdir; use tempfile::tempdir;
#[cfg(unix)]
fn write_executable(path: &std::path::Path, contents: &str) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::write(path, contents)?;
let mut perms = fs::metadata(path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(path, perms)?;
Ok(())
}
fn mk_spec(name: &str, version: &str) -> PackageSpec { fn mk_spec(name: &str, version: &str) -> PackageSpec {
PackageSpec { PackageSpec {
package: PackageInfo { package: PackageInfo {
@@ -149,8 +161,24 @@ mod tests {
fn test_makefile_build_runs_commands() -> Result<()> { fn test_makefile_build_runs_commands() -> Result<()> {
let tmp_src = tempdir()?; let tmp_src = tempdir()?;
let tmp_dest = tempdir()?; let tmp_dest = tempdir()?;
let tmp_tools = tempdir()?;
let src_path = tmp_src.path(); let src_path = tmp_src.path();
let dest_path = tmp_dest.path(); let dest_path = tmp_dest.path();
let tools_path = tmp_tools.path();
write_executable(
&tools_path.join("fakeroot"),
r#"#!/bin/sh
if [ "$1" = "--" ]; then
shift
fi
exec "$@"
"#,
)?;
let mut env = TestEnv::new();
let old_path = std::env::var("PATH").unwrap_or_default();
env.set_var("PATH", format!("{}:{}", tools_path.display(), old_path));
let mut spec = mk_spec("test-make", "1.0"); let mut spec = mk_spec("test-make", "1.0");
spec.build.flags.makefile_commands = vec![ spec.build.flags.makefile_commands = vec![
+353 -131
View File
@@ -1,23 +1,29 @@
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Args, Parser, Subcommand, ValueEnum};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Parser)] #[derive(Debug, Clone, Args, Default)]
#[command(name = "Depot")] pub struct RootfsArgs {
#[command(about = "Source-based package manager for Linux", long_about = None)]
#[command(version)]
pub struct Cli {
/// Custom root filesystem path /// Custom root filesystem path
#[arg(long, short = 'r', default_value = "/", global = true)] #[arg(long, short = 'r', default_value = "/")]
pub rootfs: PathBuf, pub rootfs: PathBuf,
}
#[derive(Debug, Clone, Args, Default)]
pub struct PromptArgs {
/// Automatically answer yes to prompts and pick the default provider choice
#[arg(long, short = 'y')]
pub yes: bool,
}
#[derive(Debug, Clone, Args, Default)]
pub struct BuildExecArgs {
/// Skip dependency checks /// Skip dependency checks
#[arg(long, global = true)] #[arg(long)]
pub no_deps: bool, pub no_deps: bool,
/// Do not export CFLAGS/CXXFLAGS/LDFLAGS to build commands /// Do not export CFLAGS/CXXFLAGS/LDFLAGS to build commands
#[arg( #[arg(
long, long,
global = true,
action = clap::ArgAction::Set, action = clap::ArgAction::Set,
num_args = 0..=1, num_args = 0..=1,
default_value_t = false, default_value_t = false,
@@ -26,148 +32,256 @@ pub struct Cli {
pub no_flags: bool, pub no_flags: bool,
/// Cross-compilation prefix (e.g., x86_64-linux-musl, aarch64-linux-gnu) /// Cross-compilation prefix (e.g., x86_64-linux-musl, aarch64-linux-gnu)
#[arg(long, global = true)] #[arg(long)]
pub cross_prefix: Option<String>, pub cross_prefix: Option<String>,
/// Clean build workspace and source cache after successful install/build /// Clean build workspace and source cache after successful install/build
#[arg(long, global = true)] #[arg(long)]
pub clean: bool, pub clean: bool,
/// Automatically answer yes to prompts and pick the default provider choice
#[arg(long, short = 'y', global = true)]
pub yes: bool,
/// Show what would happen without performing builds/installs /// Show what would happen without performing builds/installs
#[arg(long, global = true)] #[arg(long)]
pub dry_run: bool, pub dry_run: bool,
/// Install test dependencies alongside build/runtime dependencies /// Install test dependencies alongside build/runtime dependencies
#[arg(long, global = true)] #[arg(long)]
pub test_deps: bool, pub test_deps: bool,
}
#[derive(Debug, Clone, Args, Default)]
pub struct Lib32Args {
/// Build/install only the lib32 companion package path (skip primary package output) /// Build/install only the lib32 companion package path (skip primary package output)
#[arg(long, global = true)] #[arg(long)]
pub lib32_only: bool, pub lib32_only: bool,
}
#[derive(Parser)]
#[command(name = "Depot")]
#[command(about = "Source-based package manager for Linux", long_about = None)]
#[command(version)]
pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Commands, pub command: Commands,
} }
#[derive(Debug, Clone, Args)]
pub struct InstallArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
#[command(flatten)]
pub prompt_args: PromptArgs,
#[command(flatten)]
pub build_exec_args: BuildExecArgs,
#[command(flatten)]
pub lib32_args: Lib32Args,
/// One or more package names, spec paths (.toml), or package archives (.tar.zst)
#[arg(
value_name = "SPEC_OR_ARCHIVE",
num_args = 1..,
required_unless_present = "spec"
)]
pub spec_or_archive: Vec<PathBuf>,
/// Explicitly specify path to package spec (.toml file)
#[arg(
short,
long = "spec",
visible_alias = "package",
alias = "p",
conflicts_with = "spec_or_archive"
)]
pub spec: Option<PathBuf>,
}
#[derive(Debug, Clone, Args)]
pub struct RemoveArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
#[command(flatten)]
pub prompt_args: PromptArgs,
/// Package name to remove
pub package: String,
}
#[derive(Debug, Clone, Args)]
pub struct BuildArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
#[command(flatten)]
pub prompt_args: PromptArgs,
#[command(flatten)]
pub build_exec_args: BuildExecArgs,
#[command(flatten)]
pub lib32_args: Lib32Args,
/// Path to package spec (.toml file)
#[arg(value_name = "SPEC")]
pub spec_pos: Option<PathBuf>,
/// Explicitly specify path to package spec (.toml file)
#[arg(short, long = "spec", visible_alias = "package", alias = "p")]
pub spec: Option<PathBuf>,
/// Install package to rootfs after creating package archive(s)
#[arg(long)]
pub install: bool,
/// Automatically install missing dependencies before building
#[arg(long)]
pub install_deps: bool,
/// Remove dependencies auto-installed for this build after the command finishes
#[arg(long)]
pub cleanup_deps: bool,
}
#[derive(Debug, Clone, Args)]
pub struct UpdateArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
#[command(flatten)]
pub prompt_args: PromptArgs,
#[command(flatten)]
pub build_exec_args: BuildExecArgs,
/// Optional package names to update (defaults to all installed packages with upgrades)
#[arg(value_name = "PACKAGE", num_args = 0..)]
pub packages: Vec<String>,
}
#[derive(Debug, Clone, Args)]
pub struct CheckArgs {
/// Directory to scan recursively for package specs
#[arg(default_value = ".")]
pub dir: PathBuf,
}
#[derive(Debug, Clone, Args)]
pub struct InfoArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
/// Path to package spec or installed package name
pub package: String,
}
#[derive(Debug, Clone, Args)]
pub struct SearchArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
/// Search query
pub query: String,
/// Search repository file lists (binary repo metadata) by path substring
#[arg(long)]
pub files: bool,
}
#[derive(Debug, Clone, Args)]
pub struct OwnsArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
/// Path to query (absolute or relative to rootfs)
pub path: PathBuf,
}
#[derive(Debug, Clone, Args)]
pub struct ListArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
}
#[derive(Debug, Clone, Args)]
pub struct SignArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
/// One or more .zst files to sign
#[arg(value_name = "FILE", required = true, num_args = 1..)]
pub files: Vec<PathBuf>,
}
#[derive(Debug, Clone, Args)]
pub struct RepoArgs {
#[command(subcommand)]
pub command: RepoCommands,
}
#[derive(Debug, Clone, Args)]
pub struct ConfigArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
}
#[derive(Debug, Clone, Args)]
pub struct GenerateArtifactsArgs {
/// Output directory for generated files
#[arg(long, value_name = "DIR")]
pub out_dir: PathBuf,
}
#[derive(Debug, Clone, Args)]
pub struct MakeSpecArgs {
/// Output file path (defaults to <name>.toml)
#[arg(short, long)]
pub output: Option<PathBuf>,
}
#[derive(Debug, Clone, Args)]
pub struct InternalArgs {
#[command(subcommand)]
pub command: InternalCommands,
}
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Commands { pub enum Commands {
/// Build and install a package from a spec file /// Build and install a package from a spec file
Install { Install(InstallArgs),
/// One or more package names, spec paths (.toml), or package archives (.tar.zst)
#[arg(
value_name = "SPEC_OR_ARCHIVE",
num_args = 1..,
required_unless_present = "spec"
)]
spec_or_archive: Vec<PathBuf>,
/// Explicitly specify path to package spec (.toml file)
#[arg(
short,
long = "spec",
visible_alias = "package",
alias = "p",
conflicts_with = "spec_or_archive"
)]
spec: Option<PathBuf>,
},
/// Remove an installed package /// Remove an installed package
Remove { Remove(RemoveArgs),
/// Package name to remove
package: String,
},
/// Build a package without installing /// Build a package without installing
Build { Build(BuildArgs),
/// Path to package spec (.toml file)
#[arg(value_name = "SPEC")]
spec_pos: Option<PathBuf>,
/// Explicitly specify path to package spec (.toml file)
#[arg(short, long = "spec", visible_alias = "package", alias = "p")]
spec: Option<PathBuf>,
/// Install package to rootfs after creating package archive(s)
#[arg(long)]
install: bool,
/// Automatically install missing dependencies before building
#[arg(long)]
install_deps: bool,
/// Remove dependencies auto-installed for this build after the command finishes
#[arg(long)]
cleanup_deps: bool,
},
/// Update installed packages from configured repositories /// Update installed packages from configured repositories
Update { Update(UpdateArgs),
/// Optional package names to update (defaults to all installed packages with upgrades)
#[arg(value_name = "PACKAGE", num_args = 0..)]
packages: Vec<String>,
},
/// Scan package specs for upstream version updates /// Scan package specs for upstream version updates
Check { Check(CheckArgs),
/// Directory to scan recursively for package specs
#[arg(default_value = ".")]
dir: PathBuf,
},
/// Show information about a package /// Show information about a package
Info { Info(InfoArgs),
/// Path to package spec or installed package name
package: String,
},
/// Search configured source and binary repos by package name or provides /// Search configured source and binary repos by package name or provides
Search { Search(SearchArgs),
/// Search query
query: String,
/// Search repository file lists (binary repo metadata) by path substring
#[arg(long)]
files: bool,
},
/// Show which installed package owns a filesystem path /// Show which installed package owns a filesystem path
Owns { Owns(OwnsArgs),
/// Path to query (absolute or relative to rootfs)
path: PathBuf,
},
/// List installed packages /// List installed packages
List, List(ListArgs),
/// Create a detached minisign signature for a .zst file /// Create a detached minisign signature for a .zst file
Sign { Sign(SignArgs),
/// One or more .zst files to sign
#[arg(value_name = "FILE", required = true, num_args = 1..)]
files: Vec<PathBuf>,
},
/// Repository management /// Repository management
Repo { Repo(RepoArgs),
#[command(subcommand)]
command: RepoCommands,
},
/// Show current configuration /// Show current configuration
Config, Config(ConfigArgs),
/// Generate shell completion scripts and a man page into an output directory. /// Generate shell completion scripts and a man page into an output directory.
#[command(hide = true)] #[command(hide = true)]
GenerateArtifacts { GenerateArtifacts(GenerateArtifactsArgs),
/// Output directory for generated files
#[arg(long, value_name = "DIR")]
out_dir: PathBuf,
},
/// Create a new package specification interactively /// Create a new package specification interactively
MakeSpec { MakeSpec(MakeSpecArgs),
/// Output file path (defaults to <name>.toml)
#[arg(short, long)]
output: Option<PathBuf>,
},
#[command(hide = true)] #[command(hide = true)]
Internal { Internal(InternalArgs),
#[command(subcommand)]
command: InternalCommands,
},
} }
#[derive(Subcommand)] #[derive(Subcommand, Debug, Clone)]
pub enum InternalCommands { pub enum InternalCommands {
#[command(hide = true)] #[command(hide = true)]
PythonBuild { PythonBuild {
@@ -189,23 +303,38 @@ pub enum InternalCommands {
}, },
} }
#[derive(Subcommand)] #[derive(Debug, Clone, Args)]
pub struct RepoCommonArgs {
#[command(flatten)]
pub rootfs_args: RootfsArgs,
}
#[derive(Subcommand, Debug, Clone)]
pub enum RepoCommands { pub enum RepoCommands {
/// Create a repository database from a directory of packages /// Create a repository database from a directory of packages
Create { Create {
#[command(flatten)]
args: RepoCommonArgs,
/// Directory containing .depot.pkg.tar.zst files /// Directory containing .depot.pkg.tar.zst files
#[arg(default_value = ".")] #[arg(default_value = ".")]
dir: PathBuf, dir: PathBuf,
}, },
/// Sync git mirrors configured in /etc/depot.d/mirrors.toml into /usr/src/depot /// Sync git mirrors configured in /etc/depot.d/mirrors.toml into /usr/src/depot
Sync, Sync {
#[command(flatten)]
args: RepoCommonArgs,
},
/// Sync source repos configured in /etc/depot.d/repos.toml into /usr/src/depot /// Sync source repos configured in /etc/depot.d/repos.toml into /usr/src/depot
Update { Update {
#[command(flatten)]
args: RepoCommonArgs,
/// Update only one source repo by name /// Update only one source repo by name
name: Option<String>, name: Option<String>,
}, },
/// Create/update a source index at the root of a source repo /// Create/update a source index at the root of a source repo
Index { Index {
#[command(flatten)]
args: RepoCommonArgs,
/// Source repository root directory /// Source repository root directory
#[arg(default_value = ".")] #[arg(default_value = ".")]
dir: PathBuf, dir: PathBuf,
@@ -214,9 +343,14 @@ pub enum RepoCommands {
subdirs: Vec<String>, subdirs: Vec<String>,
}, },
/// List configured source and binary repos /// List configured source and binary repos
List, List {
#[command(flatten)]
args: RepoCommonArgs,
},
/// Add or update a repo entry in /etc/depot.d/repos.toml /// Add or update a repo entry in /etc/depot.d/repos.toml
Add { Add {
#[command(flatten)]
args: RepoCommonArgs,
/// Repo name (e.g. vertex) /// Repo name (e.g. vertex)
name: String, name: String,
/// Source git URL or binary repo base URL /// Source git URL or binary repo base URL
@@ -245,6 +379,8 @@ pub enum RepoCommands {
}, },
/// Remove a repo entry from /etc/depot.d/repos.toml /// Remove a repo entry from /etc/depot.d/repos.toml
Remove { Remove {
#[command(flatten)]
args: RepoCommonArgs,
/// Repo name /// Repo name
name: String, name: String,
/// Repo kind (auto-detect if unique) /// Repo kind (auto-detect if unique)
@@ -253,6 +389,8 @@ pub enum RepoCommands {
}, },
/// Enable a repo entry in /etc/depot.d/repos.toml /// Enable a repo entry in /etc/depot.d/repos.toml
Enable { Enable {
#[command(flatten)]
args: RepoCommonArgs,
/// Repo name /// Repo name
name: String, name: String,
/// Repo kind (auto-detect if unique) /// Repo kind (auto-detect if unique)
@@ -261,6 +399,8 @@ pub enum RepoCommands {
}, },
/// Disable a repo entry in /etc/depot.d/repos.toml /// Disable a repo entry in /etc/depot.d/repos.toml
Disable { Disable {
#[command(flatten)]
args: RepoCommonArgs,
/// Repo name /// Repo name
name: String, name: String,
/// Repo kind (auto-detect if unique) /// Repo kind (auto-detect if unique)
@@ -269,11 +409,16 @@ pub enum RepoCommands {
}, },
/// Query binary repo metadata for the package that owns a file path /// Query binary repo metadata for the package that owns a file path
Owns { Owns {
#[command(flatten)]
args: RepoCommonArgs,
/// Path to query (absolute or relative install path) /// Path to query (absolute or relative install path)
path: PathBuf, path: PathBuf,
}, },
/// Show status of configured git mirrors /// Show status of configured git mirrors
Status, Status {
#[command(flatten)]
args: RepoCommonArgs,
},
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
@@ -284,18 +429,21 @@ pub enum RepoKindArg {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Cli, Commands}; use super::{
use clap::Parser; BuildArgs, Cli, Commands, InstallArgs, RepoArgs, RepoCommands, SearchArgs, UpdateArgs,
};
use clap::{CommandFactory, Parser};
use std::path::PathBuf; use std::path::PathBuf;
#[test] #[test]
fn install_accepts_multiple_positional_targets() { fn install_accepts_multiple_positional_targets() {
let cli = Cli::try_parse_from(["depot", "install", "base", "linux"]).unwrap(); let cli = Cli::try_parse_from(["depot", "install", "base", "linux"]).unwrap();
match cli.command { match cli.command {
Commands::Install { Commands::Install(InstallArgs {
spec_or_archive, spec_or_archive,
spec, spec,
} => { ..
}) => {
assert!(spec.is_none()); assert!(spec.is_none());
assert_eq!( assert_eq!(
spec_or_archive, spec_or_archive,
@@ -310,10 +458,11 @@ mod tests {
fn install_accepts_spec_flag_without_positional_target() { fn install_accepts_spec_flag_without_positional_target() {
let cli = Cli::try_parse_from(["depot", "install", "--spec", "pkg.toml"]).unwrap(); let cli = Cli::try_parse_from(["depot", "install", "--spec", "pkg.toml"]).unwrap();
match cli.command { match cli.command {
Commands::Install { Commands::Install(InstallArgs {
spec_or_archive, spec_or_archive,
spec, spec,
} => { ..
}) => {
assert!(spec_or_archive.is_empty()); assert!(spec_or_archive.is_empty());
assert_eq!(spec, Some(PathBuf::from("pkg.toml"))); assert_eq!(spec, Some(PathBuf::from("pkg.toml")));
} }
@@ -325,7 +474,7 @@ mod tests {
fn update_accepts_no_package_names() { fn update_accepts_no_package_names() {
let cli = Cli::try_parse_from(["depot", "update"]).unwrap(); let cli = Cli::try_parse_from(["depot", "update"]).unwrap();
match cli.command { match cli.command {
Commands::Update { packages } => assert!(packages.is_empty()), Commands::Update(UpdateArgs { packages, .. }) => assert!(packages.is_empty()),
_ => panic!("expected update command"), _ => panic!("expected update command"),
} }
} }
@@ -334,7 +483,7 @@ mod tests {
fn update_accepts_multiple_package_names() { fn update_accepts_multiple_package_names() {
let cli = Cli::try_parse_from(["depot", "update", "linux", "openssl"]).unwrap(); let cli = Cli::try_parse_from(["depot", "update", "linux", "openssl"]).unwrap();
match cli.command { match cli.command {
Commands::Update { packages } => { Commands::Update(UpdateArgs { packages, .. }) => {
assert_eq!(packages, vec!["linux".to_string(), "openssl".to_string()]) assert_eq!(packages, vec!["linux".to_string(), "openssl".to_string()])
} }
_ => panic!("expected update command"), _ => panic!("expected update command"),
@@ -345,22 +494,25 @@ mod tests {
fn check_accepts_custom_directory() { fn check_accepts_custom_directory() {
let cli = Cli::try_parse_from(["depot", "check", "packages"]).unwrap(); let cli = Cli::try_parse_from(["depot", "check", "packages"]).unwrap();
match cli.command { match cli.command {
Commands::Check { dir } => assert_eq!(dir, PathBuf::from("packages")), Commands::Check(args) => assert_eq!(args.dir, PathBuf::from("packages")),
_ => panic!("expected check command"), _ => panic!("expected check command"),
} }
} }
#[test] #[test]
fn global_test_deps_flag_is_parsed() { fn install_test_deps_flag_is_parsed_for_install() {
let cli = Cli::try_parse_from(["depot", "--test-deps", "install", "foo"]).unwrap(); let cli = Cli::try_parse_from(["depot", "install", "--test-deps", "foo"]).unwrap();
assert!(cli.test_deps); match cli.command {
Commands::Install(args) => assert!(args.build_exec_args.test_deps),
_ => panic!("expected install command"),
}
} }
#[test] #[test]
fn build_install_deps_flag_is_parsed() { fn build_install_deps_flag_is_parsed() {
let cli = Cli::try_parse_from(["depot", "build", "--install-deps", "pkg.toml"]).unwrap(); let cli = Cli::try_parse_from(["depot", "build", "--install-deps", "pkg.toml"]).unwrap();
match cli.command { match cli.command {
Commands::Build { install_deps, .. } => assert!(install_deps), Commands::Build(BuildArgs { install_deps, .. }) => assert!(install_deps),
_ => panic!("expected build command"), _ => panic!("expected build command"),
} }
} }
@@ -369,8 +521,78 @@ mod tests {
fn build_cleanup_deps_flag_is_parsed() { fn build_cleanup_deps_flag_is_parsed() {
let cli = Cli::try_parse_from(["depot", "build", "--cleanup-deps", "pkg.toml"]).unwrap(); let cli = Cli::try_parse_from(["depot", "build", "--cleanup-deps", "pkg.toml"]).unwrap();
match cli.command { match cli.command {
Commands::Build { cleanup_deps, .. } => assert!(cleanup_deps), Commands::Build(BuildArgs { cleanup_deps, .. }) => assert!(cleanup_deps),
_ => panic!("expected build command"), _ => panic!("expected build command"),
} }
} }
#[test]
fn repo_help_does_not_show_build_only_flags() {
let mut cmd = Cli::command();
let help = cmd
.find_subcommand_mut("repo")
.expect("repo subcommand")
.render_help()
.to_string();
assert!(!help.contains("--no-deps"));
assert!(!help.contains("--no-flags"));
assert!(!help.contains("--clean"));
assert!(!help.contains("--test-deps"));
assert!(!help.contains("--lib32-only"));
}
#[test]
fn repo_create_help_only_shows_repo_options() {
let mut cmd = Cli::command();
let repo = cmd.find_subcommand_mut("repo").expect("repo subcommand");
let help = repo
.find_subcommand_mut("create")
.expect("repo create subcommand")
.render_help()
.to_string();
assert!(help.contains("--rootfs"));
assert!(!help.contains("--no-deps"));
assert!(!help.contains("--test-deps"));
}
#[test]
fn search_help_shows_only_search_options() {
let mut cmd = Cli::command();
let help = cmd
.find_subcommand_mut("search")
.expect("search subcommand")
.render_help()
.to_string();
assert!(help.contains("--rootfs"));
assert!(help.contains("--files"));
assert!(!help.contains("--no-deps"));
}
#[test]
fn repo_command_parses_nested_subcommand() {
let cli = Cli::try_parse_from(["depot", "repo", "status"]).unwrap();
match cli.command {
Commands::Repo(RepoArgs {
command: RepoCommands::Status { .. },
}) => {}
_ => panic!("expected repo status command"),
}
}
#[test]
fn search_parses_rootfs_locally() {
let cli = Cli::try_parse_from(["depot", "search", "-r", "/tmp/root", "llvm"]).unwrap();
match cli.command {
Commands::Search(SearchArgs {
rootfs_args, query, ..
}) => {
assert_eq!(rootfs_args.rootfs, PathBuf::from("/tmp/root"));
assert_eq!(query, "llvm");
}
_ => panic!("expected search command"),
}
}
} }
+377 -208
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -258,9 +258,14 @@ impl PackageSpec {
/// Return the effective dependency set used by the generated lib32 companion package. /// Return the effective dependency set used by the generated lib32 companion package.
pub fn lib32_dependencies(&self) -> Dependencies { pub fn lib32_dependencies(&self) -> Dependencies {
self.dependencies let mut deps = self
.dependencies
.lib32_dependencies() .lib32_dependencies()
.unwrap_or_else(|| self.dependencies.primary_dependencies()) .unwrap_or_else(|| self.dependencies.primary_dependencies());
if !deps.runtime.iter().any(|dep| dep == &self.package.name) {
deps.runtime.push(self.package.name.clone());
}
deps
} }
/// Return local package names/provided features for the selected output set. /// Return local package names/provided features for the selected output set.
@@ -1873,7 +1878,7 @@ test = ["bats"]
); );
assert_eq!( assert_eq!(
spec.dependencies_for_output("lib32-llvm").runtime, spec.dependencies_for_output("lib32-llvm").runtime,
vec!["lib32-zlib".to_string()] vec!["lib32-zlib".to_string(), "llvm".to_string()]
); );
assert_eq!( assert_eq!(
spec.dependencies_for_output("lib32-llvm").test, spec.dependencies_for_output("lib32-llvm").test,
+92 -28
View File
@@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
const PUBLIC_KEYS_DIR_REL: &str = "usr/share/depot/keys/public"; const PUBLIC_KEYS_DIR_REL: &str = "usr/share/depot/keys/public";
const SIGN_KEYS_DIR_REL: &str = "usr/share/depot/keys/sign"; const SIGN_KEYS_DIR_REL: &str = "usr/share/depot/keys/sign";
const SIGNING_PASSWORD_ENV: &str = "DEPOT_MINISIGN_PASSWORD"; const SIGNING_PASSWORD_ENV: &str = "DEPOT_MINISIGN_PASSWORD";
const MAX_SIGNING_PASSWORD_ATTEMPTS: usize = 3;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct KeyLocations { pub struct KeyLocations {
@@ -210,9 +211,13 @@ fn load_signing_material(keys: &KeyLocations) -> Result<SigningMaterial> {
} }
fn load_secret_key(signing_key_path: &Path, secret_key_box: SecretKeyBox) -> Result<SecretKey> { fn load_secret_key(signing_key_path: &Path, secret_key_box: SecretKeyBox) -> Result<SecretKey> {
load_secret_key_with_password_override(signing_key_path, secret_key_box, None) match secret_key_box.clone().into_unencrypted_secret_key() {
Ok(sk) => Ok(sk),
Err(_) => load_secret_key_interactive(signing_key_path, secret_key_box),
}
} }
#[cfg(test)]
fn load_secret_key_with_password_override( fn load_secret_key_with_password_override(
signing_key_path: &Path, signing_key_path: &Path,
secret_key_box: SecretKeyBox, secret_key_box: SecretKeyBox,
@@ -223,7 +228,7 @@ fn load_secret_key_with_password_override(
Err(_) => { Err(_) => {
let password = match password_override { let password = match password_override {
Some(password) => password, Some(password) => password,
None => signing_key_password(signing_key_path)?, None => signing_key_password(signing_key_path, 1)?,
}; };
secret_key_box secret_key_box
.into_secret_key(Some(password)) .into_secret_key(Some(password))
@@ -237,9 +242,19 @@ fn load_secret_key_with_password_override(
} }
} }
fn signing_key_password(signing_key_path: &Path) -> Result<String> { fn load_secret_key_interactive(
signing_key_path: &Path,
secret_key_box: SecretKeyBox,
) -> Result<SecretKey> {
if let Some(password) = std::env::var_os(SIGNING_PASSWORD_ENV) { if let Some(password) = std::env::var_os(SIGNING_PASSWORD_ENV) {
return Ok(password.to_string_lossy().into_owned()); return secret_key_box
.into_secret_key(Some(password.to_string_lossy().into_owned()))
.with_context(|| {
format!(
"Failed to load minisign signing key: {}",
signing_key_path.display()
)
});
} }
if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() { if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() {
anyhow::bail!( anyhow::bail!(
@@ -249,9 +264,40 @@ fn signing_key_password(signing_key_path: &Path) -> Result<String> {
); );
} }
let mut last_error = None;
for attempt in 1..=MAX_SIGNING_PASSWORD_ATTEMPTS {
let password = signing_key_password(signing_key_path, attempt)?;
match secret_key_box.clone().into_secret_key(Some(password)) {
Ok(secret_key) => return Ok(secret_key),
Err(err) => {
last_error = Some(err);
if attempt < MAX_SIGNING_PASSWORD_ATTEMPTS {
crate::log_warn!(
"Invalid minisign password for {} ({}/{})",
signing_key_path.display(),
attempt,
MAX_SIGNING_PASSWORD_ATTEMPTS
);
}
}
}
}
Err(last_error.expect("last minisign password error should exist")).with_context(|| {
format!(
"Failed to load minisign signing key after {} attempt(s): {}",
MAX_SIGNING_PASSWORD_ATTEMPTS,
signing_key_path.display()
)
})
}
fn signing_key_password(signing_key_path: &Path, attempt: usize) -> Result<String> {
Password::new(&format!( Password::new(&format!(
"Minisign password for {}:", "Minisign password for {} ({}/{}):",
signing_key_path.display() signing_key_path.display(),
attempt,
MAX_SIGNING_PASSWORD_ATTEMPTS
)) ))
.without_confirmation() .without_confirmation()
.prompt() .prompt()
@@ -344,7 +390,14 @@ pub fn verify_zst_file_detached_with_public_key(
/// Sign one or more `.zst` files with detached minisign signatures written to `<file>.sig`. /// Sign one or more `.zst` files with detached minisign signatures written to `<file>.sig`.
pub fn sign_zst_files_detached(rootfs: &Path, inputs: &[PathBuf]) -> Result<Vec<PathBuf>> { pub fn sign_zst_files_detached(rootfs: &Path, inputs: &[PathBuf]) -> Result<Vec<PathBuf>> {
if inputs.is_empty() { let signable_inputs = collect_signable_inputs(inputs, true)?;
let keys = locate_keys(rootfs)?;
let signing_material = load_signing_material(&keys)?;
sign_signable_inputs_detached(&signable_inputs, &signing_material)
}
fn collect_signable_inputs(inputs: &[PathBuf], require_any: bool) -> Result<Vec<&PathBuf>> {
if inputs.is_empty() && require_any {
anyhow::bail!("No input files provided for signing"); anyhow::bail!("No input files provided for signing");
} }
@@ -364,40 +417,51 @@ pub fn sign_zst_files_detached(rootfs: &Path, inputs: &[PathBuf]) -> Result<Vec<
} }
signable_inputs.push(input); signable_inputs.push(input);
} }
if signable_inputs.is_empty() {
if signable_inputs.is_empty() && require_any {
anyhow::bail!("No signable .zst files were provided"); anyhow::bail!("No signable .zst files were provided");
} }
let keys = locate_keys(rootfs)?; Ok(signable_inputs)
let signing_material = load_signing_material(&keys)?; }
let mut sig_paths = Vec::with_capacity(signable_inputs.len());
fn sign_signable_inputs_detached(
signable_inputs: &[&PathBuf],
signing_material: &SigningMaterial,
) -> Result<Vec<PathBuf>> {
let mut sig_paths = Vec::with_capacity(signable_inputs.len());
for input in signable_inputs { for input in signable_inputs {
let sig_path = detached_sig_path(input); let sig_path = detached_sig_path(input);
sign_detached_with_material(input, &sig_path, &signing_material)?; sign_detached_with_material(input, &sig_path, signing_material)?;
sig_paths.push(sig_path); sig_paths.push(sig_path);
} }
Ok(sig_paths) Ok(sig_paths)
} }
/// Attempt to sign one or more `.zst` files using discovered keys; skip if no signing key exists.
pub fn auto_sign_zst_files_detached(rootfs: &Path, inputs: &[PathBuf]) -> Result<Vec<PathBuf>> {
let signable_inputs = collect_signable_inputs(inputs, false)?;
if signable_inputs.is_empty() {
return Ok(Vec::new());
}
let keys = locate_keys(rootfs)?;
if keys.signing_key.is_none() {
crate::log_info!("No minisign signing key found; skipping detached signatures");
return Ok(Vec::new());
}
let signing_material = load_signing_material(&keys)?;
sign_signable_inputs_detached(&signable_inputs, &signing_material)
}
/// Attempt to sign a `.zst` file using discovered keys; skip if no signing key exists. /// Attempt to sign a `.zst` file using discovered keys; skip if no signing key exists.
pub fn auto_sign_zst_file_detached(rootfs: &Path, input: &Path) -> Result<Option<PathBuf>> { pub fn auto_sign_zst_file_detached(rootfs: &Path, input: &Path) -> Result<Option<PathBuf>> {
if !is_zst_file(input) { Ok(
return Ok(None); auto_sign_zst_files_detached(rootfs, &[input.to_path_buf()])?
} .into_iter()
let keys = locate_keys(rootfs)?; .next(),
if keys.signing_key.is_none() { )
crate::log_info!(
"No minisign signing key found; skipping detached signature for {}",
input.display()
);
return Ok(None);
}
let signing_material = load_signing_material(&keys)?;
let sig_path = detached_sig_path(input);
sign_detached_with_material(input, &sig_path, &signing_material)?;
Ok(Some(sig_path))
} }
#[cfg(test)] #[cfg(test)]
+248 -5
View File
@@ -203,6 +203,30 @@ fn has_known_compressed_suffix(path: &str) -> bool {
|| lower.ends_with(".z") || lower.ends_with(".z")
} }
fn logical_payload_rel_path(destdir: &Path, path: &Path) -> Result<Option<String>> {
let rel = path
.strip_prefix(destdir)
.context("Failed to strip destdir prefix during path normalization")?;
let output_root = Path::new(INTERNAL_OUTPUTS_DIR);
if !rel.starts_with(output_root) {
let rel = rel.to_string_lossy().to_string();
if rel.is_empty() {
return Ok(None);
}
return Ok(Some(rel));
}
let mut comps = rel.components();
let _internal = comps.next();
let _outputs = comps.next();
let _package = comps.next();
let logical = comps.as_path();
if logical.as_os_str().is_empty() {
return Ok(None);
}
Ok(Some(logical.to_string_lossy().to_string()))
}
fn is_manpage_rel_path(rel_path: &str) -> bool { fn is_manpage_rel_path(rel_path: &str) -> bool {
let rel = rel_path.trim_start_matches('/'); let rel = rel_path.trim_start_matches('/');
rel.starts_with("usr/share/man/") && !has_known_compressed_suffix(rel) rel.starts_with("usr/share/man/") && !has_known_compressed_suffix(rel)
@@ -497,11 +521,9 @@ fn compress_manpages_zstd(destdir: &Path) -> Result<usize> {
for entry in WalkDir::new(destdir).into_iter().filter_map(|e| e.ok()) { for entry in WalkDir::new(destdir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path().to_path_buf(); let path = entry.path().to_path_buf();
let rel = path let Some(rel) = logical_payload_rel_path(destdir, &path)? else {
.strip_prefix(destdir) continue;
.context("Failed to strip destdir prefix during manpage compression")? };
.to_string_lossy()
.to_string();
if !is_manpage_rel_path(&rel) { if !is_manpage_rel_path(&rel) {
continue; continue;
} }
@@ -606,6 +628,149 @@ fn compress_manpages_zstd(destdir: &Path) -> Result<usize> {
Ok(compressed) Ok(compressed)
} }
fn normalized_licenses(licenses: &[String]) -> Vec<String> {
let mut normalized: Vec<String> = licenses
.iter()
.map(|license| license.trim().to_string())
.collect();
normalized.sort();
normalized.dedup();
normalized
}
fn licenses_match(left: &[String], right: &[String]) -> bool {
normalized_licenses(left) == normalized_licenses(right)
}
fn supplemental_output_license_targets(
spec: &PackageSpec,
destdir: &Path,
) -> Vec<(crate::package::PackageInfo, PathBuf)> {
let declared_names: HashSet<String> = spec
.outputs()
.into_iter()
.map(|output| output.name)
.collect();
let mut targets = Vec::new();
let mut seen = HashSet::new();
for output in spec.outputs() {
if output.name != spec.package.name {
let out_destdir = output_staging_dir(destdir, &output.name);
if out_destdir.exists() && seen.insert(output.name.clone()) {
targets.push((output.clone(), out_destdir));
}
}
if !spec.build.flags.split_docs || output.name.ends_with("-docs") {
continue;
}
let docs_pkg = spec.docs_package_for_output(&output);
if declared_names.contains(&docs_pkg.name) && !seen.contains(&docs_pkg.name) {
let docs_destdir = output_staging_dir(destdir, &docs_pkg.name);
if docs_destdir.exists() && seen.insert(docs_pkg.name.clone()) {
targets.push((docs_pkg, docs_destdir));
}
continue;
}
let docs_destdir = output_staging_dir(destdir, &docs_pkg.name);
if docs_destdir.exists() && seen.insert(docs_pkg.name.clone()) {
targets.push((docs_pkg, docs_destdir));
}
}
targets
}
/// Stage license payloads for split package outputs.
///
/// Outputs that share the primary package license reuse the primary license directory via symlink.
/// Outputs with distinct metadata licenses receive copied license files from `src_dir`.
pub fn stage_split_package_licenses(
src_dir: &Path,
destdir: &Path,
spec: &PackageSpec,
) -> Result<()> {
let primary_license_dir = destdir.join("usr/share/licenses").join(&spec.package.name);
if !primary_license_dir.exists() {
return Ok(());
}
for (output, output_destdir) in supplemental_output_license_targets(spec, destdir) {
if licenses_match(&output.license, &spec.package.license) {
symlink_package_license(&output_destdir, &output.name, &spec.package.name)?;
} else {
add_licenses(src_dir, &output_destdir, &output.name)?;
}
}
Ok(())
}
/// Stage a package license directory as a symlink to another package's license directory.
pub fn symlink_package_license(destdir: &Path, pkgname: &str, target_pkgname: &str) -> Result<()> {
let licenses_dir = destdir.join("usr/share/licenses");
fs::create_dir_all(&licenses_dir)
.with_context(|| format!("Failed to create {}", licenses_dir.display()))?;
let link_path = licenses_dir.join(pkgname);
match link_path.symlink_metadata() {
Ok(metadata) => {
if metadata.file_type().is_symlink()
&& fs::read_link(&link_path)? == Path::new(target_pkgname)
{
return Ok(());
}
if metadata.file_type().is_dir() {
fs::remove_dir_all(&link_path).with_context(|| {
format!(
"Failed to remove existing license dir {}",
link_path.display()
)
})?;
} else {
fs::remove_file(&link_path).with_context(|| {
format!(
"Failed to remove existing license path {}",
link_path.display()
)
})?;
}
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err).with_context(|| {
format!(
"Failed to inspect existing license path {}",
link_path.display()
)
});
}
}
#[cfg(unix)]
{
std::os::unix::fs::symlink(target_pkgname, &link_path).with_context(|| {
format!(
"Failed to create package license symlink {} -> {}",
link_path.display(),
target_pkgname
)
})?;
Ok(())
}
#[cfg(not(unix))]
{
anyhow::bail!(
"Package license symlinks are only supported on unix hosts: {}",
link_path.display()
);
}
}
/// Process staged files - remove .la files/static libs, strip binaries, etc. /// Process staged files - remove .la files/static libs, strip binaries, etc.
pub fn process(destdir: &Path, spec: &PackageSpec) -> Result<()> { pub fn process(destdir: &Path, spec: &PackageSpec) -> Result<()> {
crate::log_info!("Processing staged files..."); crate::log_info!("Processing staged files...");
@@ -1132,6 +1297,7 @@ pub fn install_atomic(
mod tests { mod tests {
use super::*; use super::*;
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec}; use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec};
use std::io::Read;
fn mk_spec_for_stage_processing() -> PackageSpec { fn mk_spec_for_stage_processing() -> PackageSpec {
let flags = BuildFlags { let flags = BuildFlags {
@@ -1283,6 +1449,83 @@ mod tests {
assert!(!lic_dir.join("README").exists()); assert!(!lic_dir.join("README").exists());
} }
#[test]
fn compress_manpages_zstd_detects_split_output_payload_paths() {
let tmp = tempfile::tempdir().unwrap();
let dest = tmp.path().join("dest");
let page = output_staging_dir(&dest, "clang").join("usr/share/man/man1/clang.1");
std::fs::create_dir_all(page.parent().unwrap()).unwrap();
std::fs::write(&page, b"clang manpage\n").unwrap();
let count = compress_manpages_zstd(&dest).unwrap();
assert_eq!(count, 1);
assert!(!page.exists());
let compressed = page.with_extension("1.zst");
assert!(compressed.exists());
let encoded = std::fs::read(&compressed).unwrap();
let decoded = zstd::stream::decode_all(std::io::Cursor::new(encoded)).unwrap();
assert_eq!(String::from_utf8(decoded).unwrap(), "clang manpage\n");
}
#[test]
fn stage_split_package_licenses_symlinks_matching_outputs_and_copies_distinct_ones() {
let tmp = tempfile::tempdir().unwrap();
let src_dir = tmp.path().join("src");
let destdir = tmp.path().join("dest");
std::fs::create_dir_all(&src_dir).unwrap();
std::fs::create_dir_all(&destdir).unwrap();
std::fs::write(src_dir.join("LICENSE"), "license text").unwrap();
add_licenses(&src_dir, &destdir, "foo").unwrap();
let mut spec = mk_spec_for_stage_processing();
spec.packages.push(PackageInfo {
name: "foo-dev".into(),
version: "1.0".into(),
revision: 1,
description: "dev".into(),
homepage: "h".into(),
license: vec!["MIT".into()],
});
spec.packages.push(PackageInfo {
name: "foo-extras".into(),
version: "1.0".into(),
revision: 1,
description: "extras".into(),
homepage: "h".into(),
license: vec!["Apache-2.0".into()],
});
let dev_dest = output_staging_dir(&destdir, "foo-dev").join("usr/bin");
let extras_dest = output_staging_dir(&destdir, "foo-extras").join("usr/bin");
std::fs::create_dir_all(&dev_dest).unwrap();
std::fs::create_dir_all(&extras_dest).unwrap();
std::fs::write(dev_dest.join("foo-dev"), "bin").unwrap();
std::fs::write(extras_dest.join("foo-extras"), "bin").unwrap();
stage_split_package_licenses(&src_dir, &destdir, &spec).unwrap();
let dev_license =
output_staging_dir(&destdir, "foo-dev").join("usr/share/licenses/foo-dev");
let dev_meta = std::fs::symlink_metadata(&dev_license).unwrap();
assert!(dev_meta.file_type().is_symlink());
assert_eq!(
std::fs::read_link(&dev_license).unwrap(),
PathBuf::from("foo")
);
let extras_license =
output_staging_dir(&destdir, "foo-extras").join("usr/share/licenses/foo-extras");
let extras_meta = std::fs::symlink_metadata(&extras_license).unwrap();
assert!(extras_meta.is_dir());
let mut text = String::new();
std::fs::File::open(extras_license.join("LICENSE"))
.unwrap()
.read_to_string(&mut text)
.unwrap();
assert_eq!(text, "license text");
}
#[test] #[test]
fn install_atomic_update_and_rollback_restores_state() { fn install_atomic_update_and_rollback_restores_state() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();