feat: add support for package groups in dependency management
- Introduced a new `groups` field in the package specification to categorize packages. - Updated various functions to handle package groups, including parsing, registration, and querying. - Enhanced database schema to store package group information. - Modified the interactive package creation process to allow users to specify package groups. - Implemented tests to verify the correct handling of package groups in both source and binary repositories. - Updated the output formatting to include package group information where applicable.
This commit is contained in:
Generated
+1
-1
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.33.0"
|
version = "0.33.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ar",
|
"ar",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.33.0"
|
version = "0.33.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'depot',
|
'depot',
|
||||||
version: '0.33.0',
|
version: '0.33.1',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
default_options: ['buildtype=release'],
|
default_options: ['buildtype=release'],
|
||||||
)
|
)
|
||||||
|
|||||||
+295
-4
@@ -727,6 +727,7 @@ fn package_spec_from_archive_metadata(metadata: &toml::Value) -> package::Packag
|
|||||||
runtime: parse_dependency_list(metadata, "runtime"),
|
runtime: parse_dependency_list(metadata, "runtime"),
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: parse_dependency_list(metadata, "optional"),
|
optional: parse_dependency_list(metadata, "optional"),
|
||||||
|
groups: parse_dependency_list(metadata, "groups"),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -811,6 +812,7 @@ fn package_spec_from_repo_record(
|
|||||||
runtime: record.runtime_dependencies.clone(),
|
runtime: record.runtime_dependencies.clone(),
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: record.optional_dependencies.clone(),
|
optional: record.optional_dependencies.clone(),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -2275,6 +2277,172 @@ fn scan_package_specs(dir: &Path) -> Result<Vec<PathBuf>> {
|
|||||||
Ok(specs)
|
Ok(specs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spec_group_package_names(spec: &package::PackageSpec, group: &str) -> Vec<String> {
|
||||||
|
let mut names = Vec::new();
|
||||||
|
for output in spec.outputs() {
|
||||||
|
if spec
|
||||||
|
.dependencies_for_output(&output.name)
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| candidate.eq_ignore_ascii_case(group))
|
||||||
|
{
|
||||||
|
names.push(output.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if spec.builds_lib32_output() {
|
||||||
|
let lib32_name = spec.lib32_package_name();
|
||||||
|
if spec
|
||||||
|
.dependencies_for_output(&lib32_name)
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| candidate.eq_ignore_ascii_case(group))
|
||||||
|
{
|
||||||
|
names.push(lib32_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names.sort();
|
||||||
|
names.dedup();
|
||||||
|
names
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_source_group_package_names(config: &config::Config, group: &str) -> Result<Vec<String>> {
|
||||||
|
if !config.repo_clone_dir.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut packages = BTreeSet::new();
|
||||||
|
for spec_path in scan_package_specs(&config.repo_clone_dir)? {
|
||||||
|
let spec = package::PackageSpec::from_file(&spec_path)
|
||||||
|
.with_context(|| format!("Failed to parse spec {}", spec_path.display()))?;
|
||||||
|
for package_name in spec_group_package_names(&spec, group) {
|
||||||
|
packages.insert(package_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(packages.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_binary_group_package_names(
|
||||||
|
config: &config::Config,
|
||||||
|
rootfs: &Path,
|
||||||
|
group: &str,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
let host_arch = std::env::consts::ARCH;
|
||||||
|
let mut binary_repos: Vec<_> = config
|
||||||
|
.binary_repos
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, repo)| repo.enabled && repo.supports_arch(host_arch))
|
||||||
|
.collect();
|
||||||
|
binary_repos.sort_by(|a, b| a.1.priority.cmp(&b.1.priority).then_with(|| a.0.cmp(b.0)));
|
||||||
|
|
||||||
|
let mut packages = BTreeSet::new();
|
||||||
|
for (repo_name, repo_cfg) in binary_repos {
|
||||||
|
let matches = db::repo::find_binary_repo_packages_by_group(
|
||||||
|
repo_name,
|
||||||
|
repo_cfg,
|
||||||
|
rootfs,
|
||||||
|
&config.package_cache_dir,
|
||||||
|
group,
|
||||||
|
)?;
|
||||||
|
for record in matches {
|
||||||
|
packages.insert(record.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(packages.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_install_group_package_names(
|
||||||
|
config: &config::Config,
|
||||||
|
rootfs: &Path,
|
||||||
|
group: &str,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
let mut packages = BTreeSet::new();
|
||||||
|
for package_name in collect_source_group_package_names(config, group)? {
|
||||||
|
packages.insert(package_name);
|
||||||
|
}
|
||||||
|
for package_name in collect_binary_group_package_names(config, rootfs, group)? {
|
||||||
|
packages.insert(package_name);
|
||||||
|
}
|
||||||
|
Ok(packages.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_install_requests_for_groups(
|
||||||
|
config: &config::Config,
|
||||||
|
rootfs: &Path,
|
||||||
|
requests: &[PathBuf],
|
||||||
|
) -> Result<(Vec<PathBuf>, Vec<String>)> {
|
||||||
|
let mut expanded = Vec::new();
|
||||||
|
let mut explicit_groups = Vec::new();
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
|
||||||
|
for request in requests {
|
||||||
|
if request.exists() {
|
||||||
|
let key = request.to_string_lossy().to_string();
|
||||||
|
if seen.insert(key) {
|
||||||
|
expanded.push(request.clone());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_name = request.to_string_lossy().to_string();
|
||||||
|
let group_packages = collect_install_group_package_names(config, rootfs, &request_name)?;
|
||||||
|
if group_packages.is_empty() {
|
||||||
|
if seen.insert(request_name.clone()) {
|
||||||
|
expanded.push(request.clone());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::info(format!(
|
||||||
|
"Expanding group '{}' ({} packages)...",
|
||||||
|
request_name,
|
||||||
|
group_packages.len()
|
||||||
|
));
|
||||||
|
explicit_groups.push(request_name);
|
||||||
|
for package_name in group_packages {
|
||||||
|
if seen.insert(package_name.clone()) {
|
||||||
|
expanded.push(PathBuf::from(package_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((expanded, explicit_groups))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_installed_group_targets(
|
||||||
|
db_path: &Path,
|
||||||
|
requests: &[String],
|
||||||
|
) -> Result<(Vec<String>, Vec<String>)> {
|
||||||
|
let mut expanded = Vec::new();
|
||||||
|
let mut explicit_groups = Vec::new();
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
|
||||||
|
for request in requests {
|
||||||
|
if db::is_installed_group(db_path, request)? {
|
||||||
|
let group_packages = db::get_packages_in_installed_group(db_path, request)?;
|
||||||
|
if !group_packages.is_empty() {
|
||||||
|
ui::info(format!(
|
||||||
|
"Expanding group '{}' ({} packages)...",
|
||||||
|
request,
|
||||||
|
group_packages.len()
|
||||||
|
));
|
||||||
|
explicit_groups.push(request.clone());
|
||||||
|
for package_name in group_packages {
|
||||||
|
if seen.insert(package_name.clone()) {
|
||||||
|
expanded.push(package_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen.insert(request.clone()) {
|
||||||
|
expanded.push(request.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((expanded, explicit_groups))
|
||||||
|
}
|
||||||
|
|
||||||
fn human_bytes(bytes: u64) -> String {
|
fn human_bytes(bytes: u64) -> String {
|
||||||
const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
|
const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||||
let mut value = bytes as f64;
|
let mut value = bytes as f64;
|
||||||
@@ -5094,6 +5262,8 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
// Load configuration early so we can use configured repos/paths.
|
// Load configuration early so we can use configured repos/paths.
|
||||||
let config = config::Config::for_rootfs(&rootfs);
|
let config = config::Config::for_rootfs(&rootfs);
|
||||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||||
|
let (install_requests, explicit_groups) =
|
||||||
|
expand_install_requests_for_groups(&config, &rootfs, &install_requests)?;
|
||||||
let install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
let install_test_deps = install_test_deps_enabled(cli_test_deps, &config);
|
||||||
let mut planned_targets = Vec::new();
|
let mut planned_targets = Vec::new();
|
||||||
let mut planned_spec_paths = Vec::new();
|
let mut planned_spec_paths = Vec::new();
|
||||||
@@ -5192,6 +5362,10 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
install::scripts::run_deferred_hooks_if_possible(&rootfs)?;
|
install::scripts::run_deferred_hooks_if_possible(&rootfs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !dry_run && !explicit_groups.is_empty() {
|
||||||
|
db::record_installed_groups(&config.installed_db_path(&rootfs), &explicit_groups)?;
|
||||||
|
}
|
||||||
|
|
||||||
if clean && (ran_plan_mode || ran_direct_install) {
|
if clean && (ran_plan_mode || ran_direct_install) {
|
||||||
clean_build_workspace(&config)?;
|
clean_build_workspace(&config)?;
|
||||||
}
|
}
|
||||||
@@ -5207,11 +5381,18 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
let remove_lock_path = locking::lock_path(&config);
|
let remove_lock_path = locking::lock_path(&config);
|
||||||
let _remove_lock_guard =
|
let _remove_lock_guard =
|
||||||
locking::try_write(&mut remove_lock, &remove_lock_path, "remove")?;
|
locking::try_write(&mut remove_lock, &remove_lock_path, "remove")?;
|
||||||
let removal_targets = vec![package.clone()];
|
let db_path = config.installed_db_path(&rootfs);
|
||||||
|
let (removal_targets, explicit_groups) =
|
||||||
|
expand_installed_group_targets(&db_path, std::slice::from_ref(&package))?;
|
||||||
if !ui::prompt_package_action("removal", &removal_targets, true)? {
|
if !ui::prompt_package_action("removal", &removal_targets, true)? {
|
||||||
anyhow::bail!("Aborted");
|
anyhow::bail!("Aborted");
|
||||||
}
|
}
|
||||||
remove_installed_package_with_hooks(&package, &rootfs, &config)?;
|
for target in &removal_targets {
|
||||||
|
remove_installed_package_with_hooks(target, &rootfs, &config)?;
|
||||||
|
}
|
||||||
|
for group in explicit_groups {
|
||||||
|
db::remove_installed_group(&db_path, &group)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Commands::Build(BuildArgs {
|
Commands::Build(BuildArgs {
|
||||||
rootfs_args,
|
rootfs_args,
|
||||||
@@ -5764,11 +5945,16 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
let dry_run = build_exec_args.dry_run;
|
let dry_run = build_exec_args.dry_run;
|
||||||
let config = config::Config::for_rootfs(&rootfs);
|
let config = config::Config::for_rootfs(&rootfs);
|
||||||
sync_source_repositories_for_update(&config)?;
|
sync_source_repositories_for_update(&config)?;
|
||||||
if !is_explicit_depot_self_update_request(&packages) {
|
let expanded_packages = if packages.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
expand_installed_group_targets(&config.installed_db_path(&rootfs), &packages)?.0
|
||||||
|
};
|
||||||
|
if !is_explicit_depot_self_update_request(&expanded_packages) {
|
||||||
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
ensure_depot_self_update_not_required(&config, &rootfs)?;
|
||||||
}
|
}
|
||||||
run_update_command(
|
run_update_command(
|
||||||
&packages,
|
&expanded_packages,
|
||||||
&config,
|
&config,
|
||||||
UpdateCommandOptions {
|
UpdateCommandOptions {
|
||||||
rootfs: &rootfs,
|
rootfs: &rootfs,
|
||||||
@@ -6419,6 +6605,7 @@ mod tests {
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6679,6 +6866,7 @@ optional = []
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: vec!["glibc".into()],
|
runtime_dependencies: vec!["glibc".into()],
|
||||||
optional_dependencies: vec!["manpages".into()],
|
optional_dependencies: vec!["manpages".into()],
|
||||||
|
groups: vec!["base".into()],
|
||||||
};
|
};
|
||||||
let spec = package_spec_from_repo_record(&record);
|
let spec = package_spec_from_repo_record(&record);
|
||||||
let installed =
|
let installed =
|
||||||
@@ -6981,6 +7169,7 @@ optional = []
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
};
|
};
|
||||||
let spec = package_spec_from_repo_record(&record);
|
let spec = package_spec_from_repo_record(&record);
|
||||||
let installed =
|
let installed =
|
||||||
@@ -7520,6 +7709,7 @@ optional = []
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)]);
|
)]);
|
||||||
@@ -8818,6 +9008,7 @@ file = "missing.patch"
|
|||||||
runtime: Vec::new(),
|
runtime: Vec::new(),
|
||||||
test: vec!["lib32-pytest".into()],
|
test: vec!["lib32-pytest".into()],
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(!should_install_test_deps(
|
assert!(!should_install_test_deps(
|
||||||
@@ -9118,6 +9309,7 @@ optional = []
|
|||||||
runtime: vec!["lib32-zlib".into()],
|
runtime: vec!["lib32-zlib".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: vec!["lib32-gtk-doc".into()],
|
optional: vec!["lib32-gtk-doc".into()],
|
||||||
|
groups: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let lib32 = make_lib32_package_spec(&base);
|
let lib32 = make_lib32_package_spec(&base);
|
||||||
@@ -9138,4 +9330,103 @@ optional = []
|
|||||||
deps::RequestedOutputs::Lib32Only
|
deps::RequestedOutputs::Lib32Only
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_install_requests_for_groups_uses_source_specs() -> Result<()> {
|
||||||
|
let temp = tempfile::tempdir()?;
|
||||||
|
let rootfs = temp.path().join("rootfs");
|
||||||
|
let repo_root = temp.path().join("repos");
|
||||||
|
let core = repo_root.join("core").join("foo");
|
||||||
|
let desktop = repo_root.join("desktop").join("bar");
|
||||||
|
fs::create_dir_all(&rootfs)?;
|
||||||
|
fs::create_dir_all(&core)?;
|
||||||
|
fs::create_dir_all(&desktop)?;
|
||||||
|
|
||||||
|
let foo_spec = core.join("foo.toml");
|
||||||
|
fs::write(
|
||||||
|
&foo_spec,
|
||||||
|
r#"[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "1.0.0"
|
||||||
|
revision = 1
|
||||||
|
description = "foo"
|
||||||
|
homepage = "https://example.test/foo"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[[source]]
|
||||||
|
url = "https://example.test/foo-1.0.0.tar.gz"
|
||||||
|
sha256 = "skip"
|
||||||
|
extract_dir = "foo-1.0.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "custom"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
groups = ["base"]
|
||||||
|
runtime = []
|
||||||
|
optional = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let bar_spec = desktop.join("bar.toml");
|
||||||
|
fs::write(
|
||||||
|
&bar_spec,
|
||||||
|
r#"[package]
|
||||||
|
name = "bar"
|
||||||
|
version = "1.0.0"
|
||||||
|
revision = 1
|
||||||
|
description = "bar"
|
||||||
|
homepage = "https://example.test/bar"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[[source]]
|
||||||
|
url = "https://example.test/bar-1.0.0.tar.gz"
|
||||||
|
sha256 = "skip"
|
||||||
|
extract_dir = "bar-1.0.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "custom"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
groups = ["base", "desktop"]
|
||||||
|
runtime = []
|
||||||
|
optional = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut config = config::Config::for_rootfs(&rootfs);
|
||||||
|
config.repo_clone_dir = repo_root;
|
||||||
|
config.binary_repos.clear();
|
||||||
|
|
||||||
|
let (expanded, groups) =
|
||||||
|
expand_install_requests_for_groups(&config, &rootfs, &[PathBuf::from("base")])?;
|
||||||
|
|
||||||
|
assert_eq!(groups, vec!["base".to_string()]);
|
||||||
|
assert_eq!(expanded, vec![PathBuf::from("bar"), PathBuf::from("foo")]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_installed_group_targets_uses_installed_group_membership() -> Result<()> {
|
||||||
|
let temp = tempfile::tempdir()?;
|
||||||
|
let rootfs = temp.path().join("rootfs");
|
||||||
|
fs::create_dir_all(&rootfs)?;
|
||||||
|
let config = config::Config::for_rootfs(&rootfs);
|
||||||
|
let db_path = config.installed_db_path(&rootfs);
|
||||||
|
|
||||||
|
let dest = temp.path().join("dest");
|
||||||
|
fs::create_dir_all(dest.join("usr/bin"))?;
|
||||||
|
fs::write(dest.join("usr/bin/foo"), "foo")?;
|
||||||
|
|
||||||
|
let mut spec = test_package_spec(package::BuildType::Custom, None, &[]);
|
||||||
|
spec.package.name = "foo".into();
|
||||||
|
spec.dependencies.groups = vec!["base".into()];
|
||||||
|
db::register_package(&db_path, &spec, &dest)?;
|
||||||
|
db::record_installed_groups(&db_path, &[String::from("base")])?;
|
||||||
|
|
||||||
|
let (expanded, groups) = expand_installed_group_targets(&db_path, &[String::from("base")])?;
|
||||||
|
assert_eq!(groups, vec!["base".to_string()]);
|
||||||
|
assert_eq!(expanded, vec!["foo".to_string()]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+207
-5
@@ -5,7 +5,7 @@ pub mod repo;
|
|||||||
use crate::package::PackageSpec;
|
use crate::package::PackageSpec;
|
||||||
use crate::staging;
|
use crate::staging;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{Connection, OptionalExtension, params};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
@@ -174,6 +174,10 @@ pub fn register_package_with_replacement(
|
|||||||
"DELETE FROM directories WHERE package_id = ?1",
|
"DELETE FROM directories WHERE package_id = ?1",
|
||||||
params![pkg_id],
|
params![pkg_id],
|
||||||
)?;
|
)?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM package_groups WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
|
|
||||||
// Insert provides
|
// Insert provides
|
||||||
for provides in &spec.alternatives.provides {
|
for provides in &spec.alternatives.provides {
|
||||||
@@ -189,6 +193,12 @@ pub fn register_package_with_replacement(
|
|||||||
params![pkg_id, replaces],
|
params![pkg_id, replaces],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
for group in &spec.dependencies.groups {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT OR IGNORE INTO package_groups (package_id, group_name) VALUES (?1, ?2)",
|
||||||
|
params![pkg_id, group],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Detect ownership conflicts and separate into auto-removable vs fatal.
|
// Detect ownership conflicts and separate into auto-removable vs fatal.
|
||||||
let mut fatal_conflicts: Vec<(String, String)> = Vec::new();
|
let mut fatal_conflicts: Vec<(String, String)> = Vec::new();
|
||||||
@@ -461,6 +471,11 @@ pub fn show_package_info(db_path: &Path, name: &str) -> Result<()> {
|
|||||||
crate::log_info!("Homepage: {}", homepage);
|
crate::log_info!("Homepage: {}", homepage);
|
||||||
crate::log_info!("License: {}", license);
|
crate::log_info!("License: {}", license);
|
||||||
|
|
||||||
|
let groups = get_package_groups(db_path, name)?;
|
||||||
|
if !groups.is_empty() {
|
||||||
|
crate::log_info!("Groups: {}", groups.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
// Count files
|
// Count files
|
||||||
let file_count: i64 = conn.query_row(
|
let file_count: i64 = conn.query_row(
|
||||||
"SELECT COUNT(*) FROM files f JOIN packages p ON f.package_id = p.id WHERE p.name = ?1",
|
"SELECT COUNT(*) FROM files f JOIN packages p ON f.package_id = p.id WHERE p.name = ?1",
|
||||||
@@ -483,17 +498,46 @@ pub fn list_packages(db_path: &Path) -> Result<()> {
|
|||||||
let conn = Connection::open(db_path)?;
|
let conn = Connection::open(db_path)?;
|
||||||
init_db(&conn)?;
|
init_db(&conn)?;
|
||||||
|
|
||||||
let mut stmt = conn.prepare("SELECT name, version FROM packages ORDER BY name")?;
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT
|
||||||
|
p.name,
|
||||||
|
p.version,
|
||||||
|
GROUP_CONCAT(pg.group_name, ',')
|
||||||
|
FROM packages p
|
||||||
|
LEFT JOIN package_groups pg ON pg.package_id = p.id
|
||||||
|
GROUP BY p.id
|
||||||
|
ORDER BY p.name",
|
||||||
|
)?;
|
||||||
let packages = stmt.query_map([], |row| {
|
let packages = stmt.query_map([], |row| {
|
||||||
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
Ok((
|
||||||
|
row.get::<_, String>(0)?,
|
||||||
|
row.get::<_, String>(1)?,
|
||||||
|
row.get::<_, Option<String>>(2)?,
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
crate::log_info!("{:<30} VERSION", "PACKAGE");
|
crate::log_info!("{:<30} VERSION", "PACKAGE");
|
||||||
crate::log_info!("{}", "-".repeat(50));
|
crate::log_info!("{}", "-".repeat(50));
|
||||||
|
|
||||||
for pkg in packages {
|
for pkg in packages {
|
||||||
let (name, version) = pkg?;
|
let (name, version, groups_csv) = pkg?;
|
||||||
crate::log_info!("{:<30} {}", name, version);
|
let groups = groups_csv
|
||||||
|
.map(|csv| {
|
||||||
|
let mut values: Vec<String> = csv
|
||||||
|
.split(',')
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
values.sort();
|
||||||
|
values.dedup();
|
||||||
|
values
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if groups.is_empty() {
|
||||||
|
crate::log_info!("{:<30} {}", name, version);
|
||||||
|
} else {
|
||||||
|
crate::log_info!("{:<30} {} [groups: {}]", name, version, groups.join(", "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -531,6 +575,18 @@ fn init_db(conn: &Connection) -> Result<()> {
|
|||||||
UNIQUE(package_id, replaces_name)
|
UNIQUE(package_id, replaces_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS package_groups (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
package_id INTEGER NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (package_id) REFERENCES packages(id),
|
||||||
|
UNIQUE(package_id, group_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS installed_groups (
|
||||||
|
group_name TEXT PRIMARY KEY NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS files (
|
CREATE TABLE IF NOT EXISTS files (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
package_id INTEGER NOT NULL,
|
package_id INTEGER NOT NULL,
|
||||||
@@ -549,6 +605,7 @@ fn init_db(conn: &Connection) -> Result<()> {
|
|||||||
CREATE INDEX IF NOT EXISTS idx_files_package ON files(package_id);
|
CREATE INDEX IF NOT EXISTS idx_files_package ON files(package_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_provides_name ON provides(provides_name);
|
CREATE INDEX IF NOT EXISTS idx_provides_name ON provides(provides_name);
|
||||||
CREATE INDEX IF NOT EXISTS idx_replaces_name ON replaces(replaces_name);
|
CREATE INDEX IF NOT EXISTS idx_replaces_name ON replaces(replaces_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_package_groups_name ON package_groups(group_name);
|
||||||
CREATE INDEX IF NOT EXISTS idx_directories_package ON directories(package_id);
|
CREATE INDEX IF NOT EXISTS idx_directories_package ON directories(package_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);
|
CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);
|
||||||
",
|
",
|
||||||
@@ -630,6 +687,10 @@ fn delete_package_rows(conn: &Connection, pkg_id: i64) -> Result<()> {
|
|||||||
"DELETE FROM replaces WHERE package_id = ?1",
|
"DELETE FROM replaces WHERE package_id = ?1",
|
||||||
params![pkg_id],
|
params![pkg_id],
|
||||||
)?;
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM package_groups WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
conn.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
conn.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -648,6 +709,10 @@ fn delete_package_rows_tx(tx: &rusqlite::Transaction<'_>, pkg_id: i64) -> Result
|
|||||||
"DELETE FROM replaces WHERE package_id = ?1",
|
"DELETE FROM replaces WHERE package_id = ?1",
|
||||||
params![pkg_id],
|
params![pkg_id],
|
||||||
)?;
|
)?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM package_groups WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
tx.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
tx.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -805,6 +870,101 @@ pub fn get_package_provides(db_path: &Path, name: &str) -> Result<Vec<String>> {
|
|||||||
Ok(rows.filter_map(|row| row.ok()).collect())
|
Ok(rows.filter_map(|row| row.ok()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the group memberships recorded for an installed package.
|
||||||
|
pub fn get_package_groups(db_path: &Path, name: &str) -> Result<Vec<String>> {
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::open(db_path)?;
|
||||||
|
init_db(&conn)?;
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT pg.group_name
|
||||||
|
FROM package_groups pg
|
||||||
|
JOIN packages p ON p.id = pg.package_id
|
||||||
|
WHERE p.name = ?1
|
||||||
|
ORDER BY pg.group_name",
|
||||||
|
)?;
|
||||||
|
let rows = stmt.query_map(params![name], |row| row.get(0))?;
|
||||||
|
Ok(rows.filter_map(|row| row.ok()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the installed package names that belong to an explicitly recorded group.
|
||||||
|
pub fn get_packages_in_installed_group(db_path: &Path, group: &str) -> Result<Vec<String>> {
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::open(db_path)?;
|
||||||
|
init_db(&conn)?;
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT p.name
|
||||||
|
FROM package_groups pg
|
||||||
|
JOIN packages p ON p.id = pg.package_id
|
||||||
|
WHERE lower(pg.group_name) = lower(?1)
|
||||||
|
ORDER BY p.name",
|
||||||
|
)?;
|
||||||
|
let rows = stmt.query_map(params![group], |row| row.get(0))?;
|
||||||
|
Ok(rows.filter_map(|row| row.ok()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true when the named group was explicitly installed by the user.
|
||||||
|
pub fn is_installed_group(db_path: &Path, group: &str) -> Result<bool> {
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::open(db_path)?;
|
||||||
|
init_db(&conn)?;
|
||||||
|
let found = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT 1 FROM installed_groups WHERE lower(group_name) = lower(?1) LIMIT 1",
|
||||||
|
params![group],
|
||||||
|
|row| row.get::<_, i64>(0),
|
||||||
|
)
|
||||||
|
.optional()?
|
||||||
|
.is_some();
|
||||||
|
Ok(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record one or more explicit group installs.
|
||||||
|
pub fn record_installed_groups(db_path: &Path, groups: &[String]) -> Result<()> {
|
||||||
|
if groups.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = db_path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut conn = Connection::open(db_path)?;
|
||||||
|
init_db(&conn)?;
|
||||||
|
let tx = conn.transaction()?;
|
||||||
|
for group in groups {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT OR IGNORE INTO installed_groups (group_name) VALUES (?1)",
|
||||||
|
params![group],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove an explicit installed-group marker.
|
||||||
|
pub fn remove_installed_group(db_path: &Path, group: &str) -> Result<()> {
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = Connection::open(db_path)?;
|
||||||
|
init_db(&conn)?;
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM installed_groups WHERE lower(group_name) = lower(?1)",
|
||||||
|
params![group],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get set of all provided package names (alternatives)
|
/// Get set of all provided package names (alternatives)
|
||||||
pub fn get_all_provides(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
pub fn get_all_provides(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -1235,4 +1395,46 @@ mod tests {
|
|||||||
assert!(replaces.contains("grep"));
|
assert!(replaces.contains("grep"));
|
||||||
assert!(replaces.contains("patch"));
|
assert!(replaces.contains("patch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_package_persists_groups() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let db_path = tmp.path().join("packages.db");
|
||||||
|
let dest = tmp.path().join("dest");
|
||||||
|
std::fs::create_dir_all(dest.join("usr/bin")).unwrap();
|
||||||
|
std::fs::write(dest.join("usr/bin/foo"), "foo").unwrap();
|
||||||
|
|
||||||
|
let mut spec = mk_spec("foo", "1.0");
|
||||||
|
spec.dependencies.groups = vec!["base".into(), "desktop".into()];
|
||||||
|
|
||||||
|
register_package(&db_path, &spec, &dest).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_package_groups(&db_path, "foo").unwrap(),
|
||||||
|
vec!["base".to_string(), "desktop".to_string()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn installed_group_helpers_round_trip_membership() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let db_path = tmp.path().join("packages.db");
|
||||||
|
let dest = tmp.path().join("dest");
|
||||||
|
std::fs::create_dir_all(dest.join("usr/bin")).unwrap();
|
||||||
|
std::fs::write(dest.join("usr/bin/foo"), "foo").unwrap();
|
||||||
|
|
||||||
|
let mut spec = mk_spec("foo", "1.0");
|
||||||
|
spec.dependencies.groups = vec!["base".into()];
|
||||||
|
register_package(&db_path, &spec, &dest).unwrap();
|
||||||
|
|
||||||
|
record_installed_groups(&db_path, &[String::from("base")]).unwrap();
|
||||||
|
assert!(is_installed_group(&db_path, "base").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
get_packages_in_installed_group(&db_path, "base").unwrap(),
|
||||||
|
vec!["foo".to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
remove_installed_group(&db_path, "base").unwrap();
|
||||||
|
assert!(!is_installed_group(&db_path, "base").unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+199
@@ -90,6 +90,7 @@ struct IndexedPackage {
|
|||||||
replaces: Vec<String>,
|
replaces: Vec<String>,
|
||||||
runtime_dependencies: Vec<String>,
|
runtime_dependencies: Vec<String>,
|
||||||
optional_dependencies: Vec<String>,
|
optional_dependencies: Vec<String>,
|
||||||
|
groups: Vec<String>,
|
||||||
archive_files: Vec<String>,
|
archive_files: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ pub struct BinaryRepoPackageRecord {
|
|||||||
pub replaces: Vec<String>,
|
pub replaces: Vec<String>,
|
||||||
pub runtime_dependencies: Vec<String>,
|
pub runtime_dependencies: Vec<String>,
|
||||||
pub optional_dependencies: Vec<String>,
|
pub optional_dependencies: Vec<String>,
|
||||||
|
pub groups: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BinaryRepoPackageRecord {
|
impl BinaryRepoPackageRecord {
|
||||||
@@ -330,6 +332,11 @@ impl RepoManager {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
FOREIGN KEY(package_id) REFERENCES packages(id)
|
FOREIGN KEY(package_id) REFERENCES packages(id)
|
||||||
);
|
);
|
||||||
|
CREATE TABLE groups (
|
||||||
|
package_id INTEGER,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(package_id) REFERENCES packages(id)
|
||||||
|
);
|
||||||
CREATE TABLE files (
|
CREATE TABLE files (
|
||||||
package_id INTEGER,
|
package_id INTEGER,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -348,6 +355,7 @@ impl RepoManager {
|
|||||||
CREATE INDEX idx_replaces_name ON replaces(name);
|
CREATE INDEX idx_replaces_name ON replaces(name);
|
||||||
CREATE INDEX idx_dependencies_name ON dependencies(name);
|
CREATE INDEX idx_dependencies_name ON dependencies(name);
|
||||||
CREATE INDEX idx_dependencies_kind ON dependencies(kind);
|
CREATE INDEX idx_dependencies_kind ON dependencies(kind);
|
||||||
|
CREATE INDEX idx_groups_name ON groups(name);
|
||||||
CREATE INDEX idx_repo_files_path ON files(path);",
|
CREATE INDEX idx_repo_files_path ON files(path);",
|
||||||
)
|
)
|
||||||
.context("Failed to create repo DB indexes")?;
|
.context("Failed to create repo DB indexes")?;
|
||||||
@@ -380,6 +388,7 @@ impl RepoManager {
|
|||||||
let mut replaces = Vec::new();
|
let mut replaces = Vec::new();
|
||||||
let mut runtime_dependencies = Vec::new();
|
let mut runtime_dependencies = Vec::new();
|
||||||
let mut optional_dependencies = Vec::new();
|
let mut optional_dependencies = Vec::new();
|
||||||
|
let mut groups = Vec::new();
|
||||||
let mut archive_files = Vec::new();
|
let mut archive_files = Vec::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -478,6 +487,17 @@ impl RepoManager {
|
|||||||
.map(String::from)
|
.map(String::from)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
if let Some(groups_arr) = metadata
|
||||||
|
.get("dependencies")
|
||||||
|
.and_then(|v| v.get("groups"))
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
{
|
||||||
|
groups = groups_arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_str())
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,6 +546,7 @@ impl RepoManager {
|
|||||||
replaces,
|
replaces,
|
||||||
runtime_dependencies,
|
runtime_dependencies,
|
||||||
optional_dependencies,
|
optional_dependencies,
|
||||||
|
groups,
|
||||||
archive_files,
|
archive_files,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -550,6 +571,7 @@ impl RepoManager {
|
|||||||
replaces,
|
replaces,
|
||||||
runtime_dependencies,
|
runtime_dependencies,
|
||||||
optional_dependencies,
|
optional_dependencies,
|
||||||
|
groups,
|
||||||
archive_files,
|
archive_files,
|
||||||
} = indexed;
|
} = indexed;
|
||||||
|
|
||||||
@@ -608,6 +630,12 @@ impl RepoManager {
|
|||||||
params![package_id, dep],
|
params![package_id, dep],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
for group in groups {
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO groups (package_id, name) VALUES (?1, ?2)",
|
||||||
|
params![package_id, group],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
for file_path in archive_files {
|
for file_path in archive_files {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -1681,6 +1709,26 @@ fn query_package_optional_deps(conn: &Connection, package_id: i64) -> Result<Vec
|
|||||||
Ok(rows.filter_map(|r| r.ok()).collect())
|
Ok(rows.filter_map(|r| r.ok()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn query_package_groups(conn: &Connection, package_id: i64) -> Result<Vec<String>> {
|
||||||
|
let has_groups_table: bool = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='groups'",
|
||||||
|
[],
|
||||||
|
|r| {
|
||||||
|
let n: i64 = r.get(0)?;
|
||||||
|
Ok(n > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !has_groups_table {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stmt = conn.prepare("SELECT name FROM groups WHERE package_id = ?1 ORDER BY name")?;
|
||||||
|
let rows = stmt.query_map(params![package_id], |row| row.get(0))?;
|
||||||
|
Ok(rows.filter_map(|r| r.ok()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
fn find_cached_binary_repo_packages(
|
fn find_cached_binary_repo_packages(
|
||||||
repo_name: &str,
|
repo_name: &str,
|
||||||
db_path: &Path,
|
db_path: &Path,
|
||||||
@@ -1770,6 +1818,7 @@ fn find_cached_binary_repo_packages(
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -1782,6 +1831,113 @@ fn find_cached_binary_repo_packages(
|
|||||||
rec.replaces = query_package_replaces(&conn, package_id)?;
|
rec.replaces = query_package_replaces(&conn, package_id)?;
|
||||||
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
|
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
|
||||||
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
|
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
|
||||||
|
rec.groups = query_package_groups(&conn, package_id)?;
|
||||||
|
out.push(rec);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_cached_binary_repo_packages_by_group(
|
||||||
|
repo_name: &str,
|
||||||
|
db_path: &Path,
|
||||||
|
group: &str,
|
||||||
|
) -> Result<Vec<BinaryRepoPackageRecord>> {
|
||||||
|
let conn = Connection::open(db_path)
|
||||||
|
.with_context(|| format!("Failed to open binary repo DB {}", db_path.display()))?;
|
||||||
|
|
||||||
|
let has_groups_table: bool = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='groups'",
|
||||||
|
[],
|
||||||
|
|r| {
|
||||||
|
let n: i64 = r.get(0)?;
|
||||||
|
Ok(n > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !has_groups_table {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let completed_at_expr = if repo_packages_have_completed_at(&conn)? {
|
||||||
|
"p.completed_at"
|
||||||
|
} else {
|
||||||
|
"NULL"
|
||||||
|
};
|
||||||
|
let real_name_expr = if repo_packages_have_real_name(&conn)? {
|
||||||
|
"p.real_name"
|
||||||
|
} else {
|
||||||
|
"NULL"
|
||||||
|
};
|
||||||
|
let abi_breaking_expr = if repo_packages_have_abi_breaking(&conn)? {
|
||||||
|
"p.abi_breaking"
|
||||||
|
} else {
|
||||||
|
"0"
|
||||||
|
};
|
||||||
|
let sql = format!(
|
||||||
|
"SELECT
|
||||||
|
p.id,
|
||||||
|
p.name,
|
||||||
|
{real_name_expr},
|
||||||
|
p.version,
|
||||||
|
p.revision,
|
||||||
|
{abi_breaking_expr},
|
||||||
|
{completed_at_expr},
|
||||||
|
p.filename,
|
||||||
|
p.size,
|
||||||
|
p.sha256,
|
||||||
|
p.sha512,
|
||||||
|
p.description,
|
||||||
|
p.homepage,
|
||||||
|
p.license
|
||||||
|
FROM packages p
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM groups g
|
||||||
|
WHERE g.package_id = p.id
|
||||||
|
AND lower(g.name) = lower(?1)
|
||||||
|
)
|
||||||
|
ORDER BY p.name ASC"
|
||||||
|
);
|
||||||
|
let mut stmt = conn.prepare(&sql)?;
|
||||||
|
|
||||||
|
let rows = stmt.query_map(params![group], |row| {
|
||||||
|
let package_id = row.get::<_, i64>(0)?;
|
||||||
|
Ok((
|
||||||
|
package_id,
|
||||||
|
BinaryRepoPackageRecord {
|
||||||
|
repo_name: repo_name.to_string(),
|
||||||
|
name: row.get(1)?,
|
||||||
|
real_name: row.get(2)?,
|
||||||
|
version: row.get(3)?,
|
||||||
|
revision: row.get::<_, i64>(4)? as u32,
|
||||||
|
abi_breaking: row.get(5)?,
|
||||||
|
completed_at: row.get(6)?,
|
||||||
|
filename: row.get(7)?,
|
||||||
|
size: row.get::<_, i64>(8)? as u64,
|
||||||
|
sha256: row.get(9)?,
|
||||||
|
sha512: row.get(10)?,
|
||||||
|
description: row.get(11)?,
|
||||||
|
homepage: row.get(12)?,
|
||||||
|
license: row.get(13)?,
|
||||||
|
provides: Vec::new(),
|
||||||
|
conflicts: Vec::new(),
|
||||||
|
replaces: Vec::new(),
|
||||||
|
runtime_dependencies: Vec::new(),
|
||||||
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
let (package_id, mut rec) = row?;
|
||||||
|
rec.provides = query_package_provides(&conn, package_id)?;
|
||||||
|
rec.conflicts = query_package_conflicts(&conn, package_id)?;
|
||||||
|
rec.replaces = query_package_replaces(&conn, package_id)?;
|
||||||
|
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
|
||||||
|
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
|
||||||
|
rec.groups = query_package_groups(&conn, package_id)?;
|
||||||
out.push(rec);
|
out.push(rec);
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@@ -1854,6 +2010,7 @@ fn list_cached_binary_repo_packages(
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -1866,6 +2023,7 @@ fn list_cached_binary_repo_packages(
|
|||||||
rec.replaces = query_package_replaces(&conn, package_id)?;
|
rec.replaces = query_package_replaces(&conn, package_id)?;
|
||||||
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
|
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
|
||||||
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
|
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
|
||||||
|
rec.groups = query_package_groups(&conn, package_id)?;
|
||||||
out.push(rec);
|
out.push(rec);
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@@ -1949,6 +2107,18 @@ pub fn find_binary_repo_packages(
|
|||||||
find_cached_binary_repo_packages(repo_name, &db_path, query)
|
find_cached_binary_repo_packages(repo_name, &db_path, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve package records that belong to the named group from a binary repo.
|
||||||
|
pub fn find_binary_repo_packages_by_group(
|
||||||
|
repo_name: &str,
|
||||||
|
repo: &crate::config::BinaryRepo,
|
||||||
|
rootfs: &Path,
|
||||||
|
package_cache_dir: &Path,
|
||||||
|
group: &str,
|
||||||
|
) -> Result<Vec<BinaryRepoPackageRecord>> {
|
||||||
|
let db_path = fetch_binary_repo_db(repo_name, repo, rootfs, package_cache_dir)?;
|
||||||
|
find_cached_binary_repo_packages_by_group(repo_name, &db_path, group)
|
||||||
|
}
|
||||||
|
|
||||||
/// List all binary packages from a cached, verified repository database.
|
/// List all binary packages from a cached, verified repository database.
|
||||||
pub fn list_binary_repo_packages(
|
pub fn list_binary_repo_packages(
|
||||||
repo_name: &str,
|
repo_name: &str,
|
||||||
@@ -2716,6 +2886,33 @@ revision = 1
|
|||||||
assert_eq!(provide_hits[0].name, "foo");
|
assert_eq!(provide_hits[0].name, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_cached_binary_repo_packages_by_group() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let db_path = tmp.path().join("repo.db");
|
||||||
|
let mut conn = Connection::open(&db_path).unwrap();
|
||||||
|
let manager = RepoManager::new(tmp.path().to_path_buf());
|
||||||
|
manager.init_repo_schema(&mut conn).unwrap();
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO packages (id, name, version, revision, description, homepage, license, filename, size, sha256, sha512)
|
||||||
|
VALUES (1, 'foo', '1.2.3', 1, 'Foo package', 'https://example.test', 'MIT', 'foo-1.2.3-1-x86_64.depot.pkg.tar.zst', 1234, 'a', 'b')",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO groups (package_id, name) VALUES (1, 'base')",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
let hits = find_cached_binary_repo_packages_by_group("testrepo", &db_path, "base").unwrap();
|
||||||
|
assert_eq!(hits.len(), 1);
|
||||||
|
assert_eq!(hits[0].name, "foo");
|
||||||
|
assert_eq!(hits[0].groups, vec!["base".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_repo_owns_query_candidates_follow_rootfs_symlink_targets() {
|
fn test_repo_owns_query_candidates_follow_rootfs_symlink_targets() {
|
||||||
@@ -2819,6 +3016,7 @@ revision = 1
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
verify_binary_package_record_checksums(&pkg, &rec).unwrap();
|
verify_binary_package_record_checksums(&pkg, &rec).unwrap();
|
||||||
@@ -2858,6 +3056,7 @@ revision = 1
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -608,6 +608,7 @@ mod tests {
|
|||||||
runtime: Vec::new(),
|
runtime: Vec::new(),
|
||||||
test: vec!["bats".into(), "python".into()],
|
test: vec!["bats".into(), "python".into()],
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -657,6 +658,7 @@ mod tests {
|
|||||||
runtime: vec!["python".into()],
|
runtime: vec!["python".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -715,6 +717,7 @@ mod tests {
|
|||||||
runtime: vec!["foo-libs".into(), "libfoo".into(), "python".into()],
|
runtime: vec!["foo-libs".into(), "libfoo".into(), "python".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: std::collections::BTreeMap::from([(
|
package_alternatives: std::collections::BTreeMap::from([(
|
||||||
@@ -747,6 +750,7 @@ mod tests {
|
|||||||
runtime: vec!["foo".into()],
|
runtime: vec!["foo".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let missing = check_runtime_deps_for_outputs(
|
let missing = check_runtime_deps_for_outputs(
|
||||||
|
|||||||
@@ -655,6 +655,14 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
|||||||
"Optional dependency",
|
"Optional dependency",
|
||||||
"Package that enables optional runtime functionality",
|
"Package that enables optional runtime functionality",
|
||||||
)?;
|
)?;
|
||||||
|
let groups = if show_advanced {
|
||||||
|
prompt_repeating_list(
|
||||||
|
"Package group",
|
||||||
|
"Group name(s) this package belongs to, e.g. base, desktop, development",
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
let alternatives = if show_advanced {
|
let alternatives = if show_advanced {
|
||||||
Alternatives {
|
Alternatives {
|
||||||
provides: prompt_repeating_list(
|
provides: prompt_repeating_list(
|
||||||
@@ -696,6 +704,7 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
|||||||
runtime: runtime_deps,
|
runtime: runtime_deps,
|
||||||
test: test_deps,
|
test: test_deps,
|
||||||
optional: optional_deps,
|
optional: optional_deps,
|
||||||
|
groups,
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -1504,6 +1513,7 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
|| !spec.dependencies.runtime.is_empty()
|
|| !spec.dependencies.runtime.is_empty()
|
||||||
|| !spec.dependencies.test.is_empty()
|
|| !spec.dependencies.test.is_empty()
|
||||||
|| !spec.dependencies.optional.is_empty()
|
|| !spec.dependencies.optional.is_empty()
|
||||||
|
|| !spec.dependencies.groups.is_empty()
|
||||||
{
|
{
|
||||||
let mut dep_tbl = Table::new();
|
let mut dep_tbl = Table::new();
|
||||||
if !spec.dependencies.build.is_empty() {
|
if !spec.dependencies.build.is_empty() {
|
||||||
@@ -1554,6 +1564,18 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if !spec.dependencies.groups.is_empty() {
|
||||||
|
dep_tbl.insert(
|
||||||
|
"groups".into(),
|
||||||
|
Value::Array(
|
||||||
|
spec.dependencies
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.map(|s| Value::String(s.clone()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
root.insert("dependencies".into(), Value::Table(dep_tbl));
|
root.insert("dependencies".into(), Value::Table(dep_tbl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1622,17 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if !deps.groups.is_empty() {
|
||||||
|
dep_tbl.insert(
|
||||||
|
"groups".into(),
|
||||||
|
Value::Array(
|
||||||
|
deps.groups
|
||||||
|
.iter()
|
||||||
|
.map(|s| Value::String(s.clone()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
if !dep_tbl.is_empty() {
|
if !dep_tbl.is_empty() {
|
||||||
outputs_tbl.insert(pkg_name.clone(), Value::Table(dep_tbl));
|
outputs_tbl.insert(pkg_name.clone(), Value::Table(dep_tbl));
|
||||||
}
|
}
|
||||||
@@ -1999,6 +2032,7 @@ mod tests {
|
|||||||
runtime: vec![],
|
runtime: vec![],
|
||||||
test: vec!["python".into(), "bats".into()],
|
test: vec!["python".into(), "bats".into()],
|
||||||
optional: vec!["gtk-doc".into()],
|
optional: vec!["gtk-doc".into()],
|
||||||
|
groups: vec!["base".into(), "devtools".into()],
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
@@ -2023,6 +2057,14 @@ mod tests {
|
|||||||
.expect("expected dependencies.optional array");
|
.expect("expected dependencies.optional array");
|
||||||
assert_eq!(optional_deps.len(), 1);
|
assert_eq!(optional_deps.len(), 1);
|
||||||
assert_eq!(optional_deps[0].as_str(), Some("gtk-doc"));
|
assert_eq!(optional_deps[0].as_str(), Some("gtk-doc"));
|
||||||
|
let groups = val
|
||||||
|
.get("dependencies")
|
||||||
|
.and_then(|d| d.get("groups"))
|
||||||
|
.and_then(|t| t.as_array())
|
||||||
|
.expect("expected dependencies.groups array");
|
||||||
|
assert_eq!(groups.len(), 2);
|
||||||
|
assert_eq!(groups[0].as_str(), Some("base"));
|
||||||
|
assert_eq!(groups[1].as_str(), Some("devtools"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2133,6 +2175,7 @@ mod tests {
|
|||||||
runtime: vec!["foo".into(), "bar".into()],
|
runtime: vec!["foo".into(), "bar".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: vec!["base".into()],
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: Default::default(),
|
package_alternatives: Default::default(),
|
||||||
|
|||||||
@@ -228,6 +228,17 @@ impl Packager {
|
|||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
deps.insert(
|
||||||
|
"groups".to_string(),
|
||||||
|
toml::Value::Array(
|
||||||
|
self.spec
|
||||||
|
.dependencies
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.map(|s| toml::Value::String(s.clone()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
);
|
||||||
map.insert("dependencies".to_string(), toml::Value::Table(deps));
|
map.insert("dependencies".to_string(), toml::Value::Table(deps));
|
||||||
if !self.spec.build.flags.keep.is_empty() {
|
if !self.spec.build.flags.keep.is_empty() {
|
||||||
map.insert(
|
map.insert(
|
||||||
|
|||||||
@@ -1899,9 +1899,11 @@ type = "custom"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
runtime = ["base"]
|
runtime = ["base"]
|
||||||
|
groups = ["toolchain"]
|
||||||
|
|
||||||
[package_dependencies.clang]
|
[package_dependencies.clang]
|
||||||
runtime = ["llvm-libs", "llvm-libgcc"]
|
runtime = ["llvm-libs", "llvm-libgcc"]
|
||||||
|
groups = ["compiler"]
|
||||||
|
|
||||||
[package_dependencies.llvm-libs]
|
[package_dependencies.llvm-libs]
|
||||||
runtime = ["llvm-libgcc", "zstd"]
|
runtime = ["llvm-libgcc", "zstd"]
|
||||||
@@ -1914,14 +1916,26 @@ runtime = ["llvm-libgcc", "zstd"]
|
|||||||
spec.dependencies_for_output("llvm").runtime,
|
spec.dependencies_for_output("llvm").runtime,
|
||||||
vec!["base".to_string()]
|
vec!["base".to_string()]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
spec.dependencies_for_output("llvm").groups,
|
||||||
|
vec!["toolchain".to_string()]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spec.dependencies_for_output("clang").runtime,
|
spec.dependencies_for_output("clang").runtime,
|
||||||
vec!["llvm-libs".to_string(), "llvm-libgcc".to_string()]
|
vec!["llvm-libs".to_string(), "llvm-libgcc".to_string()]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
spec.dependencies_for_output("clang").groups,
|
||||||
|
vec!["compiler".to_string()]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spec.dependencies_for_output("llvm-libs").runtime,
|
spec.dependencies_for_output("llvm-libs").runtime,
|
||||||
vec!["llvm-libgcc".to_string(), "zstd".to_string()]
|
vec!["llvm-libgcc".to_string(), "zstd".to_string()]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
spec.dependencies_for_output("llvm-libs").groups,
|
||||||
|
Vec::<String>::new()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1949,11 +1963,13 @@ type = "custom"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
runtime = ["base"]
|
runtime = ["base"]
|
||||||
|
groups = ["toolchain"]
|
||||||
|
|
||||||
[dependencies.lib32]
|
[dependencies.lib32]
|
||||||
build = ["gcc-multilib"]
|
build = ["gcc-multilib"]
|
||||||
runtime = ["lib32-zlib"]
|
runtime = ["lib32-zlib"]
|
||||||
test = ["bats"]
|
test = ["bats"]
|
||||||
|
groups = ["lib32-toolchain"]
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1963,6 +1979,10 @@ test = ["bats"]
|
|||||||
spec.dependencies_for_output("llvm").runtime,
|
spec.dependencies_for_output("llvm").runtime,
|
||||||
vec!["base".to_string()]
|
vec!["base".to_string()]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
spec.dependencies_for_output("llvm").groups,
|
||||||
|
vec!["toolchain".to_string()]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spec.dependencies_for_output("lib32-llvm").build,
|
spec.dependencies_for_output("lib32-llvm").build,
|
||||||
vec!["gcc-multilib".to_string()]
|
vec!["gcc-multilib".to_string()]
|
||||||
@@ -1975,6 +1995,10 @@ test = ["bats"]
|
|||||||
spec.dependencies_for_output("lib32-llvm").test,
|
spec.dependencies_for_output("lib32-llvm").test,
|
||||||
vec!["bats".to_string()]
|
vec!["bats".to_string()]
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
spec.dependencies_for_output("lib32-llvm").groups,
|
||||||
|
vec!["lib32-toolchain".to_string()]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2274,6 +2298,7 @@ type = "meta"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
runtime = ["foo", "bar"]
|
runtime = ["foo", "bar"]
|
||||||
|
groups = ["base"]
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -2283,6 +2308,7 @@ runtime = ["foo", "bar"]
|
|||||||
assert!(spec.manual_sources.is_empty());
|
assert!(spec.manual_sources.is_empty());
|
||||||
assert!(spec.is_metapackage());
|
assert!(spec.is_metapackage());
|
||||||
assert_eq!(spec.dependencies.runtime, vec!["foo", "bar"]);
|
assert_eq!(spec.dependencies.runtime, vec!["foo", "bar"]);
|
||||||
|
assert_eq!(spec.dependencies.groups, vec!["base"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3803,6 +3829,9 @@ impl fmt::Display for PackageSpec {
|
|||||||
if !self.alternatives.replaces.is_empty() {
|
if !self.alternatives.replaces.is_empty() {
|
||||||
writeln!(f, "Replaces: {}", self.alternatives.replaces.join(", "))?;
|
writeln!(f, "Replaces: {}", self.alternatives.replaces.join(", "))?;
|
||||||
}
|
}
|
||||||
|
if !self.dependencies.groups.is_empty() {
|
||||||
|
writeln!(f, "Groups: {}", self.dependencies.groups.join(", "))?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4786,6 +4815,9 @@ pub struct DependencyGroup {
|
|||||||
/// Optional runtime integrations that enhance functionality when installed.
|
/// Optional runtime integrations that enhance functionality when installed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub optional: Vec<String>,
|
pub optional: Vec<String>,
|
||||||
|
/// Package groups associated with this package output.
|
||||||
|
#[serde(default)]
|
||||||
|
pub groups: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DependencyGroup {
|
impl DependencyGroup {
|
||||||
@@ -4795,6 +4827,7 @@ impl DependencyGroup {
|
|||||||
runtime: self.runtime.clone(),
|
runtime: self.runtime.clone(),
|
||||||
test: self.test.clone(),
|
test: self.test.clone(),
|
||||||
optional: self.optional.clone(),
|
optional: self.optional.clone(),
|
||||||
|
groups: self.groups.clone(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4815,6 +4848,9 @@ pub struct Dependencies {
|
|||||||
/// Optional runtime integrations that enhance functionality when installed.
|
/// Optional runtime integrations that enhance functionality when installed.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub optional: Vec<String>,
|
pub optional: Vec<String>,
|
||||||
|
/// Package groups associated with this package.
|
||||||
|
#[serde(default)]
|
||||||
|
pub groups: Vec<String>,
|
||||||
/// Optional dependency overrides used only for the generated `lib32-*` companion package.
|
/// Optional dependency overrides used only for the generated `lib32-*` companion package.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub lib32: Option<DependencyGroup>,
|
pub lib32: Option<DependencyGroup>,
|
||||||
@@ -4828,6 +4864,7 @@ impl Dependencies {
|
|||||||
runtime: self.runtime.clone(),
|
runtime: self.runtime.clone(),
|
||||||
test: self.test.clone(),
|
test: self.test.clone(),
|
||||||
optional: self.optional.clone(),
|
optional: self.optional.clone(),
|
||||||
|
groups: self.groups.clone(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ impl ParsedStarbuild {
|
|||||||
runtime: dedupe_preserve_order(self.dependencies.clone()),
|
runtime: dedupe_preserve_order(self.dependencies.clone()),
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: dedupe_preserve_order(self.optional_dependencies.clone()),
|
optional: dedupe_preserve_order(self.optional_dependencies.clone()),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
};
|
};
|
||||||
let mut alternatives = Alternatives {
|
let mut alternatives = Alternatives {
|
||||||
@@ -544,6 +545,7 @@ impl ParsedStarbuild {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
),
|
),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -942,6 +942,7 @@ mod tests {
|
|||||||
runtime: vec!["foo-libs".into(), "zlib".into()],
|
runtime: vec!["foo-libs".into(), "zlib".into()],
|
||||||
test: vec!["bats".into()],
|
test: vec!["bats".into()],
|
||||||
optional: vec!["docs-viewer".into()],
|
optional: vec!["docs-viewer".into()],
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
package_alternatives: BTreeMap::from([(
|
package_alternatives: BTreeMap::from([(
|
||||||
@@ -960,6 +961,7 @@ mod tests {
|
|||||||
runtime: vec!["foo-libs".into(), "libfoo".into(), "openssl".into()],
|
runtime: vec!["foo-libs".into(), "libfoo".into(), "openssl".into()],
|
||||||
test: Vec::new(),
|
test: Vec::new(),
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
lib32: None,
|
lib32: None,
|
||||||
},
|
},
|
||||||
)]),
|
)]),
|
||||||
@@ -999,6 +1001,7 @@ mod tests {
|
|||||||
replaces: Vec::new(),
|
replaces: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
optional_dependencies: Vec::new(),
|
optional_dependencies: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
match_kind: MatchKind::Exact,
|
match_kind: MatchKind::Exact,
|
||||||
@@ -1057,6 +1060,7 @@ mod tests {
|
|||||||
runtime: vec!["lib32-zlib".into()],
|
runtime: vec!["lib32-zlib".into()],
|
||||||
test: vec!["lib32-bats".into()],
|
test: vec!["lib32-bats".into()],
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let deps = source_deps_for_install(&spec, true, true);
|
let deps = source_deps_for_install(&spec, true, true);
|
||||||
@@ -1077,6 +1081,7 @@ mod tests {
|
|||||||
runtime: vec!["lib32-zlib".into()],
|
runtime: vec!["lib32-zlib".into()],
|
||||||
test: vec!["lib32-bats".into()],
|
test: vec!["lib32-bats".into()],
|
||||||
optional: Vec::new(),
|
optional: Vec::new(),
|
||||||
|
groups: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let deps = source_deps_for_install(&spec, true, false);
|
let deps = source_deps_for_install(&spec, true, false);
|
||||||
|
|||||||
Reference in New Issue
Block a user