Add update and check commands with UTC package timestamps

This commit is contained in:
2026-03-10 13:50:11 -05:00
parent 242d9a7384
commit 233bd1f1fe
12 changed files with 1823 additions and 28 deletions
Generated
+16 -3
View File
@@ -382,7 +382,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
[[package]]
name = "depot"
version = "0.12.2"
version = "0.13.0"
dependencies = [
"anyhow",
"ar",
@@ -410,6 +410,7 @@ dependencies = [
"tar",
"tempfile",
"thiserror 2.0.18",
"time",
"toml",
"url",
"walkdir",
@@ -2040,9 +2041,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.25.0"
version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom 0.4.1",
@@ -2107,11 +2108,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"js-sys",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
@@ -2120,6 +2123,16 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.2"
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "depot"
version = "0.12.2"
version = "0.13.0"
edition = "2024"
[lints.rust]
@@ -21,7 +21,7 @@ serde = { version = "1.0.228", features = ["derive"] }
yaml-rust2 = "0.11.0"
sha2 = "0.10.9"
tar = "0.4.44"
tempfile = "=3.25.0"
tempfile = "=3.26.0"
thiserror = "2.0.18"
toml = "0.9.8"
url = "2.5.8"
@@ -40,6 +40,7 @@ filetime = "0.2.27"
clap_complete = "4.5.66"
clap_mangen = "0.2.31"
sys-mount = { version = "3.1.0", default-features = false }
time = { version = "0.3.47", features = ["formatting", "parsing"] }
[dev-dependencies]
tempfile = "=3.25.0"
tempfile = "=3.26.0"
+6
View File
@@ -48,6 +48,12 @@ depot install zlib-1.2.11-1-x86_64.depot.pkg.tar.zst
- `build <SPEC>`: Build a package and create an archive without installing.
- Resolves and offers to install missing build dependencies before fetching/building.
- Missing test dependencies automatically disable test execution for that build.
- `update [PACKAGE ...]`: Update installed packages from configured repositories.
- With no package names, updates every installed package that has a newer repo version available.
- Refreshes source repos first, compares installed package version/revision and UTC completion time against repo metadata, and installs any newly introduced runtime dependencies before applying updates.
- `check [DIR]`: Recursively scan `DIR` (default `.`) for package specs and report newer upstream versions when they can be inferred from versioned source URLs.
- Supports git tag-style sources such as `...git#v$version`.
- Also checks tag-style release URLs such as GitHub `releases/download/$version/...`.
- `info <PACKAGE_OR_SPEC>`: Show information about a package.
- `search <QUERY>`: Search enabled source/binary repos by package name and provided features.
- Use `--files` to search binary repo metadata file lists.
+1 -1
View File
@@ -1,6 +1,6 @@
project(
'depot',
version: '0.12.2',
version: '0.13.0',
meson_version: '>=0.60.0',
)
+41
View File
@@ -90,6 +90,18 @@ pub enum Commands {
#[arg(long)]
install: bool,
},
/// Update installed packages from configured repositories
Update {
/// Optional package names to update (defaults to all installed packages with upgrades)
#[arg(value_name = "PACKAGE", num_args = 0..)]
packages: Vec<String>,
},
/// Scan package specs for upstream version updates
Check {
/// Directory to scan recursively for package specs
#[arg(default_value = ".")]
dir: PathBuf,
},
/// Show information about a package
Info {
/// Path to package spec or installed package name
@@ -269,4 +281,33 @@ mod tests {
_ => panic!("expected install command"),
}
}
#[test]
fn update_accepts_no_package_names() {
let cli = Cli::try_parse_from(["depot", "update"]).unwrap();
match cli.command {
Commands::Update { packages } => assert!(packages.is_empty()),
_ => panic!("expected update command"),
}
}
#[test]
fn update_accepts_multiple_package_names() {
let cli = Cli::try_parse_from(["depot", "update", "linux", "openssl"]).unwrap();
match cli.command {
Commands::Update { packages } => {
assert_eq!(packages, vec!["linux".to_string(), "openssl".to_string()])
}
_ => panic!("expected update command"),
}
}
#[test]
fn check_accepts_custom_directory() {
let cli = Cli::try_parse_from(["depot", "check", "packages"]).unwrap();
match cli.command {
Commands::Check { dir } => assert_eq!(dir, PathBuf::from("packages")),
_ => panic!("expected check command"),
}
}
}
+1404 -5
View File
File diff suppressed because it is too large Load Diff
+136 -4
View File
@@ -8,11 +8,21 @@ use anyhow::{Context, Result};
use rusqlite::{Connection, params};
use std::fs;
use std::path::Path;
use walkdir::WalkDir;
fn format_licenses(licenses: &[String]) -> String {
licenses.join(", ")
}
/// Installed package row from the local package database.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledPackageRecord {
pub name: String,
pub version: String,
pub revision: u32,
pub completed_at: Option<i64>,
}
/// Initialize database and register a package
pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> Result<()> {
// Create parent directory (auto-create db dir if missing)
@@ -24,20 +34,22 @@ pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> R
init_db(&conn)?;
let tx = conn.transaction()?;
let completed_at = package_completed_at(destdir)?;
// Generate manifest with files and directories
let manifest = staging::generate_manifest_with_dirs(destdir)?;
// Insert/update package without changing its primary key (UPSERT keeps the existing row).
tx.execute(
"INSERT INTO packages (name, version, revision, description, homepage, license)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
"INSERT INTO packages (name, version, revision, description, homepage, license, completed_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
ON CONFLICT(name) DO UPDATE SET
version=excluded.version,
revision=excluded.revision,
description=excluded.description,
homepage=excluded.homepage,
license=excluded.license",
license=excluded.license,
completed_at=excluded.completed_at",
params![
spec.package.name,
spec.package.version,
@@ -45,6 +57,7 @@ pub fn register_package(db_path: &Path, spec: &PackageSpec, destdir: &Path) -> R
spec.package.description,
spec.package.homepage,
format_licenses(&spec.package.license),
completed_at,
],
)?;
@@ -374,7 +387,8 @@ fn init_db(conn: &Connection) -> Result<()> {
revision INTEGER NOT NULL DEFAULT 1,
description TEXT,
homepage TEXT,
license TEXT
license TEXT,
completed_at INTEGER
);
CREATE TABLE IF NOT EXISTS provides (
@@ -406,9 +420,62 @@ fn init_db(conn: &Connection) -> Result<()> {
CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);
",
)?;
ensure_packages_completed_at_column(conn)?;
Ok(())
}
fn ensure_packages_completed_at_column(conn: &Connection) -> Result<()> {
let has_completed_at: bool = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'completed_at'",
[],
|row| {
let count: i64 = row.get(0)?;
Ok(count > 0)
},
)
.context("Failed to inspect installed package DB schema")?;
if !has_completed_at {
conn.execute("ALTER TABLE packages ADD COLUMN completed_at INTEGER", [])
.context("Failed to add completed_at column to installed package DB")?;
}
Ok(())
}
fn package_completed_at(destdir: &Path) -> Result<Option<i64>> {
let metadata_path = destdir.join(".metadata.toml");
if let Some(completed_at) =
crate::metadata_time::read_completed_at_from_metadata_path(&metadata_path)?
{
return Ok(Some(completed_at));
}
latest_tree_mtime(destdir)
}
fn latest_tree_mtime(path: &Path) -> Result<Option<i64>> {
let mut latest = None;
for entry in WalkDir::new(path)
.into_iter()
.filter_map(|entry| entry.ok())
{
let modified = entry
.metadata()
.with_context(|| format!("Failed to read metadata for {}", entry.path().display()))?
.modified()
.with_context(|| {
format!(
"Failed to read modification time for {}",
entry.path().display()
)
})?;
let modified = crate::metadata_time::system_time_to_unix(modified)?;
latest = Some(latest.map_or(modified, |current: i64| current.max(modified)));
}
Ok(latest)
}
/// Decide whether a conflicting path is safe to auto-remove ownership for.
/// This covers common language-shared trees such as Perl site/vendor/lib
/// directories.
@@ -477,6 +544,27 @@ pub fn get_installed_packages(db_path: &Path) -> Result<std::collections::HashSe
Ok(names)
}
/// List installed packages with version and revision metadata.
pub fn list_installed_package_records(db_path: &Path) -> Result<Vec<InstalledPackageRecord>> {
if !db_path.exists() {
return Ok(Vec::new());
}
let conn = Connection::open(db_path)?;
init_db(&conn)?;
let mut stmt =
conn.prepare("SELECT name, version, revision, completed_at FROM packages ORDER BY name")?;
let rows = stmt.query_map([], |row| {
Ok(InstalledPackageRecord {
name: row.get(0)?,
version: row.get(1)?,
revision: row.get::<_, i64>(2)? as u32,
completed_at: row.get(3)?,
})
})?;
Ok(rows.filter_map(|row| row.ok()).collect())
}
/// Get set of all provided package names (alternatives)
pub fn get_all_provides(db_path: &Path) -> Result<std::collections::HashSet<String>> {
use std::collections::HashSet;
@@ -635,6 +723,50 @@ mod tests {
assert_eq!(version.as_deref(), Some("2.0"));
}
#[test]
fn register_package_uses_metadata_completed_at_when_present() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("packages.db");
let spec = mk_spec("foo", "1.0");
let dest = tmp.path().join("dest");
std::fs::create_dir_all(dest.join("usr/bin")).unwrap();
std::fs::write(dest.join("usr/bin/foo"), "bin").unwrap();
std::fs::write(
dest.join(".metadata.toml"),
"completed_at = \"2026-03-10T12:34:56Z\"\n",
)
.unwrap();
register_package(&db_path, &spec, &dest).unwrap();
let records = list_installed_package_records(&db_path).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].completed_at, Some(1_773_146_096));
}
#[test]
fn register_package_falls_back_to_destdir_mtime_when_metadata_missing() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("packages.db");
let spec = mk_spec("foo", "1.0");
let dest = tmp.path().join("dest");
std::fs::create_dir_all(dest.join("usr/bin")).unwrap();
let file = dest.join("usr/bin/foo");
std::fs::write(&file, "bin").unwrap();
let ts = filetime::FileTime::from_unix_time(1_700_000_000, 0);
filetime::set_file_mtime(&file, ts).unwrap();
filetime::set_file_mtime(&dest.join("usr"), ts).unwrap();
filetime::set_file_mtime(&dest.join("usr/bin"), ts).unwrap();
filetime::set_file_mtime(&dest, ts).unwrap();
register_package(&db_path, &spec, &dest).unwrap();
let records = list_installed_package_records(&db_path).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].completed_at, Some(1_700_000_000));
}
#[test]
fn register_package_detects_conflicting_files() {
let tmp = tempfile::tempdir().unwrap();
+153 -12
View File
@@ -1,5 +1,6 @@
//! Repository management and SQLite database generation
use crate::metadata_time;
use anyhow::{Context, Result};
use rusqlite::{Connection, params};
use sha2::{Digest, Sha256, Sha512};
@@ -73,6 +74,7 @@ struct IndexedPackage {
name: String,
version: String,
revision: u32,
completed_at: Option<i64>,
description: Option<String>,
homepage: Option<String>,
license: Option<String>,
@@ -107,6 +109,7 @@ pub struct BinaryRepoPackageRecord {
pub name: String,
pub version: String,
pub revision: u32,
pub completed_at: Option<i64>,
pub filename: String,
pub size: u64,
pub sha256: String,
@@ -279,6 +282,7 @@ impl RepoManager {
name TEXT NOT NULL,
version TEXT NOT NULL,
revision INTEGER NOT NULL,
completed_at INTEGER,
description TEXT,
homepage TEXT,
license TEXT,
@@ -335,6 +339,7 @@ impl RepoManager {
let mut name = String::new();
let mut version = String::new();
let mut revision = 1;
let mut completed_at = path_modified_unix_timestamp(pkg_path)?;
let mut description = None;
let mut homepage = None;
let mut license = None;
@@ -372,6 +377,8 @@ impl RepoManager {
.get("revision")
.and_then(|v| v.as_integer())
.unwrap_or(1) as u32;
completed_at =
metadata_time::parse_completed_at_value(&metadata).or(completed_at);
description = metadata
.get("description")
.and_then(|v| v.as_str())
@@ -444,6 +451,7 @@ impl RepoManager {
name,
version,
revision,
completed_at,
description,
homepage,
license,
@@ -463,6 +471,7 @@ impl RepoManager {
name,
version,
revision,
completed_at,
description,
homepage,
license,
@@ -478,12 +487,13 @@ impl RepoManager {
// Insert into database
conn.execute(
"INSERT INTO packages (name, version, revision, description, homepage, license, filename, size, sha256, sha512)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
"INSERT INTO packages (name, version, revision, completed_at, description, homepage, license, filename, size, sha256, sha512)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
params![
name,
version,
revision as i64,
completed_at,
description,
homepage,
license,
@@ -1503,12 +1513,18 @@ fn find_cached_binary_repo_packages(
let conn = Connection::open(db_path)
.with_context(|| format!("Failed to open binary repo DB {}", db_path.display()))?;
let mut stmt = conn.prepare(
let completed_at_expr = if repo_packages_have_completed_at(&conn)? {
"p.completed_at"
} else {
"NULL"
};
let sql = format!(
"SELECT
p.id,
p.name,
p.version,
p.revision,
{completed_at_expr},
p.filename,
p.size,
p.sha256,
@@ -1525,8 +1541,9 @@ fn find_cached_binary_repo_packages(
)
ORDER BY
CASE WHEN lower(p.name) = lower(?1) THEN 0 ELSE 1 END,
p.name ASC",
)?;
p.name ASC"
);
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map(params![query], |row| {
let package_id = row.get::<_, i64>(0)?;
@@ -1537,13 +1554,14 @@ fn find_cached_binary_repo_packages(
name: row.get(1)?,
version: row.get(2)?,
revision: row.get::<_, i64>(3)? as u32,
filename: row.get(4)?,
size: row.get::<_, i64>(5)? as u64,
sha256: row.get(6)?,
sha512: row.get(7)?,
description: row.get(8)?,
homepage: row.get(9)?,
license: row.get(10)?,
completed_at: row.get(4)?,
filename: row.get(5)?,
size: row.get::<_, i64>(6)? as u64,
sha256: row.get(7)?,
sha512: row.get(8)?,
description: row.get(9)?,
homepage: row.get(10)?,
license: row.get(11)?,
provides: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
@@ -1562,6 +1580,93 @@ fn find_cached_binary_repo_packages(
Ok(out)
}
fn list_cached_binary_repo_packages(
repo_name: &str,
db_path: &Path,
) -> Result<Vec<BinaryRepoPackageRecord>> {
let conn = Connection::open(db_path)
.with_context(|| format!("Failed to open binary repo DB {}", db_path.display()))?;
let completed_at_expr = if repo_packages_have_completed_at(&conn)? {
"p.completed_at"
} else {
"NULL"
};
let sql = format!(
"SELECT
p.id,
p.name,
p.version,
p.revision,
{completed_at_expr},
p.filename,
p.size,
p.sha256,
p.sha512,
p.description,
p.homepage,
p.license
FROM packages p
ORDER BY p.name ASC, p.version ASC, p.revision ASC"
);
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map([], |row| {
let package_id = row.get::<_, i64>(0)?;
Ok((
package_id,
BinaryRepoPackageRecord {
repo_name: repo_name.to_string(),
name: row.get(1)?,
version: row.get(2)?,
revision: row.get::<_, i64>(3)? as u32,
completed_at: row.get(4)?,
filename: row.get(5)?,
size: row.get::<_, i64>(6)? as u64,
sha256: row.get(7)?,
sha512: row.get(8)?,
description: row.get(9)?,
homepage: row.get(10)?,
license: row.get(11)?,
provides: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
},
))
})?;
let mut out = Vec::new();
for row in rows {
let (package_id, mut rec) = row?;
rec.provides = query_package_provides(&conn, package_id)?;
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
out.push(rec);
}
Ok(out)
}
fn repo_packages_have_completed_at(conn: &Connection) -> Result<bool> {
conn.query_row(
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'completed_at'",
[],
|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>> {
let metadata = fs::metadata(path)
.with_context(|| format!("Failed to read metadata for {}", path.display()))?;
let modified = metadata
.modified()
.with_context(|| format!("Failed to read modification time for {}", path.display()))?;
Ok(Some(metadata_time::system_time_to_unix(modified)?))
}
/// Resolve an exact package name/provide match from a binary repo after verifying
/// and caching its signed `repo.db.zst`.
pub fn find_binary_repo_package(
@@ -1595,6 +1700,17 @@ pub fn find_binary_repo_packages(
find_cached_binary_repo_packages(repo_name, &db_path, query)
}
/// List all binary packages from a cached, verified repository database.
pub fn list_binary_repo_packages(
repo_name: &str,
repo: &crate::config::BinaryRepo,
rootfs: &Path,
package_cache_dir: &Path,
) -> Result<Vec<BinaryRepoPackageRecord>> {
let db_path = fetch_binary_repo_db(repo_name, repo, rootfs, package_cache_dir)?;
list_cached_binary_repo_packages(repo_name, &db_path)
}
fn verify_hex_digest(path: &Path, algorithm: &str, expected_hex: &str) -> Result<bool> {
let expected = expected_hex.trim().to_ascii_lowercase();
if expected.is_empty() {
@@ -2109,6 +2225,18 @@ mod tests {
)
.unwrap();
assert!(has_sha512);
let has_completed_at: bool = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('packages') WHERE name = 'completed_at'",
[],
|r| {
let n: i64 = r.get(0)?;
Ok(n > 0)
},
)
.unwrap();
assert!(has_completed_at);
}
#[test]
@@ -2129,6 +2257,7 @@ revision = 1
description = "test description"
homepage = "https://example.com"
license = "MIT"
completed_at = "2026-03-10T12:34:56Z"
provides = ["test-feature"]
[dependencies]
@@ -2144,6 +2273,11 @@ optional = []
let encoder = tar.into_inner().unwrap();
encoder.finish().unwrap();
filetime::set_file_mtime(
&pkg_path,
filetime::FileTime::from_unix_time(1_700_000_000, 0),
)
.unwrap();
let mut conn = Connection::open_in_memory().unwrap();
let manager = RepoManager::new(repo_dir.to_path_buf());
@@ -2190,6 +2324,11 @@ optional = []
assert_eq!(sha256.len(), 64);
assert_eq!(sha512.len(), 128);
let completed_at: Option<i64> = conn
.query_row("SELECT completed_at FROM packages", [], |r| r.get(0))
.unwrap();
assert_eq!(completed_at, Some(1_773_146_096));
let provides_count: i64 = conn
.query_row("SELECT count(*) FROM provides", [], |r| r.get(0))
.unwrap();
@@ -2323,6 +2462,7 @@ license = ["MIT", "Apache-2.0"]
name: "pkg".into(),
version: "1.0".into(),
revision: 1,
completed_at: None,
filename: "pkg.depot.pkg.tar.zst".into(),
size: 7,
sha256,
@@ -2357,6 +2497,7 @@ license = ["MIT", "Apache-2.0"]
name: "pkg".into(),
version: "1.0".into(),
revision: 1,
completed_at: None,
filename: filename.to_string(),
size: payload.len() as u64,
sha256,
+1
View File
@@ -13,6 +13,7 @@ mod fakeroot;
mod index;
mod install;
mod locking;
mod metadata_time;
mod package;
mod planner;
mod runtime_env;
+50
View File
@@ -0,0 +1,50 @@
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
pub(crate) fn current_utc_timestamp_string() -> Result<String> {
OffsetDateTime::now_utc()
.format(&Rfc3339)
.context("Failed to format UTC timestamp")
}
pub(crate) fn parse_completed_at_value(metadata: &toml::Value) -> Option<i64> {
metadata
.get("completed_at")
.and_then(|value| completed_at_value_to_unix(value).ok().flatten())
}
pub(crate) fn read_completed_at_from_metadata_path(path: &Path) -> Result<Option<i64>> {
if !path.exists() {
return Ok(None);
}
let content =
fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?;
let metadata: toml::Value =
toml::from_str(&content).with_context(|| format!("Failed to parse {}", path.display()))?;
Ok(parse_completed_at_value(&metadata))
}
pub(crate) fn system_time_to_unix(time: SystemTime) -> Result<i64> {
let duration = time
.duration_since(UNIX_EPOCH)
.map_err(|err| anyhow::anyhow!("System clock is before UNIX_EPOCH: {}", err))?;
Ok(duration.as_secs() as i64)
}
fn completed_at_value_to_unix(value: &toml::Value) -> Result<Option<i64>> {
if let Some(raw) = value.as_integer() {
return Ok(Some(raw));
}
let Some(raw) = value.as_str() else {
return Ok(None);
};
let parsed = OffsetDateTime::parse(raw, &Rfc3339)
.with_context(|| format!("Invalid UTC timestamp '{}'", raw))?;
Ok(Some(parsed.unix_timestamp()))
}
+10
View File
@@ -1,6 +1,7 @@
//! Package creation and archive management
use crate::config::Config;
use crate::metadata_time;
use crate::package::PackageSpec;
use anyhow::{Context, Result};
use std::fs;
@@ -125,6 +126,7 @@ impl Packager {
fn generate_metadata_toml(&self) -> Result<()> {
let metadata_path = self.destdir.join(".metadata.toml");
let completed_at = metadata_time::current_utc_timestamp_string()?;
// Construct a simple metadata structure
let mut map = toml::map::Map::new();
@@ -152,6 +154,10 @@ impl Packager {
"license".to_string(),
license_value(&self.spec.package.license),
);
map.insert(
"completed_at".to_string(),
toml::Value::String(completed_at),
);
// Add provides
map.insert(
@@ -405,6 +411,10 @@ mod tests {
assert_eq!(val.get("version").and_then(|v| v.as_str()), Some("1.0"));
assert_eq!(val.get("revision").and_then(|v| v.as_integer()), Some(1));
assert_eq!(val.get("license").and_then(|v| v.as_str()), Some("MIT"));
assert!(
crate::metadata_time::parse_completed_at_value(&val).is_some(),
"expected RFC3339 UTC completed_at"
);
let deps = val.get("dependencies").unwrap();
assert!(deps.get("runtime").unwrap().as_array().unwrap().is_empty());
+1
View File
@@ -837,6 +837,7 @@ mod tests {
name: name.to_string(),
version: "1.0.0".to_string(),
revision: 1,
completed_at: None,
filename: format!("{name}-1.0.0-1-x86_64.depot.pkg.tar.zst"),
size: 1024,
sha256: "sha256".to_string(),