feat: add support for renamed package updates and ABI-breaking flags
- Introduced `real_name` field in `InstalledPackageRecord` and `PackageInfo` to handle renamed packages. - Added `abi_breaking` field to manage versioned shared libraries during renamed updates. - Updated database schema to include `real_name` and `abi_breaking` columns. - Enhanced package registration logic to support replacement of renamed packages while retaining specified files and directories. - Modified various functions and tests to accommodate the new fields and ensure backward compatibility. - Updated TOML serialization to include `real_name` and `abi_breaking` in package metadata.
This commit is contained in:
Generated
+1
-1
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.23.1"
|
version = "0.24.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ar",
|
"ar",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.23.1"
|
version = "0.24.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'depot',
|
'depot',
|
||||||
version: '0.23.1',
|
version: '0.24.0',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
default_options: ['buildtype=release'],
|
default_options: ['buildtype=release'],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -879,10 +879,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.2.3".into(),
|
version: "1.2.3".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1172,10 +1174,12 @@ foo: bar
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "expect".into(),
|
name: "expect".into(),
|
||||||
|
real_name: None,
|
||||||
version: "5.45.4".into(),
|
version: "5.45.4".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1204,10 +1208,12 @@ foo: bar
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1238,10 +1244,12 @@ foo: bar
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
+51
-92
@@ -319,58 +319,20 @@ fn cmake_cache_entry_value<'a>(flags: &'a [String], variable: &str) -> Option<&'
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cmake_install_dir_args(flags: &crate::package::BuildFlags) -> Vec<String> {
|
fn cmake_install_dir_args(flags: &crate::package::BuildFlags) -> Vec<String> {
|
||||||
let prefix = cmake_cache_entry_value(&flags.configure, "CMAKE_INSTALL_PREFIX")
|
|
||||||
.unwrap_or(flags.prefix.as_str());
|
|
||||||
let dirs = crate::builder::install_dirs(flags);
|
let dirs = crate::builder::install_dirs(flags);
|
||||||
let defaults = [
|
let defaults = [
|
||||||
(
|
("CMAKE_INSTALL_BINDIR", dirs.bindir),
|
||||||
"CMAKE_INSTALL_BINDIR",
|
("CMAKE_INSTALL_SBINDIR", dirs.sbindir),
|
||||||
cmake_install_dir_value(prefix, &dirs.bindir),
|
("CMAKE_INSTALL_LIBDIR", dirs.libdir),
|
||||||
),
|
("CMAKE_INSTALL_LIBEXECDIR", dirs.libexecdir),
|
||||||
(
|
("CMAKE_INSTALL_SYSCONFDIR", dirs.sysconfdir),
|
||||||
"CMAKE_INSTALL_SBINDIR",
|
("CMAKE_INSTALL_LOCALSTATEDIR", dirs.localstatedir),
|
||||||
cmake_install_dir_value(prefix, &dirs.sbindir),
|
("CMAKE_INSTALL_SHAREDSTATEDIR", dirs.sharedstatedir),
|
||||||
),
|
("CMAKE_INSTALL_INCLUDEDIR", dirs.includedir),
|
||||||
(
|
("CMAKE_INSTALL_DATAROOTDIR", dirs.datarootdir),
|
||||||
"CMAKE_INSTALL_LIBDIR",
|
("CMAKE_INSTALL_DATADIR", dirs.datadir),
|
||||||
cmake_install_dir_value(prefix, &dirs.libdir),
|
("CMAKE_INSTALL_MANDIR", dirs.mandir),
|
||||||
),
|
("CMAKE_INSTALL_INFODIR", dirs.infodir),
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_LIBEXECDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.libexecdir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_SYSCONFDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.sysconfdir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_LOCALSTATEDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.localstatedir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_SHAREDSTATEDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.sharedstatedir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_INCLUDEDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.includedir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_DATAROOTDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.datarootdir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_DATADIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.datadir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_MANDIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.mandir),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"CMAKE_INSTALL_INFODIR",
|
|
||||||
cmake_install_dir_value(prefix, &dirs.infodir),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
defaults
|
defaults
|
||||||
@@ -429,26 +391,6 @@ fn lib32_target_triple(
|
|||||||
host.map(|host| crate::cross::lib32_target_triple(&host))
|
host.map(|host| crate::cross::lib32_target_triple(&host))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmake_install_dir_value(prefix: &str, value: &str) -> String {
|
|
||||||
let trimmed_prefix = prefix.trim();
|
|
||||||
let trimmed_value = value.trim();
|
|
||||||
if trimmed_prefix.is_empty() || trimmed_value.is_empty() {
|
|
||||||
return trimmed_value.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix_path = Path::new(trimmed_prefix);
|
|
||||||
let value_path = Path::new(trimmed_value);
|
|
||||||
if !prefix_path.is_absolute() || !value_path.is_absolute() {
|
|
||||||
return trimmed_value.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
match value_path.strip_prefix(prefix_path) {
|
|
||||||
Ok(rel) if rel.as_os_str().is_empty() => ".".to_string(),
|
|
||||||
Ok(rel) => rel.to_string_lossy().replace('\\', "/"),
|
|
||||||
Err(_) => trimmed_value.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn num_cpus() -> usize {
|
fn num_cpus() -> usize {
|
||||||
std::thread::available_parallelism()
|
std::thread::available_parallelism()
|
||||||
.map(|n| n.get())
|
.map(|n| n.get())
|
||||||
@@ -632,10 +574,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cmake_install_dir_args_include_defaults() {
|
fn test_cmake_install_dir_args_include_defaults() {
|
||||||
let args = cmake_install_dir_args(&BuildFlags::default());
|
let args = cmake_install_dir_args(&BuildFlags::default());
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=bin"));
|
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=/usr/bin"));
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SBINDIR=bin"));
|
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SBINDIR=/usr/bin"));
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_LIBDIR=lib"));
|
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_LIBDIR=/usr/lib"));
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_LIBEXECDIR=lib"));
|
assert!(
|
||||||
|
args.iter()
|
||||||
|
.any(|a| a == "-DCMAKE_INSTALL_LIBEXECDIR=/usr/lib")
|
||||||
|
);
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SYSCONFDIR=/etc"));
|
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_SYSCONFDIR=/etc"));
|
||||||
assert!(
|
assert!(
|
||||||
args.iter()
|
args.iter()
|
||||||
@@ -647,17 +592,41 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
args.iter()
|
args.iter()
|
||||||
.any(|a| a == "-DCMAKE_INSTALL_INCLUDEDIR=include")
|
.any(|a| a == "-DCMAKE_INSTALL_INCLUDEDIR=/usr/include")
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
args.iter()
|
args.iter()
|
||||||
.any(|a| a == "-DCMAKE_INSTALL_DATAROOTDIR=share")
|
.any(|a| a == "-DCMAKE_INSTALL_DATAROOTDIR=/usr/share")
|
||||||
);
|
);
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_DATADIR=share"));
|
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_MANDIR=share/man"));
|
|
||||||
assert!(
|
assert!(
|
||||||
args.iter()
|
args.iter()
|
||||||
.any(|a| a == "-DCMAKE_INSTALL_INFODIR=share/info")
|
.any(|a| a == "-DCMAKE_INSTALL_DATADIR=/usr/share")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.iter()
|
||||||
|
.any(|a| a == "-DCMAKE_INSTALL_MANDIR=/usr/share/man")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.iter()
|
||||||
|
.any(|a| a == "-DCMAKE_INSTALL_INFODIR=/usr/share/info")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmake_install_dir_args_use_lib32_defaults() {
|
||||||
|
let flags = BuildFlags {
|
||||||
|
lib32_variant: true,
|
||||||
|
..BuildFlags::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = cmake_install_dir_args(&flags);
|
||||||
|
assert!(
|
||||||
|
args.iter()
|
||||||
|
.any(|a| a == "-DCMAKE_INSTALL_LIBDIR=/usr/lib32")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.iter()
|
||||||
|
.any(|a| a == "-DCMAKE_INSTALL_LIBEXECDIR=/usr/lib32")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,19 +710,7 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.starts_with("-DCMAKE_INSTALL_DATADIR="))
|
.any(|a| a.starts_with("-DCMAKE_INSTALL_DATADIR="))
|
||||||
);
|
);
|
||||||
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=bin"));
|
assert!(args.iter().any(|a| a == "-DCMAKE_INSTALL_BINDIR=/usr/bin"));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmake_install_dir_value_makes_prefix_children_relative() {
|
|
||||||
assert_eq!(cmake_install_dir_value("/usr", "/usr/include"), "include");
|
|
||||||
assert_eq!(cmake_install_dir_value("/", "/usr/include"), "usr/include");
|
|
||||||
assert_eq!(
|
|
||||||
cmake_install_dir_value("/opt/depot", "/opt/depot/lib64"),
|
|
||||||
"lib64"
|
|
||||||
);
|
|
||||||
assert_eq!(cmake_install_dir_value("/usr", "/etc"), "/etc");
|
|
||||||
assert_eq!(cmake_install_dir_value("/usr", "include"), "include");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -772,10 +729,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "x".into(),
|
name: "x".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -273,10 +273,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -373,10 +375,12 @@ depot_install_dev_pkg() {
|
|||||||
let mut spec = mk_spec("demo", "1.0");
|
let mut spec = mk_spec("demo", "1.0");
|
||||||
spec.packages.push(crate::package::PackageInfo {
|
spec.packages.push(crate::package::PackageInfo {
|
||||||
name: "dev-pkg".into(),
|
name: "dev-pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -421,10 +425,12 @@ depot_install() {
|
|||||||
let mut spec = mk_spec("demo", "1.0");
|
let mut spec = mk_spec("demo", "1.0");
|
||||||
spec.packages.push(crate::package::PackageInfo {
|
spec.packages.push(crate::package::PackageInfo {
|
||||||
name: "dev-pkg".into(),
|
name: "dev-pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -127,10 +127,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -516,6 +516,18 @@ mod tests {
|
|||||||
assert!(args.iter().any(|a| a == "--bindir=/usr/bin"));
|
assert!(args.iter().any(|a| a == "--bindir=/usr/bin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_meson_setup_args_use_lib32_defaults() {
|
||||||
|
let flags = BuildFlags {
|
||||||
|
lib32_variant: true,
|
||||||
|
..BuildFlags::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = meson_setup_args(&flags, None, &[]);
|
||||||
|
assert!(args.iter().any(|a| a == "--libdir=/usr/lib32"));
|
||||||
|
assert!(args.iter().any(|a| a == "--libexecdir=/usr/lib32"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_meson_setup_args_include_linker_override() {
|
fn test_meson_setup_args_include_linker_override() {
|
||||||
let flags = BuildFlags {
|
let flags = BuildFlags {
|
||||||
@@ -629,10 +641,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -457,10 +457,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "env-test".into(),
|
name: "env-test".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -348,10 +348,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -781,10 +781,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "py-test".into(),
|
name: "py-test".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
+500
-41
@@ -634,6 +634,10 @@ fn package_spec_from_archive_metadata(metadata: &toml::Value) -> package::Packag
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
real_name: metadata
|
||||||
|
.get("real_name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(String::from),
|
||||||
version: metadata
|
version: metadata
|
||||||
.get("version")
|
.get("version")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
@@ -653,6 +657,10 @@ fn package_spec_from_archive_metadata(metadata: &toml::Value) -> package::Packag
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
abi_breaking: metadata
|
||||||
|
.get("abi_breaking")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
license: parse_licenses_from_toml(metadata),
|
license: parse_licenses_from_toml(metadata),
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -722,10 +730,12 @@ fn package_spec_from_repo_record(
|
|||||||
package::PackageSpec {
|
package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: record.name.clone(),
|
name: record.name.clone(),
|
||||||
|
real_name: record.real_name.clone(),
|
||||||
version: record.version.clone(),
|
version: record.version.clone(),
|
||||||
revision: record.revision,
|
revision: record.revision,
|
||||||
description: record.description.clone().unwrap_or_default(),
|
description: record.description.clone().unwrap_or_default(),
|
||||||
homepage: record.homepage.clone().unwrap_or_default(),
|
homepage: record.homepage.clone().unwrap_or_default(),
|
||||||
|
abi_breaking: record.abi_breaking,
|
||||||
license: parse_license_list_from_repo(&record.license),
|
license: parse_license_list_from_repo(&record.license),
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1193,9 +1203,31 @@ fn build_lib32_companion_package(
|
|||||||
struct PlannedStagedInstall {
|
struct PlannedStagedInstall {
|
||||||
is_update: bool,
|
is_update: bool,
|
||||||
remove_paths: Vec<String>,
|
remove_paths: Vec<String>,
|
||||||
|
renamed_transition: Option<RenamedPackageTransition>,
|
||||||
hook_context: install::hooks::HookExecutionContextOwned,
|
hook_context: install::hooks::HookExecutionContextOwned,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct RenamedPackageTransition {
|
||||||
|
replaced: db::InstalledPackageRecord,
|
||||||
|
retained_files: Vec<String>,
|
||||||
|
retained_directories: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenamedPackageTransition {
|
||||||
|
fn replacement(&self) -> db::PackageReplacement {
|
||||||
|
db::PackageReplacement {
|
||||||
|
old_name: self.replaced.name.clone(),
|
||||||
|
retained_files: self.retained_files.clone(),
|
||||||
|
retained_directories: self.retained_directories.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retains_old_package(&self) -> bool {
|
||||||
|
!self.retained_files.is_empty() || !self.retained_directories.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PlannedPackageInstall {
|
struct PlannedPackageInstall {
|
||||||
spec: package::PackageSpec,
|
spec: package::PackageSpec,
|
||||||
@@ -1468,6 +1500,121 @@ fn resolve_installed_conflicts_for_subjects(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_versioned_shared_library_path(path: &str) -> bool {
|
||||||
|
let Some(file_name) = Path::new(path).file_name().and_then(|name| name.to_str()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(version_suffix) = file_name.split(".so.").nth(1) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
!version_suffix.is_empty()
|
||||||
|
&& version_suffix
|
||||||
|
.chars()
|
||||||
|
.all(|ch| ch.is_ascii_digit() || ch == '.')
|
||||||
|
&& version_suffix.chars().any(|ch| ch.is_ascii_digit())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retained_abi_files_for_replacement(
|
||||||
|
old_files: &[String],
|
||||||
|
new_manifest: &staging::Manifest,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let new_files: HashSet<&str> = new_manifest.files.iter().map(String::as_str).collect();
|
||||||
|
let mut retained: Vec<String> = old_files
|
||||||
|
.iter()
|
||||||
|
.filter(|path| is_versioned_shared_library_path(path))
|
||||||
|
.filter(|path| !new_files.contains(path.as_str()))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
retained.sort();
|
||||||
|
retained
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retained_directories_for_files(
|
||||||
|
old_directories: &[String],
|
||||||
|
retained_files: &[String],
|
||||||
|
) -> Vec<String> {
|
||||||
|
let retained_files: HashSet<&str> = retained_files.iter().map(String::as_str).collect();
|
||||||
|
let mut directories: Vec<String> = old_directories
|
||||||
|
.iter()
|
||||||
|
.filter(|directory| {
|
||||||
|
let prefix = format!("{}/", directory);
|
||||||
|
retained_files
|
||||||
|
.iter()
|
||||||
|
.any(|file| *file == directory.as_str() || file.starts_with(&prefix))
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
directories.sort_by_key(|path| std::cmp::Reverse(path.matches('/').count()));
|
||||||
|
directories
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_installed_records_for_stream(
|
||||||
|
left: &db::InstalledPackageRecord,
|
||||||
|
right: &db::InstalledPackageRecord,
|
||||||
|
) -> Ordering {
|
||||||
|
compare_package_release(&left.version, left.revision, &right.version, right.revision)
|
||||||
|
.then_with(|| compare_completed_at(left.completed_at, right.completed_at))
|
||||||
|
.then_with(|| left.name.cmp(&right.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_primary_installed_record<'a>(
|
||||||
|
records: impl IntoIterator<Item = &'a db::InstalledPackageRecord>,
|
||||||
|
) -> Option<&'a db::InstalledPackageRecord> {
|
||||||
|
let mut best: Option<&db::InstalledPackageRecord> = None;
|
||||||
|
for record in records {
|
||||||
|
if best.as_ref().is_none_or(|current| {
|
||||||
|
compare_installed_records_for_stream(record, current) == Ordering::Greater
|
||||||
|
}) {
|
||||||
|
best = Some(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_renamed_package_transition(
|
||||||
|
db_path: &Path,
|
||||||
|
pkg_spec: &package::PackageSpec,
|
||||||
|
new_manifest: &staging::Manifest,
|
||||||
|
) -> Result<Option<RenamedPackageTransition>> {
|
||||||
|
let installed = db::list_installed_package_records(db_path)?;
|
||||||
|
if installed
|
||||||
|
.iter()
|
||||||
|
.any(|record| record.name == pkg_spec.package.name)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream_name = pkg_spec.package.effective_real_name();
|
||||||
|
let Some(replaced) = select_primary_installed_record(
|
||||||
|
installed
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.effective_real_name() == stream_name)
|
||||||
|
.filter(|record| record.name != pkg_spec.package.name),
|
||||||
|
)
|
||||||
|
.cloned() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_files = db::get_package_files(db_path, &replaced.name)?;
|
||||||
|
let old_directories = db::get_package_directories(db_path, &replaced.name)?;
|
||||||
|
let retained_files = if replaced.abi_breaking {
|
||||||
|
retained_abi_files_for_replacement(&old_files, new_manifest)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
let retained_directories = if retained_files.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
retained_directories_for_files(&old_directories, &retained_files)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(RenamedPackageTransition {
|
||||||
|
replaced,
|
||||||
|
retained_files,
|
||||||
|
retained_directories,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn plan_staged_install(
|
fn plan_staged_install(
|
||||||
pkg_spec: &package::PackageSpec,
|
pkg_spec: &package::PackageSpec,
|
||||||
destdir: &Path,
|
destdir: &Path,
|
||||||
@@ -1482,10 +1629,38 @@ fn plan_staged_install(
|
|||||||
})?;
|
})?;
|
||||||
let db_path = config.installed_db_path(rootfs);
|
let db_path = config.installed_db_path(rootfs);
|
||||||
|
|
||||||
let is_update = db::get_package_version(&db_path, &pkg_spec.package.name)?.is_some();
|
|
||||||
let new_manifest = staging::generate_manifest_with_dirs(destdir)?;
|
let new_manifest = staging::generate_manifest_with_dirs(destdir)?;
|
||||||
let remove_paths =
|
let renamed_transition = build_renamed_package_transition(&db_path, pkg_spec, &new_manifest)?;
|
||||||
|
let is_update = db::get_package_version(&db_path, &pkg_spec.package.name)?.is_some()
|
||||||
|
|| renamed_transition.is_some();
|
||||||
|
let mut remove_paths =
|
||||||
db::calculate_upgrade_paths(&db_path, &pkg_spec.package.name, &new_manifest)?;
|
db::calculate_upgrade_paths(&db_path, &pkg_spec.package.name, &new_manifest)?;
|
||||||
|
if let Some(transition) = &renamed_transition {
|
||||||
|
let old_files = db::get_package_files(&db_path, &transition.replaced.name)?;
|
||||||
|
let old_directories = db::get_package_directories(&db_path, &transition.replaced.name)?;
|
||||||
|
let retained_files: HashSet<&str> = transition
|
||||||
|
.retained_files
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect();
|
||||||
|
let retained_directories: HashSet<&str> = transition
|
||||||
|
.retained_directories
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect();
|
||||||
|
remove_paths.extend(
|
||||||
|
old_files
|
||||||
|
.into_iter()
|
||||||
|
.filter(|path| !retained_files.contains(path.as_str())),
|
||||||
|
);
|
||||||
|
remove_paths.extend(
|
||||||
|
old_directories
|
||||||
|
.into_iter()
|
||||||
|
.filter(|path| !retained_directories.contains(path.as_str())),
|
||||||
|
);
|
||||||
|
remove_paths.sort_by_key(|path| std::cmp::Reverse(path.matches('/').count()));
|
||||||
|
remove_paths.dedup();
|
||||||
|
}
|
||||||
let operation = if is_update {
|
let operation = if is_update {
|
||||||
install::hooks::HookOperation::Update
|
install::hooks::HookOperation::Update
|
||||||
} else {
|
} else {
|
||||||
@@ -1499,6 +1674,7 @@ fn plan_staged_install(
|
|||||||
Ok(PlannedStagedInstall {
|
Ok(PlannedStagedInstall {
|
||||||
is_update,
|
is_update,
|
||||||
remove_paths,
|
remove_paths,
|
||||||
|
renamed_transition,
|
||||||
hook_context: install::hooks::HookExecutionContextOwned {
|
hook_context: install::hooks::HookExecutionContextOwned {
|
||||||
operation,
|
operation,
|
||||||
package: pkg_spec.package.name.clone(),
|
package: pkg_spec.package.name.clone(),
|
||||||
@@ -1582,12 +1758,27 @@ fn install_staged_to_rootfs(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let db_path = config.installed_db_path(rootfs);
|
let db_path = config.installed_db_path(rootfs);
|
||||||
if let Err(e) = db::register_package(&db_path, pkg_spec, destdir) {
|
let replacement = plan
|
||||||
|
.renamed_transition
|
||||||
|
.as_ref()
|
||||||
|
.map(RenamedPackageTransition::replacement);
|
||||||
|
let register_result = if let Some(replacement) = replacement.as_ref() {
|
||||||
|
db::register_package_with_replacement(&db_path, pkg_spec, destdir, Some(replacement))
|
||||||
|
} else {
|
||||||
|
db::register_package(&db_path, pkg_spec, destdir)
|
||||||
|
};
|
||||||
|
if let Err(e) = register_result {
|
||||||
let _ = tx.rollback();
|
let _ = tx.rollback();
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
|
|
||||||
|
if let Some(transition) = &plan.renamed_transition
|
||||||
|
&& !transition.retains_old_package()
|
||||||
|
{
|
||||||
|
install::scripts::remove_installed_scripts(rootfs, &transition.replaced.name)?;
|
||||||
|
}
|
||||||
|
|
||||||
install::scripts::sync_staged_scripts_to_rootfs(
|
install::scripts::sync_staged_scripts_to_rootfs(
|
||||||
&staged_scripts_dir,
|
&staged_scripts_dir,
|
||||||
rootfs,
|
rootfs,
|
||||||
@@ -2028,7 +2219,8 @@ enum UpdateOrigin {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct UpdateCandidate {
|
struct UpdateCandidate {
|
||||||
package: String,
|
installed_package: String,
|
||||||
|
candidate_package: String,
|
||||||
installed_version: String,
|
installed_version: String,
|
||||||
installed_revision: u32,
|
installed_revision: u32,
|
||||||
installed_completed_at: Option<i64>,
|
installed_completed_at: Option<i64>,
|
||||||
@@ -2243,7 +2435,7 @@ fn configured_source_scan_roots(config: &config::Config) -> Vec<(String, i32, Pa
|
|||||||
|
|
||||||
fn collect_best_source_update_candidates(
|
fn collect_best_source_update_candidates(
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
target_names: &HashSet<String>,
|
target_real_names: &HashSet<String>,
|
||||||
) -> Result<HashMap<String, SourceUpdateCandidate>> {
|
) -> Result<HashMap<String, SourceUpdateCandidate>> {
|
||||||
let mut best: HashMap<String, SourceUpdateCandidate> = HashMap::new();
|
let mut best: HashMap<String, SourceUpdateCandidate> = HashMap::new();
|
||||||
|
|
||||||
@@ -2255,7 +2447,8 @@ fn collect_best_source_update_candidates(
|
|||||||
for spec_path in scan_package_specs(&root)? {
|
for spec_path in scan_package_specs(&root)? {
|
||||||
let mut spec = package::PackageSpec::from_file(&spec_path)?;
|
let mut spec = package::PackageSpec::from_file(&spec_path)?;
|
||||||
spec.apply_config(config);
|
spec.apply_config(config);
|
||||||
if !target_names.contains(&spec.package.name) {
|
let stream_name = spec.package.effective_real_name().to_string();
|
||||||
|
if !target_real_names.contains(&stream_name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2267,7 +2460,7 @@ fn collect_best_source_update_candidates(
|
|||||||
spec,
|
spec,
|
||||||
};
|
};
|
||||||
|
|
||||||
let replace = match best.get(&candidate.spec.package.name) {
|
let replace = match best.get(&stream_name) {
|
||||||
Some(current) => match compare_package_release(
|
Some(current) => match compare_package_release(
|
||||||
&candidate.spec.package.version,
|
&candidate.spec.package.version,
|
||||||
candidate.spec.package.revision,
|
candidate.spec.package.revision,
|
||||||
@@ -2296,7 +2489,7 @@ fn collect_best_source_update_candidates(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if replace {
|
if replace {
|
||||||
best.insert(candidate.spec.package.name.clone(), candidate);
|
best.insert(stream_name, candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2307,7 +2500,7 @@ fn collect_best_source_update_candidates(
|
|||||||
fn collect_best_binary_update_candidates(
|
fn collect_best_binary_update_candidates(
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
target_names: &HashSet<String>,
|
target_real_names: &HashSet<String>,
|
||||||
) -> Result<HashMap<String, (i32, db::repo::BinaryRepoPackageRecord)>> {
|
) -> Result<HashMap<String, (i32, db::repo::BinaryRepoPackageRecord)>> {
|
||||||
let mut best: HashMap<String, (i32, db::repo::BinaryRepoPackageRecord)> = HashMap::new();
|
let mut best: HashMap<String, (i32, db::repo::BinaryRepoPackageRecord)> = HashMap::new();
|
||||||
let host_arch = std::env::consts::ARCH;
|
let host_arch = std::env::consts::ARCH;
|
||||||
@@ -2328,11 +2521,12 @@ fn collect_best_binary_update_candidates(
|
|||||||
.with_context(|| format!("Failed to list binary repo '{}'", repo_name))?;
|
.with_context(|| format!("Failed to list binary repo '{}'", repo_name))?;
|
||||||
|
|
||||||
for record in records {
|
for record in records {
|
||||||
if !target_names.contains(&record.name) {
|
let stream_name = record.effective_real_name().to_string();
|
||||||
|
if !target_real_names.contains(&stream_name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let replace = match best.get(&record.name) {
|
let replace = match best.get(&stream_name) {
|
||||||
Some((current_priority, current)) => match compare_package_release(
|
Some((current_priority, current)) => match compare_package_release(
|
||||||
&record.version,
|
&record.version,
|
||||||
record.revision,
|
record.revision,
|
||||||
@@ -2361,7 +2555,7 @@ fn collect_best_binary_update_candidates(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if replace {
|
if replace {
|
||||||
best.insert(record.name.clone(), (repo_cfg.priority, record));
|
best.insert(stream_name, (repo_cfg.priority, record));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2377,8 +2571,9 @@ fn select_update_candidate(
|
|||||||
prefer_binary: bool,
|
prefer_binary: bool,
|
||||||
) -> Option<UpdateCandidate> {
|
) -> Option<UpdateCandidate> {
|
||||||
let mut best: Option<UpdateCandidate> = None;
|
let mut best: Option<UpdateCandidate> = None;
|
||||||
|
let stream_name = installed.effective_real_name();
|
||||||
|
|
||||||
if let Some(candidate) = source_candidates.get(&installed.name)
|
if let Some(candidate) = source_candidates.get(stream_name)
|
||||||
&& update_candidate_is_newer_than_installed(
|
&& update_candidate_is_newer_than_installed(
|
||||||
&candidate.spec.package.version,
|
&candidate.spec.package.version,
|
||||||
candidate.spec.package.revision,
|
candidate.spec.package.revision,
|
||||||
@@ -2389,7 +2584,8 @@ fn select_update_candidate(
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
best = Some(UpdateCandidate {
|
best = Some(UpdateCandidate {
|
||||||
package: installed.name.clone(),
|
installed_package: installed.name.clone(),
|
||||||
|
candidate_package: candidate.spec.package.name.clone(),
|
||||||
installed_version: installed.version.clone(),
|
installed_version: installed.version.clone(),
|
||||||
installed_revision: installed.revision,
|
installed_revision: installed.revision,
|
||||||
installed_completed_at,
|
installed_completed_at,
|
||||||
@@ -2407,7 +2603,7 @@ fn select_update_candidate(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((repo_priority, record)) = binary_candidates.get(&installed.name)
|
if let Some((repo_priority, record)) = binary_candidates.get(stream_name)
|
||||||
&& update_candidate_is_newer_than_installed(
|
&& update_candidate_is_newer_than_installed(
|
||||||
&record.version,
|
&record.version,
|
||||||
record.revision,
|
record.revision,
|
||||||
@@ -2418,7 +2614,8 @@ fn select_update_candidate(
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
let binary_candidate = UpdateCandidate {
|
let binary_candidate = UpdateCandidate {
|
||||||
package: installed.name.clone(),
|
installed_package: installed.name.clone(),
|
||||||
|
candidate_package: record.name.clone(),
|
||||||
installed_version: installed.version.clone(),
|
installed_version: installed.version.clone(),
|
||||||
installed_revision: installed.revision,
|
installed_revision: installed.revision,
|
||||||
installed_completed_at,
|
installed_completed_at,
|
||||||
@@ -2456,36 +2653,53 @@ fn collect_update_candidates(
|
|||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let installed_by_name: HashMap<_, _> = installed
|
let mut installed_by_real_name: HashMap<String, Vec<&db::InstalledPackageRecord>> =
|
||||||
|
HashMap::new();
|
||||||
|
for record in &installed {
|
||||||
|
installed_by_real_name
|
||||||
|
.entry(record.effective_real_name().to_string())
|
||||||
|
.or_default()
|
||||||
|
.push(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_by_real_name: HashMap<String, db::InstalledPackageRecord> = installed_by_real_name
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.filter_map(|(real_name, records)| {
|
||||||
.map(|record| (record.name.clone(), record))
|
select_primary_installed_record(records.iter().copied())
|
||||||
|
.cloned()
|
||||||
|
.map(|record| (real_name.clone(), record))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let target_names: HashSet<String> = if requested_packages.is_empty() {
|
let target_real_names: HashSet<String> = if requested_packages.is_empty() {
|
||||||
installed_by_name.keys().cloned().collect()
|
active_by_real_name.keys().cloned().collect()
|
||||||
} else {
|
} else {
|
||||||
requested_packages.iter().cloned().collect()
|
let mut targets = HashSet::new();
|
||||||
};
|
for package in requested_packages {
|
||||||
|
if let Some(record) = installed.iter().find(|record| record.name == *package) {
|
||||||
for package in requested_packages {
|
targets.insert(record.effective_real_name().to_string());
|
||||||
if !installed_by_name.contains_key(package) {
|
} else if active_by_real_name.contains_key(package) {
|
||||||
ui::warn(format!("Package '{}' is not installed; skipping", package));
|
targets.insert(package.clone());
|
||||||
|
} else {
|
||||||
|
ui::warn(format!("Package '{}' is not installed; skipping", package));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
targets
|
||||||
|
};
|
||||||
|
|
||||||
let source_candidates = if config.repo_settings.prefer_binary {
|
let source_candidates = if config.repo_settings.prefer_binary {
|
||||||
HashMap::new()
|
HashMap::new()
|
||||||
} else {
|
} else {
|
||||||
collect_best_source_update_candidates(config, &target_names)?
|
collect_best_source_update_candidates(config, &target_real_names)?
|
||||||
};
|
};
|
||||||
let binary_candidates = collect_best_binary_update_candidates(config, rootfs, &target_names)?;
|
let binary_candidates =
|
||||||
|
collect_best_binary_update_candidates(config, rootfs, &target_real_names)?;
|
||||||
|
|
||||||
let mut updates = Vec::new();
|
let mut updates = Vec::new();
|
||||||
let mut targets: Vec<_> = target_names.into_iter().collect();
|
let mut targets: Vec<_> = target_real_names.into_iter().collect();
|
||||||
targets.sort();
|
targets.sort();
|
||||||
for target in targets {
|
for target in targets {
|
||||||
let Some(installed) = installed_by_name.get(&target) else {
|
let Some(installed) = active_by_real_name.get(&target) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let installed_completed_at = installed_package_completed_at(installed, &db_path, rootfs)?;
|
let installed_completed_at = installed_package_completed_at(installed, &db_path, rootfs)?;
|
||||||
@@ -2509,7 +2723,7 @@ fn collect_missing_update_dependencies(
|
|||||||
) -> Result<Vec<String>> {
|
) -> Result<Vec<String>> {
|
||||||
let mut planned_provides = HashSet::new();
|
let mut planned_provides = HashSet::new();
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
planned_provides.insert(candidate.package.clone());
|
planned_provides.insert(candidate.candidate_package.clone());
|
||||||
for provide in &candidate.provides {
|
for provide in &candidate.provides {
|
||||||
planned_provides.insert(provide.clone());
|
planned_provides.insert(provide.clone());
|
||||||
}
|
}
|
||||||
@@ -2571,10 +2785,11 @@ fn run_update_command(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|candidate| {
|
.map(|candidate| {
|
||||||
let summary = format!(
|
let summary = format!(
|
||||||
"{} v{}-{} -> v{}-{}",
|
"{} v{}-{} -> {} v{}-{}",
|
||||||
candidate.package,
|
candidate.installed_package,
|
||||||
candidate.installed_version,
|
candidate.installed_version,
|
||||||
candidate.installed_revision,
|
candidate.installed_revision,
|
||||||
|
candidate.candidate_package,
|
||||||
candidate.candidate_version,
|
candidate.candidate_version,
|
||||||
candidate.candidate_revision
|
candidate.candidate_revision
|
||||||
);
|
);
|
||||||
@@ -2595,7 +2810,7 @@ fn run_update_command(
|
|||||||
let conflict_subjects: Vec<_> = updates
|
let conflict_subjects: Vec<_> = updates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|candidate| InstallConflictSubject {
|
.map(|candidate| InstallConflictSubject {
|
||||||
package: candidate.package.clone(),
|
package: candidate.candidate_package.clone(),
|
||||||
provides: candidate.provides.clone(),
|
provides: candidate.provides.clone(),
|
||||||
conflicts: candidate.conflicts.clone(),
|
conflicts: candidate.conflicts.clone(),
|
||||||
})
|
})
|
||||||
@@ -2673,12 +2888,13 @@ fn run_update_command(
|
|||||||
let exe = std::env::current_exe().context("Failed to locate depot executable")?;
|
let exe = std::env::current_exe().context("Failed to locate depot executable")?;
|
||||||
for (idx, candidate) in updates.iter().enumerate() {
|
for (idx, candidate) in updates.iter().enumerate() {
|
||||||
ui::info(format!(
|
ui::info(format!(
|
||||||
"[{}/{}] updating {} v{}-{} -> v{}-{}",
|
"[{}/{}] updating {} v{}-{} -> {} v{}-{}",
|
||||||
idx + 1,
|
idx + 1,
|
||||||
updates.len(),
|
updates.len(),
|
||||||
candidate.package,
|
candidate.installed_package,
|
||||||
candidate.installed_version,
|
candidate.installed_version,
|
||||||
candidate.installed_revision,
|
candidate.installed_revision,
|
||||||
|
candidate.candidate_package,
|
||||||
candidate.candidate_version,
|
candidate.candidate_version,
|
||||||
candidate.candidate_revision
|
candidate.candidate_revision
|
||||||
));
|
));
|
||||||
@@ -5415,8 +5631,10 @@ mod tests {
|
|||||||
db::repo::BinaryRepoPackageRecord {
|
db::repo::BinaryRepoPackageRecord {
|
||||||
repo_name: "core".into(),
|
repo_name: "core".into(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: filename.into(),
|
filename: filename.into(),
|
||||||
size: 1,
|
size: 1,
|
||||||
@@ -5449,10 +5667,12 @@ mod tests {
|
|||||||
package::PackageSpec {
|
package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -5598,8 +5818,10 @@ optional = []
|
|||||||
let record = db::repo::BinaryRepoPackageRecord {
|
let record = db::repo::BinaryRepoPackageRecord {
|
||||||
repo_name: "core".into(),
|
repo_name: "core".into(),
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: archive_path
|
filename: archive_path
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -5897,8 +6119,10 @@ optional = []
|
|||||||
let record = db::repo::BinaryRepoPackageRecord {
|
let record = db::repo::BinaryRepoPackageRecord {
|
||||||
repo_name: "core".into(),
|
repo_name: "core".into(),
|
||||||
name: "sudo".into(),
|
name: "sudo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: archive_path
|
filename: archive_path
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -6017,10 +6241,12 @@ optional = []
|
|||||||
let spec = package::PackageSpec {
|
let spec = package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "filesystem".into(),
|
name: "filesystem".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.1".into(),
|
version: "1.0.1".into(),
|
||||||
revision: 3,
|
revision: 3,
|
||||||
description: "Base filesystem".into(),
|
description: "Base filesystem".into(),
|
||||||
homepage: "https://example.test".into(),
|
homepage: "https://example.test".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["Unlicense".into()],
|
license: vec!["Unlicense".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -6047,6 +6273,117 @@ optional = []
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renamed_abi_updates_keep_versioned_shared_libraries() -> Result<()> {
|
||||||
|
let rootfs = tempfile::tempdir().context("Failed to create temp rootfs")?;
|
||||||
|
let mut cfg = config::Config::for_rootfs(rootfs.path());
|
||||||
|
cfg.db_dir = rootfs.path().join("var/lib/depot");
|
||||||
|
cfg.build_dir = rootfs.path().join("var/cache/depot/build");
|
||||||
|
|
||||||
|
let old_spec = package::PackageSpec {
|
||||||
|
package: package::PackageInfo {
|
||||||
|
name: "libxml214".into(),
|
||||||
|
real_name: Some("libxml2".into()),
|
||||||
|
version: "2.14.9".into(),
|
||||||
|
revision: 1,
|
||||||
|
description: "libxml2 2.14".into(),
|
||||||
|
homepage: "https://example.test/libxml2".into(),
|
||||||
|
abi_breaking: true,
|
||||||
|
license: vec!["MIT".into()],
|
||||||
|
},
|
||||||
|
packages: Vec::new(),
|
||||||
|
alternatives: package::Alternatives::default(),
|
||||||
|
manual_sources: Vec::new(),
|
||||||
|
source: Vec::new(),
|
||||||
|
build: package::Build {
|
||||||
|
build_type: package::BuildType::Bin,
|
||||||
|
flags: package::BuildFlags::default(),
|
||||||
|
},
|
||||||
|
dependencies: package::Dependencies::default(),
|
||||||
|
package_alternatives: Default::default(),
|
||||||
|
package_dependencies: Default::default(),
|
||||||
|
spec_dir: PathBuf::from("."),
|
||||||
|
};
|
||||||
|
let old_dest = rootfs.path().join("old-dest");
|
||||||
|
fs::create_dir_all(old_dest.join("usr/lib/pkgconfig"))?;
|
||||||
|
fs::write(old_dest.join("usr/lib/libxml2.so.14.9.0"), "old-real")?;
|
||||||
|
std::os::unix::fs::symlink("libxml2.so.14.9.0", old_dest.join("usr/lib/libxml2.so.14"))?;
|
||||||
|
std::os::unix::fs::symlink("libxml2.so.14", old_dest.join("usr/lib/libxml2.so"))?;
|
||||||
|
fs::write(
|
||||||
|
old_dest.join("usr/lib/pkgconfig/libxml-2.0.pc"),
|
||||||
|
"old-pkgconfig",
|
||||||
|
)?;
|
||||||
|
install_package_outputs_to_rootfs(&old_spec, &old_dest, rootfs.path(), &cfg)?;
|
||||||
|
|
||||||
|
let new_spec = package::PackageSpec {
|
||||||
|
package: package::PackageInfo {
|
||||||
|
name: "libxml215".into(),
|
||||||
|
real_name: Some("libxml2".into()),
|
||||||
|
version: "2.15.1".into(),
|
||||||
|
revision: 1,
|
||||||
|
description: "libxml2 2.15".into(),
|
||||||
|
homepage: "https://example.test/libxml2".into(),
|
||||||
|
abi_breaking: true,
|
||||||
|
license: vec!["MIT".into()],
|
||||||
|
},
|
||||||
|
packages: Vec::new(),
|
||||||
|
alternatives: package::Alternatives::default(),
|
||||||
|
manual_sources: Vec::new(),
|
||||||
|
source: Vec::new(),
|
||||||
|
build: package::Build {
|
||||||
|
build_type: package::BuildType::Bin,
|
||||||
|
flags: package::BuildFlags::default(),
|
||||||
|
},
|
||||||
|
dependencies: package::Dependencies::default(),
|
||||||
|
package_alternatives: Default::default(),
|
||||||
|
package_dependencies: Default::default(),
|
||||||
|
spec_dir: PathBuf::from("."),
|
||||||
|
};
|
||||||
|
let new_dest = rootfs.path().join("new-dest");
|
||||||
|
fs::create_dir_all(new_dest.join("usr/lib/pkgconfig"))?;
|
||||||
|
fs::write(new_dest.join("usr/lib/libxml2.so.15.1.0"), "new-real")?;
|
||||||
|
std::os::unix::fs::symlink("libxml2.so.15.1.0", new_dest.join("usr/lib/libxml2.so.15"))?;
|
||||||
|
std::os::unix::fs::symlink("libxml2.so.15", new_dest.join("usr/lib/libxml2.so"))?;
|
||||||
|
fs::write(
|
||||||
|
new_dest.join("usr/lib/pkgconfig/libxml-2.0.pc"),
|
||||||
|
"new-pkgconfig",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let installed =
|
||||||
|
install_package_outputs_to_rootfs(&new_spec, &new_dest, rootfs.path(), &cfg)?;
|
||||||
|
assert_eq!(installed.len(), 1);
|
||||||
|
assert!(installed[0].is_update);
|
||||||
|
assert_eq!(installed[0].package.name, "libxml215");
|
||||||
|
|
||||||
|
assert!(rootfs.path().join("usr/lib/libxml2.so.14.9.0").exists());
|
||||||
|
assert!(rootfs.path().join("usr/lib/libxml2.so.14").exists());
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(rootfs.path().join("usr/lib/libxml2.so.15.1.0"))?,
|
||||||
|
"new-real"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read_to_string(rootfs.path().join("usr/lib/pkgconfig/libxml-2.0.pc"))?,
|
||||||
|
"new-pkgconfig"
|
||||||
|
);
|
||||||
|
|
||||||
|
let db_path = cfg.installed_db_path(rootfs.path());
|
||||||
|
let old_files = db::get_package_files(&db_path, "libxml214")?;
|
||||||
|
assert_eq!(
|
||||||
|
old_files,
|
||||||
|
vec![
|
||||||
|
"usr/lib/libxml2.so.14".to_string(),
|
||||||
|
"usr/lib/libxml2.so.14.9.0".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_files = db::get_package_files(&db_path, "libxml215")?;
|
||||||
|
assert!(new_files.contains(&"usr/lib/libxml2.so".to_string()));
|
||||||
|
assert!(new_files.contains(&"usr/lib/libxml2.so.15".to_string()));
|
||||||
|
assert!(new_files.contains(&"usr/lib/libxml2.so.15.1.0".to_string()));
|
||||||
|
assert!(new_files.contains(&"usr/lib/pkgconfig/libxml-2.0.pc".to_string()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_missing_dependencies_preserves_order_and_uniqueness() {
|
fn merge_missing_dependencies_preserves_order_and_uniqueness() {
|
||||||
let merged = merge_missing_dependencies(
|
let merged = merge_missing_dependencies(
|
||||||
@@ -6061,21 +6398,130 @@ optional = []
|
|||||||
assert_eq!(merged, vec!["make", "pkgconf", "glibc", "openssl", "zlib"]);
|
assert_eq!(merged, vec!["make", "pkgconf", "glibc", "openssl", "zlib"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collect_update_candidates_matches_renamed_packages_by_real_name() -> Result<()> {
|
||||||
|
let temp = tempfile::tempdir()?;
|
||||||
|
let rootfs = temp.path().join("rootfs");
|
||||||
|
let repo_clones = temp.path().join("repos");
|
||||||
|
let build_dir = temp.path().join("build");
|
||||||
|
let db_dir = rootfs.join("var/lib/depot");
|
||||||
|
fs::create_dir_all(&rootfs)?;
|
||||||
|
fs::create_dir_all(&repo_clones)?;
|
||||||
|
fs::create_dir_all(&build_dir)?;
|
||||||
|
fs::create_dir_all(&db_dir)?;
|
||||||
|
|
||||||
|
let mut config = config::Config::for_rootfs(&rootfs);
|
||||||
|
config.repo_clone_dir = repo_clones.clone();
|
||||||
|
config.build_dir = build_dir;
|
||||||
|
config.db_dir = db_dir.clone();
|
||||||
|
config.repo_settings.prefer_binary = false;
|
||||||
|
config.binary_repos.clear();
|
||||||
|
config.source_repos.insert(
|
||||||
|
"private".into(),
|
||||||
|
config::SourceRepo {
|
||||||
|
url: "https://example.test/private.git".into(),
|
||||||
|
enabled: true,
|
||||||
|
priority: 0,
|
||||||
|
subdirs: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let installed_spec = package::PackageSpec {
|
||||||
|
package: package::PackageInfo {
|
||||||
|
name: "icu78".into(),
|
||||||
|
real_name: Some("icu".into()),
|
||||||
|
version: "78.2".into(),
|
||||||
|
revision: 1,
|
||||||
|
description: "icu78".into(),
|
||||||
|
homepage: "https://example.test/icu".into(),
|
||||||
|
abi_breaking: true,
|
||||||
|
license: vec!["MIT".into()],
|
||||||
|
},
|
||||||
|
packages: Vec::new(),
|
||||||
|
alternatives: package::Alternatives::default(),
|
||||||
|
manual_sources: Vec::new(),
|
||||||
|
source: Vec::new(),
|
||||||
|
build: package::Build {
|
||||||
|
build_type: package::BuildType::Bin,
|
||||||
|
flags: package::BuildFlags::default(),
|
||||||
|
},
|
||||||
|
dependencies: package::Dependencies::default(),
|
||||||
|
package_alternatives: Default::default(),
|
||||||
|
package_dependencies: Default::default(),
|
||||||
|
spec_dir: PathBuf::from("."),
|
||||||
|
};
|
||||||
|
let dest = temp.path().join("dest");
|
||||||
|
fs::create_dir_all(dest.join("usr/lib"))?;
|
||||||
|
fs::write(dest.join("usr/lib/libicuuc.so.78.2"), "icu78")?;
|
||||||
|
db::register_package(&config.installed_db_path(&rootfs), &installed_spec, &dest)?;
|
||||||
|
|
||||||
|
let repo_root = repo_clones.join("private");
|
||||||
|
fs::create_dir_all(&repo_root)?;
|
||||||
|
fs::write(
|
||||||
|
repo_root.join("icu79.toml"),
|
||||||
|
r#"[package]
|
||||||
|
name = "icu79"
|
||||||
|
real_name = "icu"
|
||||||
|
version = "79.1"
|
||||||
|
revision = 1
|
||||||
|
description = "icu79"
|
||||||
|
homepage = "https://example.test/icu"
|
||||||
|
abi_breaking = true
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "meta"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
runtime = []
|
||||||
|
optional = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let installed_records =
|
||||||
|
db::list_installed_package_records(&config.installed_db_path(&rootfs))?;
|
||||||
|
assert_eq!(installed_records.len(), 1);
|
||||||
|
assert_eq!(installed_records[0].real_name.as_deref(), Some("icu"));
|
||||||
|
|
||||||
|
let source_candidates =
|
||||||
|
collect_best_source_update_candidates(&config, &HashSet::from([String::from("icu")]))?;
|
||||||
|
assert!(source_candidates.contains_key("icu"));
|
||||||
|
let selected = select_update_candidate(
|
||||||
|
&installed_records[0],
|
||||||
|
installed_records[0].completed_at,
|
||||||
|
&source_candidates,
|
||||||
|
&HashMap::new(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert!(selected.is_some());
|
||||||
|
|
||||||
|
let updates = collect_update_candidates(&config, &rootfs, &["icu78".into()])?;
|
||||||
|
assert_eq!(updates.len(), 1);
|
||||||
|
assert_eq!(updates[0].installed_package, "icu78");
|
||||||
|
assert_eq!(updates[0].candidate_package, "icu79");
|
||||||
|
assert_eq!(updates[0].candidate_version, "79.1");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_candidate_prefers_binary_when_versions_match_and_config_does() {
|
fn update_candidate_prefers_binary_when_versions_match_and_config_does() {
|
||||||
let installed = db::InstalledPackageRecord {
|
let installed = db::InstalledPackageRecord {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
};
|
};
|
||||||
let source_spec = package::PackageSpec {
|
let source_spec = package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.1.0".into(),
|
version: "1.1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "test".into(),
|
description: "test".into(),
|
||||||
homepage: "https://example.test".into(),
|
homepage: "https://example.test".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -6115,8 +6561,10 @@ optional = []
|
|||||||
db::repo::BinaryRepoPackageRecord {
|
db::repo::BinaryRepoPackageRecord {
|
||||||
repo_name: "binary".into(),
|
repo_name: "binary".into(),
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.1.0".into(),
|
version: "1.1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: "pkg-1.1.0-1-x86_64.depot.pkg.tar.zst".into(),
|
filename: "pkg-1.1.0-1-x86_64.depot.pkg.tar.zst".into(),
|
||||||
size: 1,
|
size: 1,
|
||||||
@@ -6148,17 +6596,21 @@ optional = []
|
|||||||
fn select_update_candidate_uses_newer_timestamp_when_versions_match() {
|
fn select_update_candidate_uses_newer_timestamp_when_versions_match() {
|
||||||
let installed = db::InstalledPackageRecord {
|
let installed = db::InstalledPackageRecord {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: Some(100),
|
completed_at: Some(100),
|
||||||
};
|
};
|
||||||
let source_spec = package::PackageSpec {
|
let source_spec = package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "test".into(),
|
description: "test".into(),
|
||||||
homepage: "https://example.test".into(),
|
homepage: "https://example.test".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -6227,10 +6679,12 @@ optional = []
|
|||||||
let installed_spec = package::PackageSpec {
|
let installed_spec = package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "pkg".into(),
|
description: "pkg".into(),
|
||||||
homepage: "https://example.test".into(),
|
homepage: "https://example.test".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -6397,10 +6851,12 @@ optional = []
|
|||||||
let libc_spec = package::PackageSpec {
|
let libc_spec = package::PackageSpec {
|
||||||
package: package::PackageInfo {
|
package: package::PackageInfo {
|
||||||
name: "glibc".into(),
|
name: "glibc".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "glibc".into(),
|
description: "glibc".into(),
|
||||||
homepage: "https://example.test".into(),
|
homepage: "https://example.test".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -6424,7 +6880,8 @@ optional = []
|
|||||||
let missing = collect_missing_update_dependencies(
|
let missing = collect_missing_update_dependencies(
|
||||||
&[
|
&[
|
||||||
UpdateCandidate {
|
UpdateCandidate {
|
||||||
package: "pkg".into(),
|
installed_package: "pkg".into(),
|
||||||
|
candidate_package: "pkg".into(),
|
||||||
installed_version: "1.0".into(),
|
installed_version: "1.0".into(),
|
||||||
installed_revision: 1,
|
installed_revision: 1,
|
||||||
installed_completed_at: None,
|
installed_completed_at: None,
|
||||||
@@ -6441,7 +6898,8 @@ optional = []
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
UpdateCandidate {
|
UpdateCandidate {
|
||||||
package: "helper".into(),
|
installed_package: "helper".into(),
|
||||||
|
candidate_package: "helper".into(),
|
||||||
installed_version: "1.0".into(),
|
installed_version: "1.0".into(),
|
||||||
installed_revision: 1,
|
installed_revision: 1,
|
||||||
installed_completed_at: None,
|
installed_completed_at: None,
|
||||||
@@ -6458,7 +6916,8 @@ optional = []
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
UpdateCandidate {
|
UpdateCandidate {
|
||||||
package: "tool".into(),
|
installed_package: "tool".into(),
|
||||||
|
candidate_package: "tool".into(),
|
||||||
installed_version: "1.0".into(),
|
installed_version: "1.0".into(),
|
||||||
installed_revision: 1,
|
installed_revision: 1,
|
||||||
installed_completed_at: None,
|
installed_completed_at: None,
|
||||||
|
|||||||
+175
-17
@@ -21,14 +21,57 @@ fn verbose_remove_output() -> bool {
|
|||||||
/// Installed package row from the local package database.
|
/// Installed package row from the local package database.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct InstalledPackageRecord {
|
pub struct InstalledPackageRecord {
|
||||||
|
/// Installed package name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Stable package stream name used for renamed updates.
|
||||||
|
pub real_name: Option<String>,
|
||||||
|
/// Installed package version.
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
/// Installed package revision.
|
||||||
pub revision: u32,
|
pub revision: u32,
|
||||||
|
/// Whether renamed updates should retain versioned shared libraries.
|
||||||
|
pub abi_breaking: bool,
|
||||||
|
/// Package completion timestamp if known.
|
||||||
pub completed_at: Option<i64>,
|
pub completed_at: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InstalledPackageRecord {
|
||||||
|
/// Return the stable package stream name, defaulting to the package name.
|
||||||
|
pub fn effective_real_name(&self) -> &str {
|
||||||
|
self.real_name.as_deref().unwrap_or(&self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename-aware replacement metadata applied while registering a new package.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PackageReplacement {
|
||||||
|
/// Installed package name being replaced.
|
||||||
|
pub old_name: String,
|
||||||
|
/// Subset of old-package files to keep installed and owned by the old package.
|
||||||
|
pub retained_files: Vec<String>,
|
||||||
|
/// Subset of old-package directories to keep owned by the old package.
|
||||||
|
pub retained_directories: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageReplacement {
|
||||||
|
/// Return true when the replaced package remains installed after registration.
|
||||||
|
pub fn retains_old_package(&self) -> bool {
|
||||||
|
!self.retained_files.is_empty() || !self.retained_directories.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize database and register a package
|
/// Initialize database and register a package
|
||||||
pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> Result<()> {
|
pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> Result<()> {
|
||||||
|
register_package_with_replacement(db_path, spec, destdir, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize database and register a package, optionally replacing a renamed predecessor.
|
||||||
|
pub fn register_package_with_replacement(
|
||||||
|
db_path: &Path,
|
||||||
|
spec: &PackageSpec,
|
||||||
|
destdir: &Path,
|
||||||
|
replacement: Option<&PackageReplacement>,
|
||||||
|
) -> Result<()> {
|
||||||
// Create parent directory (auto-create db dir if missing)
|
// Create parent directory (auto-create db dir if missing)
|
||||||
if let Some(parent) = db_path.parent() {
|
if let Some(parent) = db_path.parent() {
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
@@ -43,24 +86,70 @@ pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> R
|
|||||||
// Generate manifest with files and directories
|
// Generate manifest with files and directories
|
||||||
let manifest = staging::generate_manifest_with_dirs(destdir)?;
|
let manifest = staging::generate_manifest_with_dirs(destdir)?;
|
||||||
|
|
||||||
|
if let Some(replacement) = replacement {
|
||||||
|
if replacement.old_name == spec.package.name {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Replacement package '{}' cannot match new package name",
|
||||||
|
spec.package.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_pkg_id: i64 = tx
|
||||||
|
.query_row(
|
||||||
|
"SELECT id FROM packages WHERE name = ?1",
|
||||||
|
params![replacement.old_name],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Package '{}' not found", replacement.old_name))?;
|
||||||
|
|
||||||
|
if replacement.retains_old_package() {
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM files WHERE package_id = ?1",
|
||||||
|
params![old_pkg_id],
|
||||||
|
)?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM directories WHERE package_id = ?1",
|
||||||
|
params![old_pkg_id],
|
||||||
|
)?;
|
||||||
|
for file in &replacement.retained_files {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO files (package_id, path) VALUES (?1, ?2)",
|
||||||
|
params![old_pkg_id, file],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
for directory in &replacement.retained_directories {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO directories (package_id, path) VALUES (?1, ?2)",
|
||||||
|
params![old_pkg_id, directory],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete_package_rows_tx(&tx, old_pkg_id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert/update package without changing its primary key (UPSERT keeps the existing row).
|
// Insert/update package without changing its primary key (UPSERT keeps the existing row).
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"INSERT INTO packages (name, version, revision, description, homepage, license, completed_at)
|
"INSERT INTO packages (name, real_name, version, revision, description, homepage, license, abi_breaking, completed_at)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||||
ON CONFLICT(name) DO UPDATE SET
|
ON CONFLICT(name) DO UPDATE SET
|
||||||
|
real_name=excluded.real_name,
|
||||||
version=excluded.version,
|
version=excluded.version,
|
||||||
revision=excluded.revision,
|
revision=excluded.revision,
|
||||||
description=excluded.description,
|
description=excluded.description,
|
||||||
homepage=excluded.homepage,
|
homepage=excluded.homepage,
|
||||||
license=excluded.license,
|
license=excluded.license,
|
||||||
|
abi_breaking=excluded.abi_breaking,
|
||||||
completed_at=excluded.completed_at",
|
completed_at=excluded.completed_at",
|
||||||
params![
|
params![
|
||||||
spec.package.name,
|
spec.package.name,
|
||||||
|
spec.package.real_name,
|
||||||
spec.package.version,
|
spec.package.version,
|
||||||
spec.package.revision,
|
spec.package.revision,
|
||||||
spec.package.description,
|
spec.package.description,
|
||||||
spec.package.homepage,
|
spec.package.homepage,
|
||||||
format_licenses(&spec.package.license),
|
format_licenses(&spec.package.license),
|
||||||
|
spec.package.abi_breaking,
|
||||||
completed_at,
|
completed_at,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
@@ -330,16 +419,7 @@ pub fn remove_package(db_path: &Path, name: &str, rootfs: &Path) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove from database
|
// Remove from database
|
||||||
conn.execute("DELETE FROM files WHERE package_id = ?1", params![pkg_id])?;
|
delete_package_rows(&conn, pkg_id)?;
|
||||||
conn.execute(
|
|
||||||
"DELETE FROM directories WHERE package_id = ?1",
|
|
||||||
params![pkg_id],
|
|
||||||
)?;
|
|
||||||
conn.execute(
|
|
||||||
"DELETE FROM provides WHERE package_id = ?1",
|
|
||||||
params![pkg_id],
|
|
||||||
)?;
|
|
||||||
conn.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
|
||||||
|
|
||||||
crate::log_info!(
|
crate::log_info!(
|
||||||
"Removed {} files and {} directories",
|
"Removed {} files and {} directories",
|
||||||
@@ -422,11 +502,13 @@ fn init_db(conn: &Connection) -> Result<()> {
|
|||||||
CREATE TABLE IF NOT EXISTS packages (
|
CREATE TABLE IF NOT EXISTS packages (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT UNIQUE NOT NULL,
|
name TEXT UNIQUE NOT NULL,
|
||||||
|
real_name TEXT,
|
||||||
version TEXT NOT NULL,
|
version TEXT NOT NULL,
|
||||||
revision INTEGER NOT NULL DEFAULT 1,
|
revision INTEGER NOT NULL DEFAULT 1,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
homepage TEXT,
|
homepage TEXT,
|
||||||
license TEXT,
|
license TEXT,
|
||||||
|
abi_breaking INTEGER NOT NULL DEFAULT 0,
|
||||||
completed_at INTEGER
|
completed_at INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -460,6 +542,8 @@ fn init_db(conn: &Connection) -> Result<()> {
|
|||||||
",
|
",
|
||||||
)?;
|
)?;
|
||||||
ensure_packages_completed_at_column(conn)?;
|
ensure_packages_completed_at_column(conn)?;
|
||||||
|
ensure_packages_real_name_column(conn)?;
|
||||||
|
ensure_packages_abi_breaking_column(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +565,73 @@ fn ensure_packages_completed_at_column(conn: &Connection) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_packages_real_name_column(conn: &Connection) -> Result<()> {
|
||||||
|
let has_real_name: bool = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'real_name'",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
let count: i64 = row.get(0)?;
|
||||||
|
Ok(count > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.context("Failed to inspect installed package DB schema")?;
|
||||||
|
if !has_real_name {
|
||||||
|
conn.execute("ALTER TABLE packages ADD COLUMN real_name TEXT", [])
|
||||||
|
.context("Failed to add real_name column to installed package DB")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_packages_abi_breaking_column(conn: &Connection) -> Result<()> {
|
||||||
|
let has_abi_breaking: bool = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'abi_breaking'",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
let count: i64 = row.get(0)?;
|
||||||
|
Ok(count > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.context("Failed to inspect installed package DB schema")?;
|
||||||
|
if !has_abi_breaking {
|
||||||
|
conn.execute(
|
||||||
|
"ALTER TABLE packages ADD COLUMN abi_breaking INTEGER NOT NULL DEFAULT 0",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.context("Failed to add abi_breaking column to installed package DB")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_package_rows(conn: &Connection, pkg_id: i64) -> Result<()> {
|
||||||
|
conn.execute("DELETE FROM files WHERE package_id = ?1", params![pkg_id])?;
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM directories WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM provides WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
|
conn.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_package_rows_tx(tx: &rusqlite::Transaction<'_>, pkg_id: i64) -> Result<()> {
|
||||||
|
tx.execute("DELETE FROM files WHERE package_id = ?1", params![pkg_id])?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM directories WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM provides WHERE package_id = ?1",
|
||||||
|
params![pkg_id],
|
||||||
|
)?;
|
||||||
|
tx.execute("DELETE FROM packages WHERE id = ?1", params![pkg_id])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn package_completed_at(destdir: &Path) -> Result<Option<i64>> {
|
fn package_completed_at(destdir: &Path) -> Result<Option<i64>> {
|
||||||
let metadata_path = destdir.join(".metadata.toml");
|
let metadata_path = destdir.join(".metadata.toml");
|
||||||
if let Some(completed_at) =
|
if let Some(completed_at) =
|
||||||
@@ -597,14 +748,19 @@ pub fn list_installed_package_records(db_path: &Path) -> Result<Vec<InstalledPac
|
|||||||
|
|
||||||
let conn = Connection::open(db_path)?;
|
let conn = Connection::open(db_path)?;
|
||||||
init_db(&conn)?;
|
init_db(&conn)?;
|
||||||
let mut stmt =
|
let mut stmt = conn.prepare(
|
||||||
conn.prepare("SELECT name, version, revision, completed_at FROM packages ORDER BY name")?;
|
"SELECT name, real_name, version, revision, abi_breaking, completed_at
|
||||||
|
FROM packages
|
||||||
|
ORDER BY name",
|
||||||
|
)?;
|
||||||
let rows = stmt.query_map([], |row| {
|
let rows = stmt.query_map([], |row| {
|
||||||
Ok(InstalledPackageRecord {
|
Ok(InstalledPackageRecord {
|
||||||
name: row.get(0)?,
|
name: row.get(0)?,
|
||||||
version: row.get(1)?,
|
real_name: row.get(1)?,
|
||||||
revision: row.get::<_, i64>(2)? as u32,
|
version: row.get(2)?,
|
||||||
completed_at: row.get(3)?,
|
revision: row.get::<_, i64>(3)? as u32,
|
||||||
|
abi_breaking: row.get(4)?,
|
||||||
|
completed_at: row.get(5)?,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Ok(rows.filter_map(|row| row.ok()).collect())
|
Ok(rows.filter_map(|row| row.ok()).collect())
|
||||||
@@ -708,10 +864,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
+117
-24
@@ -73,8 +73,10 @@ impl<R: Read> Read for HashingReader<R> {
|
|||||||
|
|
||||||
struct IndexedPackage {
|
struct IndexedPackage {
|
||||||
name: String,
|
name: String,
|
||||||
|
real_name: Option<String>,
|
||||||
version: String,
|
version: String,
|
||||||
revision: u32,
|
revision: u32,
|
||||||
|
abi_breaking: bool,
|
||||||
completed_at: Option<i64>,
|
completed_at: Option<i64>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
homepage: Option<String>,
|
homepage: Option<String>,
|
||||||
@@ -109,8 +111,10 @@ pub struct BinaryRepoSearchHit {
|
|||||||
pub struct BinaryRepoPackageRecord {
|
pub struct BinaryRepoPackageRecord {
|
||||||
pub repo_name: String,
|
pub repo_name: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub real_name: Option<String>,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub revision: u32,
|
pub revision: u32,
|
||||||
|
pub abi_breaking: bool,
|
||||||
pub completed_at: Option<i64>,
|
pub completed_at: Option<i64>,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
@@ -125,6 +129,13 @@ pub struct BinaryRepoPackageRecord {
|
|||||||
pub optional_dependencies: Vec<String>,
|
pub optional_dependencies: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BinaryRepoPackageRecord {
|
||||||
|
/// Return the stable package stream name, defaulting to the package name.
|
||||||
|
pub fn effective_real_name(&self) -> &str {
|
||||||
|
self.real_name.as_deref().unwrap_or(&self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Local cache paths for a binary package archive and its detached signature.
|
/// Local cache paths for a binary package archive and its detached signature.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BinaryRepoCachedArchive {
|
pub struct BinaryRepoCachedArchive {
|
||||||
@@ -283,8 +294,10 @@ impl RepoManager {
|
|||||||
"CREATE TABLE packages (
|
"CREATE TABLE packages (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
real_name TEXT,
|
||||||
version TEXT NOT NULL,
|
version TEXT NOT NULL,
|
||||||
revision INTEGER NOT NULL,
|
revision INTEGER NOT NULL,
|
||||||
|
abi_breaking INTEGER NOT NULL DEFAULT 0,
|
||||||
completed_at INTEGER,
|
completed_at INTEGER,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
homepage TEXT,
|
homepage TEXT,
|
||||||
@@ -346,8 +359,10 @@ impl RepoManager {
|
|||||||
let mut hashing_reader = HashingReader::new(file);
|
let mut hashing_reader = HashingReader::new(file);
|
||||||
|
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
let mut real_name = None;
|
||||||
let mut version = String::new();
|
let mut version = String::new();
|
||||||
let mut revision = 1;
|
let mut revision = 1;
|
||||||
|
let mut abi_breaking = false;
|
||||||
let mut completed_at = path_modified_unix_timestamp(pkg_path)?;
|
let mut completed_at = path_modified_unix_timestamp(pkg_path)?;
|
||||||
let mut description = None;
|
let mut description = None;
|
||||||
let mut homepage = None;
|
let mut homepage = None;
|
||||||
@@ -378,6 +393,10 @@ impl RepoManager {
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
real_name = metadata
|
||||||
|
.get("real_name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(String::from);
|
||||||
version = metadata
|
version = metadata
|
||||||
.get("version")
|
.get("version")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
@@ -387,6 +406,10 @@ impl RepoManager {
|
|||||||
.get("revision")
|
.get("revision")
|
||||||
.and_then(|v| v.as_integer())
|
.and_then(|v| v.as_integer())
|
||||||
.unwrap_or(1) as u32;
|
.unwrap_or(1) as u32;
|
||||||
|
abi_breaking = metadata
|
||||||
|
.get("abi_breaking")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
completed_at =
|
completed_at =
|
||||||
metadata_time::parse_completed_at_value(&metadata).or(completed_at);
|
metadata_time::parse_completed_at_value(&metadata).or(completed_at);
|
||||||
description = metadata
|
description = metadata
|
||||||
@@ -469,8 +492,10 @@ impl RepoManager {
|
|||||||
|
|
||||||
Ok(IndexedPackage {
|
Ok(IndexedPackage {
|
||||||
name,
|
name,
|
||||||
|
real_name,
|
||||||
version,
|
version,
|
||||||
revision,
|
revision,
|
||||||
|
abi_breaking,
|
||||||
completed_at,
|
completed_at,
|
||||||
description,
|
description,
|
||||||
homepage,
|
homepage,
|
||||||
@@ -490,8 +515,10 @@ impl RepoManager {
|
|||||||
fn insert_indexed_package(&self, conn: &mut Connection, indexed: IndexedPackage) -> Result<()> {
|
fn insert_indexed_package(&self, conn: &mut Connection, indexed: IndexedPackage) -> Result<()> {
|
||||||
let IndexedPackage {
|
let IndexedPackage {
|
||||||
name,
|
name,
|
||||||
|
real_name,
|
||||||
version,
|
version,
|
||||||
revision,
|
revision,
|
||||||
|
abi_breaking,
|
||||||
completed_at,
|
completed_at,
|
||||||
description,
|
description,
|
||||||
homepage,
|
homepage,
|
||||||
@@ -509,12 +536,14 @@ impl RepoManager {
|
|||||||
|
|
||||||
// Insert into database
|
// Insert into database
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO packages (name, version, revision, completed_at, description, homepage, license, filename, size, sha256, sha512)
|
"INSERT INTO packages (name, real_name, version, revision, abi_breaking, completed_at, description, homepage, license, filename, size, sha256, sha512)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
||||||
params![
|
params![
|
||||||
name,
|
name,
|
||||||
|
real_name,
|
||||||
version,
|
version,
|
||||||
revision as i64,
|
revision as i64,
|
||||||
|
abi_breaking,
|
||||||
completed_at,
|
completed_at,
|
||||||
description,
|
description,
|
||||||
homepage,
|
homepage,
|
||||||
@@ -1620,12 +1649,24 @@ fn find_cached_binary_repo_packages(
|
|||||||
} else {
|
} else {
|
||||||
"NULL"
|
"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!(
|
let sql = format!(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id,
|
p.id,
|
||||||
p.name,
|
p.name,
|
||||||
|
{real_name_expr},
|
||||||
p.version,
|
p.version,
|
||||||
p.revision,
|
p.revision,
|
||||||
|
{abi_breaking_expr},
|
||||||
{completed_at_expr},
|
{completed_at_expr},
|
||||||
p.filename,
|
p.filename,
|
||||||
p.size,
|
p.size,
|
||||||
@@ -1654,16 +1695,18 @@ fn find_cached_binary_repo_packages(
|
|||||||
BinaryRepoPackageRecord {
|
BinaryRepoPackageRecord {
|
||||||
repo_name: repo_name.to_string(),
|
repo_name: repo_name.to_string(),
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
version: row.get(2)?,
|
real_name: row.get(2)?,
|
||||||
revision: row.get::<_, i64>(3)? as u32,
|
version: row.get(3)?,
|
||||||
completed_at: row.get(4)?,
|
revision: row.get::<_, i64>(4)? as u32,
|
||||||
filename: row.get(5)?,
|
abi_breaking: row.get(5)?,
|
||||||
size: row.get::<_, i64>(6)? as u64,
|
completed_at: row.get(6)?,
|
||||||
sha256: row.get(7)?,
|
filename: row.get(7)?,
|
||||||
sha512: row.get(8)?,
|
size: row.get::<_, i64>(8)? as u64,
|
||||||
description: row.get(9)?,
|
sha256: row.get(9)?,
|
||||||
homepage: row.get(10)?,
|
sha512: row.get(10)?,
|
||||||
license: row.get(11)?,
|
description: row.get(11)?,
|
||||||
|
homepage: row.get(12)?,
|
||||||
|
license: row.get(13)?,
|
||||||
provides: Vec::new(),
|
provides: Vec::new(),
|
||||||
conflicts: Vec::new(),
|
conflicts: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
@@ -1696,12 +1739,24 @@ fn list_cached_binary_repo_packages(
|
|||||||
} else {
|
} else {
|
||||||
"NULL"
|
"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!(
|
let sql = format!(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id,
|
p.id,
|
||||||
p.name,
|
p.name,
|
||||||
|
{real_name_expr},
|
||||||
p.version,
|
p.version,
|
||||||
p.revision,
|
p.revision,
|
||||||
|
{abi_breaking_expr},
|
||||||
{completed_at_expr},
|
{completed_at_expr},
|
||||||
p.filename,
|
p.filename,
|
||||||
p.size,
|
p.size,
|
||||||
@@ -1722,16 +1777,18 @@ fn list_cached_binary_repo_packages(
|
|||||||
BinaryRepoPackageRecord {
|
BinaryRepoPackageRecord {
|
||||||
repo_name: repo_name.to_string(),
|
repo_name: repo_name.to_string(),
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
version: row.get(2)?,
|
real_name: row.get(2)?,
|
||||||
revision: row.get::<_, i64>(3)? as u32,
|
version: row.get(3)?,
|
||||||
completed_at: row.get(4)?,
|
revision: row.get::<_, i64>(4)? as u32,
|
||||||
filename: row.get(5)?,
|
abi_breaking: row.get(5)?,
|
||||||
size: row.get::<_, i64>(6)? as u64,
|
completed_at: row.get(6)?,
|
||||||
sha256: row.get(7)?,
|
filename: row.get(7)?,
|
||||||
sha512: row.get(8)?,
|
size: row.get::<_, i64>(8)? as u64,
|
||||||
description: row.get(9)?,
|
sha256: row.get(9)?,
|
||||||
homepage: row.get(10)?,
|
sha512: row.get(10)?,
|
||||||
license: row.get(11)?,
|
description: row.get(11)?,
|
||||||
|
homepage: row.get(12)?,
|
||||||
|
license: row.get(13)?,
|
||||||
provides: Vec::new(),
|
provides: Vec::new(),
|
||||||
conflicts: Vec::new(),
|
conflicts: Vec::new(),
|
||||||
runtime_dependencies: Vec::new(),
|
runtime_dependencies: Vec::new(),
|
||||||
@@ -1764,6 +1821,30 @@ fn repo_packages_have_completed_at(conn: &Connection) -> Result<bool> {
|
|||||||
.context("Failed to inspect binary repo DB schema")
|
.context("Failed to inspect binary repo DB schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repo_packages_have_real_name(conn: &Connection) -> Result<bool> {
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'real_name'",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
let count: i64 = row.get(0)?;
|
||||||
|
Ok(count > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.context("Failed to inspect binary repo DB schema")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repo_packages_have_abi_breaking(conn: &Connection) -> Result<bool> {
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'abi_breaking'",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
let count: i64 = row.get(0)?;
|
||||||
|
Ok(count > 0)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.context("Failed to inspect binary repo DB schema")
|
||||||
|
}
|
||||||
|
|
||||||
fn path_modified_unix_timestamp(path: &Path) -> Result<Option<i64>> {
|
fn path_modified_unix_timestamp(path: &Path) -> Result<Option<i64>> {
|
||||||
let metadata = fs::metadata(path)
|
let metadata = fs::metadata(path)
|
||||||
.with_context(|| format!("Failed to read metadata for {}", path.display()))?;
|
.with_context(|| format!("Failed to read metadata for {}", path.display()))?;
|
||||||
@@ -2350,8 +2431,10 @@ mod tests {
|
|||||||
|
|
||||||
let metadata = r#"
|
let metadata = r#"
|
||||||
name = "test"
|
name = "test"
|
||||||
|
real_name = "icu"
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
revision = 1
|
revision = 1
|
||||||
|
abi_breaking = true
|
||||||
description = "test description"
|
description = "test description"
|
||||||
homepage = "https://example.com"
|
homepage = "https://example.com"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -2385,8 +2468,10 @@ optional = []
|
|||||||
|
|
||||||
type PackageRow = (
|
type PackageRow = (
|
||||||
String,
|
String,
|
||||||
|
Option<String>,
|
||||||
String,
|
String,
|
||||||
i64,
|
i64,
|
||||||
|
i64,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
@@ -2394,9 +2479,9 @@ optional = []
|
|||||||
String,
|
String,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (name, version, revision, desc, home, lic, sha256, sha512): PackageRow = conn
|
let (name, real_name, version, revision, abi_breaking, desc, home, lic, sha256, sha512): PackageRow = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT name, version, revision, description, homepage, license, sha256, sha512 FROM packages",
|
"SELECT name, real_name, version, revision, abi_breaking, description, homepage, license, sha256, sha512 FROM packages",
|
||||||
[],
|
[],
|
||||||
|r| {
|
|r| {
|
||||||
Ok((
|
Ok((
|
||||||
@@ -2408,14 +2493,18 @@ optional = []
|
|||||||
r.get(5)?,
|
r.get(5)?,
|
||||||
r.get(6)?,
|
r.get(6)?,
|
||||||
r.get(7)?,
|
r.get(7)?,
|
||||||
|
r.get(8)?,
|
||||||
|
r.get(9)?,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(name, "test");
|
assert_eq!(name, "test");
|
||||||
|
assert_eq!(real_name, Some("icu".to_string()));
|
||||||
assert_eq!(version, "1.0");
|
assert_eq!(version, "1.0");
|
||||||
assert_eq!(revision, 1);
|
assert_eq!(revision, 1);
|
||||||
|
assert_eq!(abi_breaking, 1);
|
||||||
assert_eq!(desc, Some("test description".to_string()));
|
assert_eq!(desc, Some("test description".to_string()));
|
||||||
assert_eq!(home, Some("https://example.com".to_string()));
|
assert_eq!(home, Some("https://example.com".to_string()));
|
||||||
assert_eq!(lic, Some("MIT".to_string()));
|
assert_eq!(lic, Some("MIT".to_string()));
|
||||||
@@ -2651,8 +2740,10 @@ revision = 1
|
|||||||
let rec = BinaryRepoPackageRecord {
|
let rec = BinaryRepoPackageRecord {
|
||||||
repo_name: "repo".into(),
|
repo_name: "repo".into(),
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: "pkg.depot.pkg.tar.zst".into(),
|
filename: "pkg.depot.pkg.tar.zst".into(),
|
||||||
size: 7,
|
size: 7,
|
||||||
@@ -2687,8 +2778,10 @@ revision = 1
|
|||||||
BinaryRepoPackageRecord {
|
BinaryRepoPackageRecord {
|
||||||
repo_name: "repo".into(),
|
repo_name: "repo".into(),
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
size: payload.len() as u64,
|
size: payload.len() as u64,
|
||||||
|
|||||||
+10
@@ -483,10 +483,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: crate::package::PackageInfo {
|
package: crate::package::PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -558,10 +560,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: crate::package::PackageInfo {
|
package: crate::package::PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -605,10 +609,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: crate::package::PackageInfo {
|
package: crate::package::PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -652,18 +658,22 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: crate::package::PackageInfo {
|
package: crate::package::PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: vec![crate::package::PackageInfo {
|
packages: vec![crate::package::PackageInfo {
|
||||||
name: "foo-libs".into(),
|
name: "foo-libs".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
}],
|
}],
|
||||||
alternatives: Default::default(),
|
alternatives: Default::default(),
|
||||||
|
|||||||
@@ -1033,10 +1033,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -674,10 +674,12 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
|||||||
Ok(PackageSpec {
|
Ok(PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name,
|
name,
|
||||||
|
real_name: None,
|
||||||
version,
|
version,
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description,
|
description,
|
||||||
homepage,
|
homepage,
|
||||||
|
abi_breaking: false,
|
||||||
license,
|
license,
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -708,6 +710,9 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
// package block
|
// package block
|
||||||
let mut pkg = Table::new();
|
let mut pkg = Table::new();
|
||||||
pkg.insert("name".into(), Value::String(spec.package.name.clone()));
|
pkg.insert("name".into(), Value::String(spec.package.name.clone()));
|
||||||
|
if let Some(real_name) = &spec.package.real_name {
|
||||||
|
pkg.insert("real_name".into(), Value::String(real_name.clone()));
|
||||||
|
}
|
||||||
pkg.insert(
|
pkg.insert(
|
||||||
"version".into(),
|
"version".into(),
|
||||||
Value::String(spec.package.version.clone()),
|
Value::String(spec.package.version.clone()),
|
||||||
@@ -724,6 +729,9 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
Value::String(spec.package.homepage.clone()),
|
Value::String(spec.package.homepage.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if spec.package.abi_breaking {
|
||||||
|
pkg.insert("abi_breaking".into(), Value::Boolean(true));
|
||||||
|
}
|
||||||
if let Some(v) = licenses_to_toml_value(&spec.package.license) {
|
if let Some(v) = licenses_to_toml_value(&spec.package.license) {
|
||||||
pkg.insert("license".into(), v);
|
pkg.insert("license".into(), v);
|
||||||
}
|
}
|
||||||
@@ -735,6 +743,9 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
for p in &spec.packages {
|
for p in &spec.packages {
|
||||||
let mut pt = Table::new();
|
let mut pt = Table::new();
|
||||||
pt.insert("name".into(), Value::String(p.name.clone()));
|
pt.insert("name".into(), Value::String(p.name.clone()));
|
||||||
|
if let Some(real_name) = &p.real_name {
|
||||||
|
pt.insert("real_name".into(), Value::String(real_name.clone()));
|
||||||
|
}
|
||||||
pt.insert("version".into(), Value::String(p.version.clone()));
|
pt.insert("version".into(), Value::String(p.version.clone()));
|
||||||
if !p.description.is_empty() {
|
if !p.description.is_empty() {
|
||||||
pt.insert("description".into(), Value::String(p.description.clone()));
|
pt.insert("description".into(), Value::String(p.description.clone()));
|
||||||
@@ -742,6 +753,9 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
if !p.homepage.is_empty() {
|
if !p.homepage.is_empty() {
|
||||||
pt.insert("homepage".into(), Value::String(p.homepage.clone()));
|
pt.insert("homepage".into(), Value::String(p.homepage.clone()));
|
||||||
}
|
}
|
||||||
|
if p.abi_breaking {
|
||||||
|
pt.insert("abi_breaking".into(), Value::Boolean(true));
|
||||||
|
}
|
||||||
if let Some(v) = licenses_to_toml_value(&p.license) {
|
if let Some(v) = licenses_to_toml_value(&p.license) {
|
||||||
pt.insert("license".into(), v);
|
pt.insert("license".into(), v);
|
||||||
}
|
}
|
||||||
@@ -1483,10 +1497,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1526,18 +1542,22 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: vec![PackageInfo {
|
packages: vec![PackageInfo {
|
||||||
name: "foo-dev".into(),
|
name: "foo-dev".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "dev files".into(),
|
description: "dev files".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
}],
|
}],
|
||||||
alternatives: Alternatives::default(),
|
alternatives: Alternatives::default(),
|
||||||
@@ -1570,10 +1590,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into(), "Apache-2.0".into()],
|
license: vec!["MIT".into(), "Apache-2.0".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1651,10 +1673,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1728,10 +1752,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1764,10 +1790,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1821,10 +1849,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "A test".into(),
|
description: "A test".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1880,10 +1910,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo-meta".into(),
|
name: "foo-meta".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "Meta package".into(),
|
description: "Meta package".into(),
|
||||||
homepage: "".into(),
|
homepage: "".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1919,10 +1951,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "vertex-keyring".into(),
|
name: "vertex-keyring".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "keyring".into(),
|
description: "keyring".into(),
|
||||||
homepage: "https://www.vertexlinux.net".into(),
|
homepage: "https://www.vertexlinux.net".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -138,6 +138,12 @@ impl Packager {
|
|||||||
"version".to_string(),
|
"version".to_string(),
|
||||||
toml::Value::String(self.spec.package.version.clone()),
|
toml::Value::String(self.spec.package.version.clone()),
|
||||||
);
|
);
|
||||||
|
if let Some(real_name) = &self.spec.package.real_name {
|
||||||
|
map.insert(
|
||||||
|
"real_name".to_string(),
|
||||||
|
toml::Value::String(real_name.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
map.insert(
|
map.insert(
|
||||||
"revision".to_string(),
|
"revision".to_string(),
|
||||||
toml::Value::Integer(self.spec.package.revision as i64),
|
toml::Value::Integer(self.spec.package.revision as i64),
|
||||||
@@ -150,6 +156,10 @@ impl Packager {
|
|||||||
"homepage".to_string(),
|
"homepage".to_string(),
|
||||||
toml::Value::String(self.spec.package.homepage.clone()),
|
toml::Value::String(self.spec.package.homepage.clone()),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"abi_breaking".to_string(),
|
||||||
|
toml::Value::Boolean(self.spec.package.abi_breaking),
|
||||||
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"license".to_string(),
|
"license".to_string(),
|
||||||
license_value(&self.spec.package.license),
|
license_value(&self.spec.package.license),
|
||||||
@@ -294,10 +304,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "test".into(),
|
name: "test".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -455,6 +467,26 @@ mod tests {
|
|||||||
assert_eq!(arr[1].as_str(), Some("Apache-2.0"));
|
assert_eq!(arr[1].as_str(), Some("Apache-2.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_metadata_toml_includes_real_name_and_abi_breaking() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let dest = tmp.path();
|
||||||
|
|
||||||
|
let mut packager = mk_packager(dest.to_path_buf());
|
||||||
|
packager.spec.package.real_name = Some("icu".into());
|
||||||
|
packager.spec.package.abi_breaking = true;
|
||||||
|
packager.generate_metadata_toml().unwrap();
|
||||||
|
|
||||||
|
let meta_path = dest.join(".metadata.toml");
|
||||||
|
let content = fs::read_to_string(meta_path).unwrap();
|
||||||
|
let val: toml::Value = toml::from_str(&content).unwrap();
|
||||||
|
assert_eq!(val.get("real_name").and_then(|v| v.as_str()), Some("icu"));
|
||||||
|
assert_eq!(
|
||||||
|
val.get("abi_breaking").and_then(|v| v.as_bool()),
|
||||||
|
Some(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_metadata_toml_includes_keep_paths() {
|
fn test_generate_metadata_toml_includes_keep_paths() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -3578,10 +3578,12 @@ type = "custom"
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
real_name: None,
|
||||||
version: version.into(),
|
version: version.into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -3633,12 +3635,18 @@ impl fmt::Display for PackageSpec {
|
|||||||
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct PackageInfo {
|
pub struct PackageInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Stable package stream name used to associate renamed ABI-split packages.
|
||||||
|
#[serde(default, alias = "real-name", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub real_name: Option<String>,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
/// Maintenance revision of the package (defaults to 1)
|
/// Maintenance revision of the package (defaults to 1)
|
||||||
#[serde(default = "default_revision")]
|
#[serde(default = "default_revision")]
|
||||||
pub revision: u32,
|
pub revision: u32,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub homepage: String,
|
pub homepage: String,
|
||||||
|
/// When true, renamed updates retain versioned shared libraries from the old package.
|
||||||
|
#[serde(default, alias = "abi-breaking")]
|
||||||
|
pub abi_breaking: bool,
|
||||||
#[serde(
|
#[serde(
|
||||||
deserialize_with = "deserialize_licenses",
|
deserialize_with = "deserialize_licenses",
|
||||||
serialize_with = "serialize_licenses"
|
serialize_with = "serialize_licenses"
|
||||||
@@ -3646,6 +3654,13 @@ pub struct PackageInfo {
|
|||||||
pub license: Vec<String>,
|
pub license: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PackageInfo {
|
||||||
|
/// Return the stable package stream name, defaulting to the package name.
|
||||||
|
pub fn effective_real_name(&self) -> &str {
|
||||||
|
self.real_name.as_deref().unwrap_or(&self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn default_revision() -> u32 {
|
fn default_revision() -> u32 {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -806,18 +806,22 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: vec![PackageInfo {
|
packages: vec![PackageInfo {
|
||||||
name: "foo-libs".into(),
|
name: "foo-libs".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
}],
|
}],
|
||||||
alternatives: Alternatives::default(),
|
alternatives: Alternatives::default(),
|
||||||
@@ -878,8 +882,10 @@ mod tests {
|
|||||||
record: Box::new(db::repo::BinaryRepoPackageRecord {
|
record: Box::new(db::repo::BinaryRepoPackageRecord {
|
||||||
repo_name: repo_name.to_string(),
|
repo_name: repo_name.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0.0".to_string(),
|
version: "1.0.0".to_string(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
|
abi_breaking: false,
|
||||||
completed_at: None,
|
completed_at: None,
|
||||||
filename: format!("{name}-1.0.0-1-x86_64.depot.pkg.tar.zst"),
|
filename: format!("{name}-1.0.0-1-x86_64.depot.pkg.tar.zst"),
|
||||||
size: 1024,
|
size: 1024,
|
||||||
|
|||||||
@@ -724,10 +724,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "pkg".into(),
|
name: "pkg".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -320,10 +320,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -499,10 +499,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -556,10 +558,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -616,10 +620,12 @@ mod tests {
|
|||||||
let spec = PackageSpec {
|
let spec = PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "json".into(),
|
name: "json".into(),
|
||||||
|
real_name: None,
|
||||||
version: "3.11.3".into(),
|
version: "3.11.3".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
|
|||||||
@@ -1504,10 +1504,12 @@ mod tests {
|
|||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
name: "foo".into(),
|
name: "foo".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "d".into(),
|
description: "d".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
},
|
},
|
||||||
packages: Vec::new(),
|
packages: Vec::new(),
|
||||||
@@ -1608,10 +1610,12 @@ mod tests {
|
|||||||
let mut spec = mk_spec_for_stage_processing();
|
let mut spec = mk_spec_for_stage_processing();
|
||||||
spec.packages.push(PackageInfo {
|
spec.packages.push(PackageInfo {
|
||||||
name: "foo-dev".into(),
|
name: "foo-dev".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "dev".into(),
|
description: "dev".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
});
|
});
|
||||||
spec.build.flags.split_docs = true;
|
spec.build.flags.split_docs = true;
|
||||||
@@ -1677,18 +1681,22 @@ mod tests {
|
|||||||
let mut spec = mk_spec_for_stage_processing();
|
let mut spec = mk_spec_for_stage_processing();
|
||||||
spec.packages.push(PackageInfo {
|
spec.packages.push(PackageInfo {
|
||||||
name: "foo-dev".into(),
|
name: "foo-dev".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "dev".into(),
|
description: "dev".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["MIT".into()],
|
license: vec!["MIT".into()],
|
||||||
});
|
});
|
||||||
spec.packages.push(PackageInfo {
|
spec.packages.push(PackageInfo {
|
||||||
name: "foo-extras".into(),
|
name: "foo-extras".into(),
|
||||||
|
real_name: None,
|
||||||
version: "1.0".into(),
|
version: "1.0".into(),
|
||||||
revision: 1,
|
revision: 1,
|
||||||
description: "extras".into(),
|
description: "extras".into(),
|
||||||
homepage: "h".into(),
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
license: vec!["Apache-2.0".into()],
|
license: vec!["Apache-2.0".into()],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user