From 575d8c76f20ded72328be32b806fecf00c507fc3 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 2 Sep 2026 11:50:50 +0200 Subject: [PATCH 01/20] Initial add local metadata --- src/cli/commands/update.rs | 31 +++++++++- src/register/metadata/mod.rs | 117 +++++++++++++++++++++++++++++++++++ src/register/mod.rs | 1 + 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/register/metadata/mod.rs diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index 7afb678b..e15fb50a 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -14,14 +14,14 @@ use crate::{ }, parameter_checks, }, - config::Config, + config::{Config, Repository}, installer::{ Installer, InstallerOptions, types::{OptionalPackageId, PackageName, Version}, }, platforms::Target, - register::package_register::PackageRegister, - repositories::manager::RepositoryManager, + register::{self, package_register::PackageRegister}, + repositories::{manager::RepositoryManager, provider}, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -46,6 +46,10 @@ pub struct UpdateArgs { /// Exclude packages when using the `--all` flag, specified with ... #[arg(long, requires = "all")] exclude: Vec, + + /// Only refresh the local metadata + #[arg(long)] + refresh_only: bool, } impl HandleCommand for UpdateArgs { @@ -55,6 +59,27 @@ impl HandleCommand for UpdateArgs { let register_dir = PackageRegister::get_path(&config.prefix_directory); let mut register = PackageRegister::from(®ister_dir).unwrap_or_exit(1); + if self.refresh_only { + for package_version in register.iterate_all() { + let package_id = &package_version.package_id; + let package_dir = &package_version.install_path; + + let repository = Repository::new( + &package_version.metadata_repository_url, + &package_version.metadata_repository_provider, + ); + + let Some(provider) = provider::create_metadata_provider(&repository) else { + error!(msg: "Cannot create provider for repository"); + continue; + }; + + register::metadata::store_metadata(&provider, package_id, package_dir).unwrap_or_exit(1); + } + + return; + } + let options = InstallerOptions::default(); let installer = Installer::new(&config, &mut register, &manager, options); diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs new file mode 100644 index 00000000..4373979a --- /dev/null +++ b/src/register/metadata/mod.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-3.0-only +use std::{fs, path::Path}; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::{ + installer::{ + scripts::SCRIPT_EXTENSION, + types::{Dependency, PackageId}, + }, + platforms::Target, + repositories::{ + error::RepositoryError, + provider::MetadataProvider, + types::{PackageMeta, PackageTarget, PackageVersionMeta}, + }, + utils::ioerror::{self, IOResultExt}, +}; + +pub const DIRECTORY_NAME: &str = ".packit"; + +#[derive(Error, Debug)] +pub enum LocalMetadataError { + #[error("Cannot find metadata file '{file_path}'")] + MetadataFileNotFound { + file_path: String, + }, + + #[error("Cannot fetch package metadata from repository")] + RepositoryError(#[from] RepositoryError), + + #[error("Error while interacting with filesystem")] + IOError(#[from] ioerror::IOError), + + #[error("Cannot serialize local metadata file")] + SerializeError(#[from] toml::ser::Error), +} + +type Result = core::result::Result; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalMetadata { + dependencies: Vec, +} + +pub fn store_metadata(provider: &Box, package_id: &PackageId, package_dir: &Path) -> Result<()> { + let metadata_dir = package_dir.join(DIRECTORY_NAME); + + let package_meta = provider.read_package(&package_id.name)?; + let package_version_meta = provider.read_package_version(&package_id.name, &package_id.version)?; + let target_bounds = package_version_meta.get_best_target(&Target::current())?; + let target_meta = package_version_meta.get_target(&target_bounds)?; + + let local_metadata = create_local_metadata(&package_meta, &package_version_meta, target_meta)?; + + // Create metadata dir if it does not exist + if !metadata_dir.exists() { + fs::create_dir_all(&metadata_dir).err_with_path("create dirs", &metadata_dir)?; + } + + // Write local metadata file + let local_meta_destination = metadata_dir.join("metadata.toml"); + let local_meta_str = toml::ser::to_string(&local_metadata)?; + fs::write(&local_meta_destination, local_meta_str).err_with_path("write", local_meta_destination)?; + + // Download external test files + let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); + for external_file in external_test_files { + download_file(provider, &package_id, external_file, &metadata_dir.join(external_file), true)?; + } + + // Download test script + let test_script_path = package_version_meta.get_test_script_path(&target_bounds)?; + let test_script_destination = metadata_dir.join(format!("test.{SCRIPT_EXTENSION}")); + download_file(provider, &package_id, &test_script_path, &test_script_destination, false)?; + + // Download uninstall script + if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { + let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; + let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); + download_file(provider, &package_id, &uninstall_script_path, &uninstall_script_destination, false)?; + } + + Ok(()) +} + +fn create_local_metadata( + package_meta: &PackageMeta, + package_version_meta: &PackageVersionMeta, + target_meta: &PackageTarget, +) -> Result { + Ok(LocalMetadata { + dependencies: package_version_meta.dependencies.iter().chain(target_meta.dependencies.iter()).cloned().collect(), + }) +} + +fn download_file( + provider: &Box, + package_id: &PackageId, + file_path: &str, + destination: &Path, + required: bool, +) -> Result<()> { + let Some(bytes) = provider.read_file_bytes(&package_id.name, &file_path)? else { + if !required { + return Ok(()); + } + + return Err(LocalMetadataError::MetadataFileNotFound { + file_path: file_path.into(), + }); + }; + + fs::write(destination, bytes).err_with_path("write", destination)?; + Ok(()) +} diff --git a/src/register/mod.rs b/src/register/mod.rs index 724dd8c1..1e9a5374 100644 --- a/src/register/mod.rs +++ b/src/register/mod.rs @@ -2,4 +2,5 @@ pub mod error; pub mod installed_package; pub mod installed_package_version; +pub mod metadata; pub mod package_register; From 21cb9511697d5467a07c16bce49f8eb3ddac0dbc Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 2 Sep 2026 23:18:40 +0200 Subject: [PATCH 02/20] Add existing file content checks to local metadata generation --- src/cli/commands/update.rs | 4 +- src/register/metadata/mod.rs | 98 +++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index e15fb50a..1636fa7c 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -74,7 +74,9 @@ impl HandleCommand for UpdateArgs { continue; }; - register::metadata::store_metadata(&provider, package_id, package_dir).unwrap_or_exit(1); + let updated_metadata = register::metadata::refresh_metadata(&provider, package_id, package_dir) + .unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); + println!("{package_id}: {updated_metadata}"); } return; diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 4373979a..7cbbac60 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, +}; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -44,7 +48,9 @@ pub struct LocalMetadata { dependencies: Vec, } -pub fn store_metadata(provider: &Box, package_id: &PackageId, package_dir: &Path) -> Result<()> { +/// Refreshes the local metadata of the given package. +/// Returns true if the metadata was changed, false otherwise. +pub fn refresh_metadata(provider: &Box, package_id: &PackageId, package_dir: &Path) -> Result { let metadata_dir = package_dir.join(DIRECTORY_NAME); let package_meta = provider.read_package(&package_id.name)?; @@ -53,38 +59,63 @@ pub fn store_metadata(provider: &Box, package_id: &Package let target_meta = package_version_meta.get_target(&target_bounds)?; let local_metadata = create_local_metadata(&package_meta, &package_version_meta, target_meta)?; + let local_meta_str = toml::ser::to_string(&local_metadata)?; + + let mut updated = false; // Create metadata dir if it does not exist if !metadata_dir.exists() { fs::create_dir_all(&metadata_dir).err_with_path("create dirs", &metadata_dir)?; + updated = true; + } + + // Collect a list of all files in the metadata directory before refreshing + let mut before_files = Vec::new(); + for entry in fs::read_dir(&metadata_dir).err_with_path("read", &metadata_dir)? { + let entry = entry.err_with_path("iterate", &metadata_dir)?; + before_files.push(entry.path()); } + let mut after_files = Vec::new(); - // Write local metadata file + // If the metadata is updated, write the metadata to the file let local_meta_destination = metadata_dir.join("metadata.toml"); - let local_meta_str = toml::ser::to_string(&local_metadata)?; - fs::write(&local_meta_destination, local_meta_str).err_with_path("write", local_meta_destination)?; + let local_meta_bytes = local_meta_str.into(); + updated = updated || write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))?; // Download external test files let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); for external_file in external_test_files { - download_file(provider, &package_id, external_file, &metadata_dir.join(external_file), true)?; + let destination = metadata_dir.join(external_file); + let new_file = request_file(provider, &package_id, external_file, true)?; + updated = updated || write_file_if_changed(&before_files, &mut after_files, destination, new_file)?; } // Download test script let test_script_path = package_version_meta.get_test_script_path(&target_bounds)?; let test_script_destination = metadata_dir.join(format!("test.{SCRIPT_EXTENSION}")); - download_file(provider, &package_id, &test_script_path, &test_script_destination, false)?; + let new_file = request_file(provider, &package_id, &test_script_path, false)?; + updated = updated || write_file_if_changed(&before_files, &mut after_files, test_script_destination, new_file)?; // Download uninstall script if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); - download_file(provider, &package_id, &uninstall_script_path, &uninstall_script_destination, false)?; + let new_file = request_file(provider, &package_id, &uninstall_script_path, true)?; + updated = updated || write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)?; + } + + // Remove files that are not needed anymore + let removed_files: Vec<_> = before_files.iter().filter(|x| !after_files.contains(x)).collect(); + for removed_file in removed_files { + fs::remove_file(removed_file).err_with_path("remove", removed_file)?; + updated = true; } - Ok(()) + Ok(updated) } +/// Creates the local metadata from the given package, version and target metadata. +/// Returns the created `LocalMetadata`. fn create_local_metadata( package_meta: &PackageMeta, package_version_meta: &PackageVersionMeta, @@ -95,16 +126,13 @@ fn create_local_metadata( }) } -fn download_file( - provider: &Box, - package_id: &PackageId, - file_path: &str, - destination: &Path, - required: bool, -) -> Result<()> { +/// Requests a file from the given provider. +/// If the file cannot be found, it returns an `LocalMetadataError::MetadataFileNotFound`, or None if the file is not required. +/// Returns the bytes of the file if it can be found. +fn request_file(provider: &Box, package_id: &PackageId, file_path: &str, required: bool) -> Result> { let Some(bytes) = provider.read_file_bytes(&package_id.name, &file_path)? else { if !required { - return Ok(()); + return Ok(None); } return Err(LocalMetadataError::MetadataFileNotFound { @@ -112,6 +140,38 @@ fn download_file( }); }; - fs::write(destination, bytes).err_with_path("write", destination)?; - Ok(()) + Ok(Some(bytes)) +} + +/// Writes a metadata file when it is changed. +/// Compares the new content with the old content of the file. +/// Updates the list `after_files` when a file should be kept. +/// Returns true if the file changed, false otherwise. +fn write_file_if_changed( + before_files: &Vec, + after_files: &mut Vec, + destination: PathBuf, + new_content: Option, +) -> Result { + // Get new content, return when the file is not available + // True is returned when the file was present before, false if the file never existed + let Some(new_content) = new_content else { + return Ok(before_files.contains(&destination)); + }; + + // If the file already existed, check content equality + if before_files.contains(&destination) { + let old_content = fs::read(&destination).err_with_path("read", &destination)?; + + // If the file did not change, skip writing and store it as new file + if new_content == old_content { + after_files.push(destination); + return Ok(false); + } + } + + // Write new file data + fs::write(&destination, new_content).err_with_path("write", &destination)?; + after_files.push(destination); + Ok(true) } From 210c06c3aaef43eb918cb452c1f282db99281cb1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 14:00:12 +0200 Subject: [PATCH 03/20] Implement refresh functionality in update command properly --- src/cli/commands/update.rs | 64 ++++++++++++++++++++++++------------ src/register/metadata/mod.rs | 16 ++++++--- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index 1636fa7c..cd086255 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -59,33 +59,16 @@ impl HandleCommand for UpdateArgs { let register_dir = PackageRegister::get_path(&config.prefix_directory); let mut register = PackageRegister::from(®ister_dir).unwrap_or_exit(1); + // If refresh only is specified, only refresh the specified packages if self.refresh_only { - for package_version in register.iterate_all() { - let package_id = &package_version.package_id; - let package_dir = &package_version.install_path; - - let repository = Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ); - - let Some(provider) = provider::create_metadata_provider(&repository) else { - error!(msg: "Cannot create provider for repository"); - continue; - }; - - let updated_metadata = register::metadata::refresh_metadata(&provider, package_id, package_dir) - .unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); - println!("{package_id}: {updated_metadata}"); - } - + self.refresh_metadata(®ister, &config); return; } let options = InstallerOptions::default(); let installer = Installer::new(&config, &mut register, &manager, options); - // If `--all` is specified use all the updatable pacakges + // If `--all` is specified use all the updatable packages let optional_ids = match self.all { true => &self.get_updatables(installer), false if self.packages.is_empty() => { @@ -108,6 +91,7 @@ impl HandleCommand for UpdateArgs { exit(1); } + // Update all given packages for optional_id in optional_ids { match optional_id.versioned() { Some(package_id) if register.get_package_version(&package_id).is_some() => {}, @@ -154,6 +138,9 @@ impl HandleCommand for UpdateArgs { None => println!("{} is up-to-date!", optional_id.name.style()), } } + + // Refresh metadata of all given packages + self.refresh_metadata(®ister, &config); } } @@ -171,9 +158,10 @@ impl UpdateArgs { } } + // If there are no updatable packages, show a message and return an empty list if filtered_updatables.is_empty() { println!("All packages are up-to-date!"); - exit(0); + return Vec::new(); } println!("The following packages will be updated:"); @@ -188,4 +176,38 @@ impl UpdateArgs { filtered_updatables.into_iter().map(OptionalPackageId::from).collect() } + + fn refresh_metadata(&self, register: &PackageRegister, config: &Config) { + // If `--all` is specified use all installed packages + let packages = match self.all { + // Only filter exclude packages when all is specified + true => ®ister.iterate_all().map(|x| x.package_id.clone()).filter(|x| !self.exclude.contains(&x.name)).collect(), + false if self.packages.is_empty() => { + error!(msg: "No packages specified to refresh"); + exit(1); + }, + false => ¶meter_checks::expand_optional_ids(®ister, &config, &self.packages), + }; + + for package_id in packages { + let Some(package_version) = register.get_package_version(package_id) else { + error!(msg: "Expected package version {} to exist, skipping refresh", package_id.style()); + continue; + }; + + let repository = Repository::new( + &package_version.metadata_repository_url, + &package_version.metadata_repository_provider, + ); + + let Some(provider) = provider::create_metadata_provider(&repository) else { + error!(msg: "Cannot create provider for repository"); + continue; + }; + + let updated_metadata = register::metadata::refresh_metadata(&provider, package_id, &package_version.install_path) + .unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); + println!("{package_id}: {updated_metadata}"); + } + } } diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 7cbbac60..3df9be75 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -80,28 +80,36 @@ pub fn refresh_metadata(provider: &Box, package_id: &Packa // If the metadata is updated, write the metadata to the file let local_meta_destination = metadata_dir.join("metadata.toml"); let local_meta_bytes = local_meta_str.into(); - updated = updated || write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))?; + if write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))? { + updated = true; + } // Download external test files let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); for external_file in external_test_files { let destination = metadata_dir.join(external_file); let new_file = request_file(provider, &package_id, external_file, true)?; - updated = updated || write_file_if_changed(&before_files, &mut after_files, destination, new_file)?; + if write_file_if_changed(&before_files, &mut after_files, destination, new_file)? { + updated = true; + } } // Download test script let test_script_path = package_version_meta.get_test_script_path(&target_bounds)?; let test_script_destination = metadata_dir.join(format!("test.{SCRIPT_EXTENSION}")); let new_file = request_file(provider, &package_id, &test_script_path, false)?; - updated = updated || write_file_if_changed(&before_files, &mut after_files, test_script_destination, new_file)?; + if write_file_if_changed(&before_files, &mut after_files, test_script_destination, new_file)? { + updated = true; + } // Download uninstall script if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); let new_file = request_file(provider, &package_id, &uninstall_script_path, true)?; - updated = updated || write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)?; + if write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)? { + updated = true; + } } // Remove files that are not needed anymore From 322e508e58e11a7a7b1786b164e23259dd1c12b1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 14:09:00 +0200 Subject: [PATCH 04/20] Add local metadata refresh after package install --- src/installer/installer.rs | 10 +++++++++- src/repositories/manager.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/installer/installer.rs b/src/installer/installer.rs index 751b9502..64629668 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -25,7 +25,7 @@ use crate::{ unpack::unpack, }, platforms::{DEFAULT_PREFIX, Target, permissions, symlink}, - register::{installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, + register::{self, installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, repositories::{ manager::RepositoryManager, provider, @@ -240,6 +240,14 @@ impl<'a> Installer<'a> { ); self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; + // TODO + register::metadata::refresh_metadata( + self.repository_manager.get_metadata_provider(&install_meta.repository_id)?, + &package_id, + &install_directory, + ) + .unwrap(); + self.execute_postinstall(&package_id, install_meta, &install_directory, &script_args)?; self.determine_active(install_meta, &package_id, target_meta)?; diff --git a/src/repositories/manager.rs b/src/repositories/manager.rs index ec3b8745..79a81aa9 100644 --- a/src/repositories/manager.rs +++ b/src/repositories/manager.rs @@ -495,7 +495,7 @@ impl<'a> RepositoryManager<'a> { /// A helper method to get the metadata provider. /// Returns a `RepositoryNotFoundError` if no repository with the given `repository_id` can be found. #[expect(clippy::borrowed_box)] - fn get_metadata_provider(&self, repository_id: &str) -> Result<&Box> { + pub fn get_metadata_provider(&self, repository_id: &str) -> Result<&Box> { // Check if repository is unsupported if self.unsupported_repositories.contains_key(repository_id) { return Err(RepositoryError::RepositoryNotSupported { From 77e57eeda24ad1c93dfa7f111acf935315ae893c Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 16:15:48 +0200 Subject: [PATCH 05/20] Add reading of local metadata --- src/register/metadata/mod.rs | 45 +++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 3df9be75..0b3d15b2 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -23,20 +23,29 @@ use crate::{ }; pub const DIRECTORY_NAME: &str = ".packit"; +const METADATA_FILENAME: &str = "metadata.toml"; #[derive(Error, Debug)] pub enum LocalMetadataError { - #[error("Cannot find metadata file '{file_path}'")] - MetadataFileNotFound { + #[error("Cannot find metadata file '{file_path}' in source repository")] + RepositoryMetadataFileNotFound { file_path: String, }, + #[error("Cannot find local metadata file '{}'", file_path.display())] + LocalMetadataFileNotFound { + file_path: PathBuf, + }, + #[error("Cannot fetch package metadata from repository")] RepositoryError(#[from] RepositoryError), #[error("Error while interacting with filesystem")] IOError(#[from] ioerror::IOError), + #[error("Cannot parse local metadata file")] + ParseError(#[from] toml::de::Error), + #[error("Cannot serialize local metadata file")] SerializeError(#[from] toml::ser::Error), } @@ -48,10 +57,34 @@ pub struct LocalMetadata { dependencies: Vec, } +/// Reads the local metadata file from the storage of the given package. +/// Returns the `LocalMetadata` parsed from the storage. +pub fn read_local_metadata(package_install_dir: &Path) -> Result { + let path = package_install_dir.join(DIRECTORY_NAME).join(METADATA_FILENAME); + if !path.exists() { + return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); + } + + let content = fs::read_to_string(&path).err_with_path("read", &path)?; + Ok(toml::de::from_str(&content)?) +} + +/// Reads the specified local metadata file from the storage of the given package. +/// Returns the file as bytes. +pub fn read_local_meta_file(package_install_dir: &Path, file: &str) -> Result { + let path = package_install_dir.join(DIRECTORY_NAME).join(file); + if !path.exists() { + return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); + } + + let content = fs::read(&path).err_with_path("read", &path)?; + Ok(content.into()) +} + /// Refreshes the local metadata of the given package. /// Returns true if the metadata was changed, false otherwise. -pub fn refresh_metadata(provider: &Box, package_id: &PackageId, package_dir: &Path) -> Result { - let metadata_dir = package_dir.join(DIRECTORY_NAME); +pub fn refresh_metadata(provider: &Box, package_id: &PackageId, package_install_dir: &Path) -> Result { + let metadata_dir = package_install_dir.join(DIRECTORY_NAME); let package_meta = provider.read_package(&package_id.name)?; let package_version_meta = provider.read_package_version(&package_id.name, &package_id.version)?; @@ -78,7 +111,7 @@ pub fn refresh_metadata(provider: &Box, package_id: &Packa let mut after_files = Vec::new(); // If the metadata is updated, write the metadata to the file - let local_meta_destination = metadata_dir.join("metadata.toml"); + let local_meta_destination = metadata_dir.join(METADATA_FILENAME); let local_meta_bytes = local_meta_str.into(); if write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))? { updated = true; @@ -143,7 +176,7 @@ fn request_file(provider: &Box, package_id: &PackageId, fi return Ok(None); } - return Err(LocalMetadataError::MetadataFileNotFound { + return Err(LocalMetadataError::RepositoryMetadataFileNotFound { file_path: file_path.into(), }); }; From b44b12d55e8c73598e22457cf6d0828095645912 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 16:30:45 +0200 Subject: [PATCH 06/20] Refactor local metadata implementation --- src/cli/commands/update.rs | 6 +- src/installer/error.rs | 5 +- src/installer/installer.rs | 12 +- src/register/installed_package_version.rs | 9 +- src/register/metadata/error.rs | 33 ++++ src/register/metadata/handler.rs | 206 ++++++++++++++++++++ src/register/metadata/mod.rs | 220 +--------------------- 7 files changed, 262 insertions(+), 229 deletions(-) create mode 100644 src/register/metadata/error.rs create mode 100644 src/register/metadata/handler.rs diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index cd086255..abfb1ef4 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -20,7 +20,7 @@ use crate::{ types::{OptionalPackageId, PackageName, Version}, }, platforms::Target, - register::{self, package_register::PackageRegister}, + register::package_register::PackageRegister, repositories::{manager::RepositoryManager, provider}, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -205,8 +205,8 @@ impl UpdateArgs { continue; }; - let updated_metadata = register::metadata::refresh_metadata(&provider, package_id, &package_version.install_path) - .unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); + let local_meta = package_version.get_local_metadata(); + let updated_metadata = local_meta.refresh(&provider).unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); println!("{package_id}: {updated_metadata}"); } } diff --git a/src/installer/error.rs b/src/installer/error.rs index 2c5beea4..35068fff 100644 --- a/src/installer/error.rs +++ b/src/installer/error.rs @@ -11,7 +11,7 @@ use crate::{ unpack::UnpackError, }, platforms::{permissions::error::PermissionError, symlink::SymlinkError}, - register::error::RegisterError, + register::{error::RegisterError, metadata::error::LocalMetadataError}, repositories::error::RepositoryError, utils::{ioerror, tree::TreeError}, }; @@ -103,6 +103,9 @@ pub enum InstallerError { #[error("Error while setting or getting permissions")] PermissionError(#[from] PermissionError), + #[error("Error while using local metadata")] + LocalMetadataError(#[from] LocalMetadataError), + #[error("Error while interacting with filesystem")] IOError(#[from] ioerror::IOError), } diff --git a/src/installer/installer.rs b/src/installer/installer.rs index 64629668..d29f44e6 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -25,7 +25,7 @@ use crate::{ unpack::unpack, }, platforms::{DEFAULT_PREFIX, Target, permissions, symlink}, - register::{self, installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, + register::{installed_package_version::InstalledPackageVersion, metadata::LocalMetaHandler, package_register::PackageRegister}, repositories::{ manager::RepositoryManager, provider, @@ -240,13 +240,9 @@ impl<'a> Installer<'a> { ); self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; - // TODO - register::metadata::refresh_metadata( - self.repository_manager.get_metadata_provider(&install_meta.repository_id)?, - &package_id, - &install_directory, - ) - .unwrap(); + // Refresh the local metadata for the new package + let local_metadata = LocalMetaHandler::new(&package_id, &install_directory); + local_metadata.refresh(self.repository_manager.get_metadata_provider(&install_meta.repository_id)?)?; self.execute_postinstall(&package_id, install_meta, &install_directory, &script_args)?; diff --git a/src/register/installed_package_version.rs b/src/register/installed_package_version.rs index 500fa6b4..747e196b 100644 --- a/src/register/installed_package_version.rs +++ b/src/register/installed_package_version.rs @@ -3,7 +3,7 @@ use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; -use crate::{config::Repository, installer::types::PackageId, repositories::types::Licenses}; +use crate::{config::Repository, installer::types::PackageId, register::metadata::LocalMetaHandler, repositories::types::Licenses}; /// Represents a specific package version which is installed on the system. #[cfg_attr(test, derive(PartialEq))] @@ -42,3 +42,10 @@ pub struct InstalledPackageVersion { fn is_repository_provider_default(value: &String) -> bool { *value == Repository::default_repository_provider() } + +impl InstalledPackageVersion { + /// Gets the local metadata handler for the installed package version. + pub fn get_local_metadata(&self) -> LocalMetaHandler<'_> { + LocalMetaHandler::new(&self.package_id, &self.install_path) + } +} diff --git a/src/register/metadata/error.rs b/src/register/metadata/error.rs new file mode 100644 index 00000000..c8fc2adb --- /dev/null +++ b/src/register/metadata/error.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-only +use std::path::PathBuf; + +use thiserror::Error; + +use crate::{repositories::error::RepositoryError, utils::ioerror}; + +#[derive(Error, Debug)] +pub enum LocalMetadataError { + #[error("Cannot find metadata file '{file_path}' in source repository")] + RepositoryMetadataFileNotFound { + file_path: String, + }, + + #[error("Cannot find local metadata file '{}'", file_path.display())] + LocalMetadataFileNotFound { + file_path: PathBuf, + }, + + #[error("Cannot fetch package metadata from repository")] + RepositoryError(#[from] RepositoryError), + + #[error("Error while interacting with filesystem")] + IOError(#[from] ioerror::IOError), + + #[error("Cannot parse local metadata file")] + ParseError(#[from] toml::de::Error), + + #[error("Cannot serialize local metadata file")] + SerializeError(#[from] toml::ser::Error), +} + +pub type Result = core::result::Result; diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs new file mode 100644 index 00000000..cbd2a1df --- /dev/null +++ b/src/register/metadata/handler.rs @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-3.0-only +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use bytes::Bytes; +use serde::{Deserialize, Serialize}; + +use crate::{ + installer::{ + scripts::SCRIPT_EXTENSION, + types::{Dependency, PackageId}, + }, + platforms::Target, + register::metadata::error::{LocalMetadataError, Result}, + repositories::{ + provider::MetadataProvider, + types::{PackageMeta, PackageTarget, PackageVersionMeta}, + }, + utils::ioerror::IOResultExt, +}; + +pub const DIRECTORY_NAME: &str = ".packit"; +const METADATA_FILENAME: &str = "metadata.toml"; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalMetadata { + dependencies: Vec, +} + +pub struct LocalMetaHandler<'a> { + package_id: &'a PackageId, + package_install_dir: &'a Path, +} + +impl<'a> LocalMetaHandler<'a> { + pub fn new(package_id: &'a PackageId, package_install_dir: &'a Path) -> Self { + Self { + package_id, + package_install_dir, + } + } + + /// Reads the local metadata file from the storage of the given package. + /// Returns the `LocalMetadata` parsed from the storage. + pub fn read_metadata(&self) -> Result { + let path = self.package_install_dir.join(DIRECTORY_NAME).join(METADATA_FILENAME); + if !path.exists() { + return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); + } + + let content = fs::read_to_string(&path).err_with_path("read", &path)?; + Ok(toml::de::from_str(&content)?) + } + + /// Reads the specified local metadata file from the storage of the given package. + /// Returns the file as bytes. + pub fn read_file(&self, file: &str) -> Result { + let path = self.package_install_dir.join(DIRECTORY_NAME).join(file); + if !path.exists() { + return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); + } + + let content = fs::read(&path).err_with_path("read", &path)?; + Ok(content.into()) + } + + /// Refreshes the local metadata of the given package. + /// Returns true if the metadata was changed, false otherwise. + pub fn refresh(&self, provider: &Box) -> Result { + let metadata_dir = self.package_install_dir.join(DIRECTORY_NAME); + + let package_meta = provider.read_package(&self.package_id.name)?; + let package_version_meta = provider.read_package_version(&self.package_id.name, &self.package_id.version)?; + let target_bounds = package_version_meta.get_best_target(&Target::current())?; + let target_meta = package_version_meta.get_target(&target_bounds)?; + + let local_metadata = self.create_local_metadata(&package_meta, &package_version_meta, target_meta)?; + let local_meta_str = toml::ser::to_string(&local_metadata)?; + + let mut updated = false; + + // Create metadata dir if it does not exist + if !metadata_dir.exists() { + fs::create_dir_all(&metadata_dir).err_with_path("create dirs", &metadata_dir)?; + updated = true; + } + + // Collect a list of all files in the metadata directory before refreshing + let mut before_files = Vec::new(); + for entry in fs::read_dir(&metadata_dir).err_with_path("read", &metadata_dir)? { + let entry = entry.err_with_path("iterate", &metadata_dir)?; + before_files.push(entry.path()); + } + let mut after_files = Vec::new(); + + // If the metadata is updated, write the metadata to the file + let local_meta_destination = metadata_dir.join(METADATA_FILENAME); + let local_meta_bytes = local_meta_str.into(); + if self.write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))? { + updated = true; + } + + // Download external test files + let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); + for external_file in external_test_files { + let destination = metadata_dir.join(external_file); + let new_file = self.request_file(provider, external_file, true)?; + if self.write_file_if_changed(&before_files, &mut after_files, destination, new_file)? { + updated = true; + } + } + + // Download test script + let test_script_path = package_version_meta.get_test_script_path(&target_bounds)?; + let test_script_destination = metadata_dir.join(format!("test.{SCRIPT_EXTENSION}")); + let new_file = self.request_file(provider, &test_script_path, false)?; + if self.write_file_if_changed(&before_files, &mut after_files, test_script_destination, new_file)? { + updated = true; + } + + // Download uninstall script + if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { + let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; + let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); + let new_file = self.request_file(provider, &uninstall_script_path, true)?; + if self.write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)? { + updated = true; + } + } + + // Remove files that are not needed anymore + let removed_files: Vec<_> = before_files.iter().filter(|x| !after_files.contains(x)).collect(); + for removed_file in removed_files { + fs::remove_file(removed_file).err_with_path("remove", removed_file)?; + updated = true; + } + + Ok(updated) + } + + /// Creates the local metadata from the given package, version and target metadata. + /// Returns the created `LocalMetadata`. + fn create_local_metadata( + &self, + package_meta: &PackageMeta, + package_version_meta: &PackageVersionMeta, + target_meta: &PackageTarget, + ) -> Result { + Ok(LocalMetadata { + dependencies: package_version_meta.dependencies.iter().chain(target_meta.dependencies.iter()).cloned().collect(), + }) + } + + /// Requests a file from the given provider. + /// If the file cannot be found, it returns an `LocalMetadataError::MetadataFileNotFound`, or None if the file is not required. + /// Returns the bytes of the file if it can be found. + fn request_file(&self, provider: &Box, file_path: &str, required: bool) -> Result> { + let Some(bytes) = provider.read_file_bytes(&self.package_id.name, &file_path)? else { + if !required { + return Ok(None); + } + + return Err(LocalMetadataError::RepositoryMetadataFileNotFound { + file_path: file_path.into(), + }); + }; + + Ok(Some(bytes)) + } + + /// Writes a metadata file when it is changed. + /// Compares the new content with the old content of the file. + /// Updates the list `after_files` when a file should be kept. + /// Returns true if the file changed, false otherwise. + fn write_file_if_changed( + &self, + before_files: &Vec, + after_files: &mut Vec, + destination: PathBuf, + new_content: Option, + ) -> Result { + // Get new content, return when the file is not available + // True is returned when the file was present before, false if the file never existed + let Some(new_content) = new_content else { + return Ok(before_files.contains(&destination)); + }; + + // If the file already existed, check content equality + if before_files.contains(&destination) { + let old_content = fs::read(&destination).err_with_path("read", &destination)?; + + // If the file did not change, skip writing and store it as new file + if new_content == old_content { + after_files.push(destination); + return Ok(false); + } + } + + // Write new file data + fs::write(&destination, new_content).err_with_path("write", &destination)?; + after_files.push(destination); + Ok(true) + } +} diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 0b3d15b2..4a85abb1 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -1,218 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{ - fs, - path::{Path, PathBuf}, -}; +pub mod error; +mod handler; -use bytes::Bytes; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::{ - installer::{ - scripts::SCRIPT_EXTENSION, - types::{Dependency, PackageId}, - }, - platforms::Target, - repositories::{ - error::RepositoryError, - provider::MetadataProvider, - types::{PackageMeta, PackageTarget, PackageVersionMeta}, - }, - utils::ioerror::{self, IOResultExt}, -}; - -pub const DIRECTORY_NAME: &str = ".packit"; -const METADATA_FILENAME: &str = "metadata.toml"; - -#[derive(Error, Debug)] -pub enum LocalMetadataError { - #[error("Cannot find metadata file '{file_path}' in source repository")] - RepositoryMetadataFileNotFound { - file_path: String, - }, - - #[error("Cannot find local metadata file '{}'", file_path.display())] - LocalMetadataFileNotFound { - file_path: PathBuf, - }, - - #[error("Cannot fetch package metadata from repository")] - RepositoryError(#[from] RepositoryError), - - #[error("Error while interacting with filesystem")] - IOError(#[from] ioerror::IOError), - - #[error("Cannot parse local metadata file")] - ParseError(#[from] toml::de::Error), - - #[error("Cannot serialize local metadata file")] - SerializeError(#[from] toml::ser::Error), -} - -type Result = core::result::Result; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LocalMetadata { - dependencies: Vec, -} - -/// Reads the local metadata file from the storage of the given package. -/// Returns the `LocalMetadata` parsed from the storage. -pub fn read_local_metadata(package_install_dir: &Path) -> Result { - let path = package_install_dir.join(DIRECTORY_NAME).join(METADATA_FILENAME); - if !path.exists() { - return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); - } - - let content = fs::read_to_string(&path).err_with_path("read", &path)?; - Ok(toml::de::from_str(&content)?) -} - -/// Reads the specified local metadata file from the storage of the given package. -/// Returns the file as bytes. -pub fn read_local_meta_file(package_install_dir: &Path, file: &str) -> Result { - let path = package_install_dir.join(DIRECTORY_NAME).join(file); - if !path.exists() { - return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); - } - - let content = fs::read(&path).err_with_path("read", &path)?; - Ok(content.into()) -} - -/// Refreshes the local metadata of the given package. -/// Returns true if the metadata was changed, false otherwise. -pub fn refresh_metadata(provider: &Box, package_id: &PackageId, package_install_dir: &Path) -> Result { - let metadata_dir = package_install_dir.join(DIRECTORY_NAME); - - let package_meta = provider.read_package(&package_id.name)?; - let package_version_meta = provider.read_package_version(&package_id.name, &package_id.version)?; - let target_bounds = package_version_meta.get_best_target(&Target::current())?; - let target_meta = package_version_meta.get_target(&target_bounds)?; - - let local_metadata = create_local_metadata(&package_meta, &package_version_meta, target_meta)?; - let local_meta_str = toml::ser::to_string(&local_metadata)?; - - let mut updated = false; - - // Create metadata dir if it does not exist - if !metadata_dir.exists() { - fs::create_dir_all(&metadata_dir).err_with_path("create dirs", &metadata_dir)?; - updated = true; - } - - // Collect a list of all files in the metadata directory before refreshing - let mut before_files = Vec::new(); - for entry in fs::read_dir(&metadata_dir).err_with_path("read", &metadata_dir)? { - let entry = entry.err_with_path("iterate", &metadata_dir)?; - before_files.push(entry.path()); - } - let mut after_files = Vec::new(); - - // If the metadata is updated, write the metadata to the file - let local_meta_destination = metadata_dir.join(METADATA_FILENAME); - let local_meta_bytes = local_meta_str.into(); - if write_file_if_changed(&before_files, &mut after_files, local_meta_destination, Some(local_meta_bytes))? { - updated = true; - } - - // Download external test files - let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); - for external_file in external_test_files { - let destination = metadata_dir.join(external_file); - let new_file = request_file(provider, &package_id, external_file, true)?; - if write_file_if_changed(&before_files, &mut after_files, destination, new_file)? { - updated = true; - } - } - - // Download test script - let test_script_path = package_version_meta.get_test_script_path(&target_bounds)?; - let test_script_destination = metadata_dir.join(format!("test.{SCRIPT_EXTENSION}")); - let new_file = request_file(provider, &package_id, &test_script_path, false)?; - if write_file_if_changed(&before_files, &mut after_files, test_script_destination, new_file)? { - updated = true; - } - - // Download uninstall script - if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { - let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; - let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); - let new_file = request_file(provider, &package_id, &uninstall_script_path, true)?; - if write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)? { - updated = true; - } - } - - // Remove files that are not needed anymore - let removed_files: Vec<_> = before_files.iter().filter(|x| !after_files.contains(x)).collect(); - for removed_file in removed_files { - fs::remove_file(removed_file).err_with_path("remove", removed_file)?; - updated = true; - } - - Ok(updated) -} - -/// Creates the local metadata from the given package, version and target metadata. -/// Returns the created `LocalMetadata`. -fn create_local_metadata( - package_meta: &PackageMeta, - package_version_meta: &PackageVersionMeta, - target_meta: &PackageTarget, -) -> Result { - Ok(LocalMetadata { - dependencies: package_version_meta.dependencies.iter().chain(target_meta.dependencies.iter()).cloned().collect(), - }) -} - -/// Requests a file from the given provider. -/// If the file cannot be found, it returns an `LocalMetadataError::MetadataFileNotFound`, or None if the file is not required. -/// Returns the bytes of the file if it can be found. -fn request_file(provider: &Box, package_id: &PackageId, file_path: &str, required: bool) -> Result> { - let Some(bytes) = provider.read_file_bytes(&package_id.name, &file_path)? else { - if !required { - return Ok(None); - } - - return Err(LocalMetadataError::RepositoryMetadataFileNotFound { - file_path: file_path.into(), - }); - }; - - Ok(Some(bytes)) -} - -/// Writes a metadata file when it is changed. -/// Compares the new content with the old content of the file. -/// Updates the list `after_files` when a file should be kept. -/// Returns true if the file changed, false otherwise. -fn write_file_if_changed( - before_files: &Vec, - after_files: &mut Vec, - destination: PathBuf, - new_content: Option, -) -> Result { - // Get new content, return when the file is not available - // True is returned when the file was present before, false if the file never existed - let Some(new_content) = new_content else { - return Ok(before_files.contains(&destination)); - }; - - // If the file already existed, check content equality - if before_files.contains(&destination) { - let old_content = fs::read(&destination).err_with_path("read", &destination)?; - - // If the file did not change, skip writing and store it as new file - if new_content == old_content { - after_files.push(destination); - return Ok(false); - } - } - - // Write new file data - fs::write(&destination, new_content).err_with_path("write", &destination)?; - after_files.push(destination); - Ok(true) -} +pub use handler::LocalMetaHandler; +pub use handler::LocalMetadata; From 731b050088384759753b3b95779e6132c1e366d1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 17:14:51 +0200 Subject: [PATCH 07/20] Add all required fields to LocalMetadata --- src/register/metadata/handler.rs | 72 ++++++++++++++++++++++++++++---- src/repositories/types/common.rs | 2 +- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index cbd2a1df..6f59f83f 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use std::{ + cmp::max, + collections::{HashMap, HashSet}, fs, path::{Path, PathBuf}, }; @@ -10,13 +12,13 @@ use serde::{Deserialize, Serialize}; use crate::{ installer::{ scripts::SCRIPT_EXTENSION, - types::{Dependency, PackageId}, + types::{Dependency, PackageId, PackageName, Version}, }, platforms::Target, register::metadata::error::{LocalMetadataError, Result}, repositories::{ provider::MetadataProvider, - types::{PackageMeta, PackageTarget, PackageVersionMeta}, + types::{DeprecationInfo, PackageMeta, PackageVersionMeta, Requirement, TargetBounds}, }, utils::ioerror::IOResultExt, }; @@ -24,17 +26,40 @@ use crate::{ pub const DIRECTORY_NAME: &str = ".packit"; const METADATA_FILENAME: &str = "metadata.toml"; -#[derive(Clone, Debug, Serialize, Deserialize)] +/// Represents the local metadata. +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct LocalMetadata { - dependencies: Vec, + pub required_packit_version: Option, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dependencies: Vec, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub test_requirements: Vec, + + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub external_test_files: HashSet, + + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub script_args: HashMap, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub revisions: Vec, + pub deprecation: Option, + pub skip_symlinking: bool, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub conflicts_with: Vec, } +/// Handler which handles the reading and refreshing of local metadata. pub struct LocalMetaHandler<'a> { package_id: &'a PackageId, package_install_dir: &'a Path, } impl<'a> LocalMetaHandler<'a> { + /// Creates a new `LocalMetaHandler` for the given package. pub fn new(package_id: &'a PackageId, package_install_dir: &'a Path) -> Self { Self { package_id, @@ -76,7 +101,7 @@ impl<'a> LocalMetaHandler<'a> { let target_bounds = package_version_meta.get_best_target(&Target::current())?; let target_meta = package_version_meta.get_target(&target_bounds)?; - let local_metadata = self.create_local_metadata(&package_meta, &package_version_meta, target_meta)?; + let local_metadata = self.create_local_metadata(&package_meta, &package_version_meta, &target_bounds)?; let local_meta_str = toml::ser::to_string(&local_metadata)?; let mut updated = false; @@ -103,9 +128,12 @@ impl<'a> LocalMetaHandler<'a> { } // Download external test files - let external_test_files = package_version_meta.external_test_files.iter().chain(target_meta.external_test_files.iter()); + let external_test_files = package_version_meta.get_external_test_files(&target_bounds)?; for external_file in external_test_files { - let destination = metadata_dir.join(external_file); + // Flatten external file directory names + let normalized_file_name = external_file.replace("/", "-"); + + let destination = metadata_dir.join(normalized_file_name); let new_file = self.request_file(provider, external_file, true)?; if self.write_file_if_changed(&before_files, &mut after_files, destination, new_file)? { updated = true; @@ -146,10 +174,36 @@ impl<'a> LocalMetaHandler<'a> { &self, package_meta: &PackageMeta, package_version_meta: &PackageVersionMeta, - target_meta: &PackageTarget, + target_bounds: &TargetBounds, ) -> Result { + let target_meta = package_version_meta.get_target(target_bounds)?; + + let required_packit_version = max(&package_meta.required_packit_version, &package_version_meta.required_packit_version); + let dependencies = package_version_meta.dependencies.iter().chain(target_meta.dependencies.iter()).cloned().collect(); + + // Normalize external test files to flatten directories into the name + let external_test_files = package_version_meta + .get_external_test_files(target_bounds)? + .iter() + .map(ToString::to_string) + .map(|x| x.replace("/", "-")) + .collect(); + + let script_args = + package_version_meta.get_script_args(target_bounds)?.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(); + + let deprecation = package_meta.deprecation.as_ref().or(package_version_meta.deprecation.as_ref()).cloned(); + Ok(LocalMetadata { - dependencies: package_version_meta.dependencies.iter().chain(target_meta.dependencies.iter()).cloned().collect(), + required_packit_version: required_packit_version.clone(), + dependencies, + test_requirements: target_meta.test_requirements.clone(), + external_test_files, + script_args, + revisions: package_version_meta.revisions.clone(), + deprecation, + skip_symlinking: target_meta.skip_symlinking.unwrap_or(package_version_meta.skip_symlinking), + conflicts_with: package_meta.conflicts_with.clone(), }) } diff --git a/src/repositories/types/common.rs b/src/repositories/types/common.rs index 4797733a..2370b36f 100644 --- a/src/repositories/types/common.rs +++ b/src/repositories/types/common.rs @@ -71,7 +71,7 @@ pub struct FileSize(pub u32); pub struct Date(chrono::NaiveDate); /// Represents information about a package deprecation and disabling. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct DeprecationInfo { pub deprecated_from: Date, From 67c11996e2dd3ba04438c8cfdbf37c7a48a83c25 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 17:43:39 +0200 Subject: [PATCH 08/20] Implement local metadata reading --- src/cli/commands/link.rs | 51 +++----------------- src/cli/commands/util/package.rs | 1 + src/installer/installer.rs | 80 ++++++++++--------------------- src/integrity/error.rs | 5 +- src/integrity/verifier/package.rs | 56 +++++++--------------- src/register/metadata/handler.rs | 14 +++++- 6 files changed, 67 insertions(+), 140 deletions(-) diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index c98738a4..a7c241cb 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -8,18 +8,16 @@ use crate::{ cli::{ commands::HandleCommand, display::{ - logging::{error, warning}, + logging::warning, not_found, standard_print, styled::{MapStyled, Styled}, }, }, - config::{Config, Repository}, + config::Config, installer::{Symlinker, types::PackageName}, - platforms::Target, register::{ installed_package::InstalledPackage, installed_package_version::InstalledPackageVersion, package_register::PackageRegister, }, - repositories::provider, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -105,48 +103,11 @@ impl LinkArgs { return false; } - let repository = Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ); + let local_meta_handler = package_version.get_local_metadata(); + let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Unable to read local metadata", 1); - let Some(provider) = provider::create_metadata_provider(&repository) else { - error!(msg: "Cannot create provider for repository"); - return false; - }; - - let package_version_meta = match provider.read_package_version(&self.package_name, &package.active_version) { - Ok(package_version_meta) => package_version_meta, - Err(e) => { - error!(e, "Unable to read package metadata for package"); - return false; - }, - }; - - // Skip if the package version metadata defines skip_symlinking - if package_version_meta.skip_symlinking { - warning!("The package metadata defines we should not symlink this package"); - return false; - } - - let target_bounds = match package_version_meta.get_best_target(&Target::current()) { - Ok(target_bounds) => target_bounds, - Err(e) => { - error!(e, "The metadata does not contain the current target"); - return false; - }, - }; - - let target = match package_version_meta.get_target(&target_bounds) { - Ok(target) => target, - Err(e) => { - error!(e, "Cannot get current target from package metadata"); - return false; - }, - }; - - // Skip if the package version target metadata defines skip_symlinking - if let Some(true) = target.skip_symlinking { + // Skip if the local metadata defines skip_symlinking + if local_metadata.skip_symlinking { warning!("The package metadata defines we should not symlink this package"); return false; } diff --git a/src/cli/commands/util/package.rs b/src/cli/commands/util/package.rs index 4e42cd97..71402b85 100644 --- a/src/cli/commands/util/package.rs +++ b/src/cli/commands/util/package.rs @@ -86,6 +86,7 @@ impl PackageArgs { None => not_found::register_package_version(package_id, register), }; + // TODO: should we also store prebuilds.toml, or require the repository to be available? // Create metadata provider let repository = Repository::new( &package_version.metadata_repository_url, diff --git a/src/installer/installer.rs b/src/installer/installer.rs index d29f44e6..b7169335 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -13,22 +13,25 @@ use crate::{ standard_print, styled::{MapStyled, Styled}, }, - config::{Config, Repository}, + config::Config, installer::{ InstallLabel, error::{InstallerError, Result}, install_tree::{InstallMeta, InstallTree, InstallTreeBuilder, InstallType}, options::InstallerOptions, - scripts::{self, ScriptData, ScriptError}, + scripts::{self, SCRIPT_EXTENSION, ScriptData, ScriptError}, symlinker::Symlinker, types::{OptionalPackageId, PackageId, PackageName, Version}, unpack::unpack, }, platforms::{DEFAULT_PREFIX, Target, permissions, symlink}, - register::{installed_package_version::InstalledPackageVersion, metadata::LocalMetaHandler, package_register::PackageRegister}, + register::{ + installed_package_version::InstalledPackageVersion, + metadata::{LocalMetaHandler, error::LocalMetadataError}, + package_register::PackageRegister, + }, repositories::{ manager::RepositoryManager, - provider, types::{Checksum, PackageTarget}, }, utils::{io, ioerror::IOResultExt, reading::ReadExt}, @@ -644,12 +647,9 @@ impl<'a> Installer<'a> { }, }; - // Load metadata repository - let repository = match installed_package.get_package_version(&package_id.version) { - Some(package_version) => Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ), + // Get installed package version + let installed_package_version = match installed_package.get_package_version(&package_id.version) { + Some(installed_package_version) => installed_package_version, None => { return Err(InstallerError::UnreachableError { msg: "Package version cannot be found eventhough it was found before".to_string(), @@ -658,7 +658,7 @@ impl<'a> Installer<'a> { }; // Run uninstall script - self.run_uninstall_script(&repository, &package_id, &directory)?; + self.run_uninstall_script(installed_package_version)?; // Remove the dependency symlinks if they exist let dependency_directory_path = self.config.prefix_directory.join("dependencies").join(package_id.to_string()); @@ -740,12 +740,6 @@ impl<'a> Installer<'a> { // Run uninstall scripts for all versions for package_version in &installed_versions { - // Create repository - let repository = Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ); - // Remove the dependency symlinks let dependency_directory_path = self.config.prefix_directory.join("dependencies").join(package_version.package_id.to_string()); if fs::exists(&dependency_directory_path).err_with_path("check existence of", &dependency_directory_path)? { @@ -753,7 +747,7 @@ impl<'a> Installer<'a> { } // Run uninstall script - self.run_uninstall_script(&repository, &package_version.package_id, &directory)?; + self.run_uninstall_script(package_version)?; } if let Some(directory) = directory.to_str() { @@ -772,52 +766,28 @@ impl<'a> Installer<'a> { /// Downloads and runs the uninstall script of a given package. /// Could return an `InstallerError`. - fn run_uninstall_script(&self, repository: &Repository, package_id: &PackageId, install_directory: &Path) -> Result<()> { - // Create metadata repository provider for source repository - let provider = match provider::create_metadata_provider(repository) { - Some(provider) => provider, - None => { - warning!("Unable to create repository provider, skipping uninstall script execution. This may cause stray files"); - return Ok(()); - }, - }; + fn run_uninstall_script(&self, installed_package_version: &InstalledPackageVersion) -> Result<()> { + let package_id = &installed_package_version.package_id; - // Load package version from metadata repository - let package_version = match provider.read_package_version(&package_id.name, &package_id.version) { - Ok(package_version) => package_version, - Err(e) => { - warning!( - "Unable to read package version from metadata repository, skipping uninstall script execution. This may cause stray files" - ); - warning!("{e}"); + let local_meta_handler = installed_package_version.get_local_metadata(); + let local_metadata = local_meta_handler.read_metadata()?; + + // Copy uninstall script to tempfile if it exists + let script_text = match local_meta_handler.read_file(&format!("uninstall.{SCRIPT_EXTENSION}")) { + Ok(script_text) => script_text, + Err(LocalMetadataError::LocalMetadataFileNotFound { .. }) => { + debug!("Skipping uninstall script execution since metadata does not define it"); return Ok(()); }, - }; - - let target_bounds = package_version.get_best_target(&Target::current())?; - - // Check if uninstall script should be used - let target_meta = package_version.get_target(&target_bounds)?; - let use_script = target_meta.use_uninstall.unwrap_or(package_version.use_uninstall.unwrap_or(false)); - if !use_script { - debug!("Skipping uninstall script execution since metadata does not define it"); - return Ok(()); - } - - // Get script path from package version metadata - let script_path = package_version.get_uninstall_script_path(&target_bounds)?; - - // Download uninstall script if it exists - let Some(script_text) = provider.read_file(&package_id.name, &script_path)? else { - return Err(ScriptError::ScriptNotFound(script_path).into()); + Err(e) => return Err(e.into()), }; let script_file = scripts::write_script_to_tempfile(&script_text)?; // Run script - let script_args = package_version.get_script_args(&target_bounds)?; + let script_args = local_metadata.script_args.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); let script_data = ScriptData::new( &script_file, - &install_directory, + &installed_package_version.install_path, package_id, self.config, &script_args, diff --git a/src/integrity/error.rs b/src/integrity/error.rs index b184acb9..23d7205c 100644 --- a/src/integrity/error.rs +++ b/src/integrity/error.rs @@ -11,7 +11,7 @@ use crate::{ }, packager::PackagerError, platforms::{permissions::error::PermissionError, symlink::SymlinkError}, - register::error::RegisterError, + register::{error::RegisterError, metadata::error::LocalMetadataError}, repositories::error::RepositoryError, utils::ioerror, }; @@ -64,6 +64,9 @@ pub enum VerifierError { #[error("Cannot perform check, because of an error when executing a script")] ScriptError(#[from] ScriptError), + #[error("Error while using local metadata")] + LocalMetadataError(#[from] LocalMetadataError), + #[error("Cannot perform check or fix, because of an error while interacting with the filesystem")] IOError(#[from] ioerror::IOError), } diff --git a/src/integrity/verifier/package.rs b/src/integrity/verifier/package.rs index fa14ee86..7e2d8d98 100644 --- a/src/integrity/verifier/package.rs +++ b/src/integrity/verifier/package.rs @@ -9,13 +9,15 @@ use crate::{ config::{Config, Repository}, installer::{ self, - scripts::{self, ScriptData, ScriptError}, + scripts::{self, SCRIPT_EXTENSION, ScriptData, ScriptError}, types::{Dependency, PackageId, PackageName}, }, integrity::{Issue, error::Result, utils::get_storage_packages}, packager, platforms::Target, - register::{installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, + register::{ + installed_package_version::InstalledPackageVersion, metadata::error::LocalMetadataError, package_register::PackageRegister, + }, repositories::{ provider::{self, create_metadata_provider}, types::{Checksum, PackageVersionMeta, PrebuildsList}, @@ -69,6 +71,7 @@ fn check_package_alterations(package_id: &PackageId, register: &PackageRegister, disable_prebuilds: false, }; + // TODO: prebuild list and prebuild provider are needed here // Create providers let Some(provider) = provider::create_metadata_provider(&repository) else { warning!("Cannot create metadata provider for {}, skipping check", package_id.style()); @@ -627,30 +630,18 @@ fn check_package_test(package_id: &PackageId, register: &PackageRegister, config let package_version = register.get_package_version(package_id).expect("Expected package to exist"); - let repository = Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ); + let local_meta_handler = package_version.get_local_metadata(); + let local_metadata = local_meta_handler.read_metadata()?; - let provider = match provider::create_metadata_provider(&repository) { - Some(provider) => provider, - None => { - warning!("Cannot create metadata provider for '{package_id}', skipping check"); - return Ok(false); - }, + // Copy test script to tempfile if it exists + let script_text = match local_meta_handler.read_file(&format!("test.{SCRIPT_EXTENSION}")) { + Ok(script_text) => script_text, + Err(LocalMetadataError::LocalMetadataFileNotFound { .. }) => return Ok(false), + Err(e) => return Err(e.into()), }; - - // Get the target - let version_meta = provider.read_package_version(&package_id.name, &package_id.version)?; - let target_bounds = version_meta.get_best_target(&Target::current())?; - let target = version_meta.get_target(&target_bounds)?; - - // Get the test script data - let script_path = version_meta.get_test_script_path(&target_bounds)?; - let script_args = version_meta.get_script_args(&target_bounds)?; - let script_text = provider.read_file(&package_id.name, &script_path)?; - let Some(script_text) = script_text else { return Ok(false) }; let downloaded_script = scripts::write_script_to_tempfile(&script_text)?; + + let script_args = local_metadata.script_args.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); let script_data = ScriptData::new( &downloaded_script, &package_version.install_path, @@ -661,25 +652,14 @@ fn check_package_test(package_id: &PackageId, register: &PackageRegister, config ); // Get the external test files - let external_files = version_meta.external_test_files.iter().chain(&target.external_test_files); let mut read_files = Vec::new(); - for file in external_files { - let file_content = provider.read_file_bytes(&package_id.name, file)?; - - match file_content { - Some(content) => read_files.push((file, content)), - None => { - warning!( - "Skipping {} test, because the required files could not be downloaded", - package_id.style() - ); - return Ok(false); - }, - } + for file in &local_metadata.external_test_files { + let file_content = local_meta_handler.read_file_bytes(file)?; + read_files.push((file, file_content)); } // Run the test script and only return true if the test itself failed - match scripts::run_test_script(&script_data, &read_files, &target.test_requirements) { + match scripts::run_test_script(&script_data, &read_files, &local_metadata.test_requirements) { Ok(_) => Ok(false), Err(ScriptError::RequirementNotSatisfied(requirement)) => { warning!( diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 6f59f83f..953cdca4 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -79,9 +79,21 @@ impl<'a> LocalMetaHandler<'a> { Ok(toml::de::from_str(&content)?) } + /// Reads the specified local metadata file from the storage of the given package. + /// Returns the file as string. + pub fn read_file(&self, file: &str) -> Result { + let path = self.package_install_dir.join(DIRECTORY_NAME).join(file); + if !path.exists() { + return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); + } + + let content = fs::read_to_string(&path).err_with_path("read", &path)?; + Ok(content) + } + /// Reads the specified local metadata file from the storage of the given package. /// Returns the file as bytes. - pub fn read_file(&self, file: &str) -> Result { + pub fn read_file_bytes(&self, file: &str) -> Result { let path = self.package_install_dir.join(DIRECTORY_NAME).join(file); if !path.exists() { return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); From 793ec9f3346dbb6cbafe726022ea1d541b2546d1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 20:27:32 +0200 Subject: [PATCH 09/20] Add last_metadata_refresh and last_metadata_change fields to register --- src/cli/commands/info.rs | 2 ++ src/cli/commands/init.rs | 37 +++++++++++++++++++++-- src/cli/commands/update.rs | 18 ++++++++--- src/installer/installer.rs | 13 +++++++- src/register/installed_package_version.rs | 18 +++++++++++ src/register/metadata/handler.rs | 3 -- src/register/package_register.rs | 5 +++ 7 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/cli/commands/info.rs b/src/cli/commands/info.rs index edb608f7..6bc8980a 100644 --- a/src/cli/commands/info.rs +++ b/src/cli/commands/info.rs @@ -216,8 +216,10 @@ impl InfoArgs { pair_aligner.add("Install path", package_version.install_path.display()); pair_aligner.add("Active", package.active_version == package_id.version); pair_aligner.add("Symlinked", package.symlinked); + pair_aligner.add("Last metadata refresh", package_version.last_metadata_refresh); if self.verbose { + pair_aligner.add("Last metadata change", package_version.last_metadata_change); pair_aligner.add("Metadata repository provider", &package_version.metadata_repository_provider); pair_aligner.add("Metadata repository url", &package_version.metadata_repository_url); } diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index 1dfd6769..a1e5150b 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -6,18 +6,19 @@ use std::{ str::FromStr, }; +use chrono::DateTime; use clap::Args; use crate::{ cli::{commands::HandleCommand, display::logging::error}, - config::{Config, EditableConfig}, + config::{Config, EditableConfig, Repository}, installer::{ Symlinker, types::{PackageId, PackageName, Version}, }, platforms::{DEFAULT_CONFIG_DIR, DEFAULT_PREFIX, permissions}, register::{installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, - repositories::types::Licenses, + repositories::{provider, types::Licenses}, utils::{ constants::{DEFAULT_METADATA_REPOSITORY_PROVIDER, DEFAULT_METADATA_REPOSITORY_URL}, packit_version::packit_version, @@ -117,6 +118,8 @@ impl HandleCommand for InitArgs { dependents: HashSet::new(), install_path: packit_package_path, revisions: Vec::new(), + last_metadata_refresh: DateTime::default(), // Initialize to UNIX epoch + last_metadata_change: DateTime::default(), // Initialize to UNIX epoch }; let active = false; let symlinked = false; @@ -150,5 +153,35 @@ impl HandleCommand for InitArgs { "Packit cannot be initialized: error while setting permissions of files in the prefix", 1, ); + + // Get the installed package from the register + let Some(installed_package_version) = register.get_package_version_mut(&package_id) else { + error!(msg: "Packit cannot be initialized: newly created register does not contain the Packit version"); + exit(1); + }; + + // Create the repository provider to fetch Packit metadata from + let repository = Repository::new( + &installed_package_version.metadata_repository_url, + &installed_package_version.metadata_repository_provider, + ); + let Some(provider) = provider::create_metadata_provider(&repository) else { + error!(msg: "Packit cannot be initialized: cannot fetch Packit metadata from repository"); + exit(1); + }; + + // Fetch Packit metadata from the default repository + let updated = installed_package_version + .get_local_metadata() + .refresh(&provider) + .unwrap_or_exit_msg("Packit cannot be initialized: error while retrieving Packit metadata", 1); + + // Update last refresh in the package version + installed_package_version.update_metadata_refresh(updated); + + // Save register + register + .save_to(&PackageRegister::get_path(&prefix_directory)) + .unwrap_or_exit_msg("Packit cannot be initialized: error while saving register", 1); } } diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index abfb1ef4..db934980 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -61,7 +61,7 @@ impl HandleCommand for UpdateArgs { // If refresh only is specified, only refresh the specified packages if self.refresh_only { - self.refresh_metadata(®ister, &config); + self.refresh_metadata(&mut register, &config); return; } @@ -140,7 +140,7 @@ impl HandleCommand for UpdateArgs { } // Refresh metadata of all given packages - self.refresh_metadata(®ister, &config); + self.refresh_metadata(&mut register, &config); } } @@ -177,7 +177,7 @@ impl UpdateArgs { filtered_updatables.into_iter().map(OptionalPackageId::from).collect() } - fn refresh_metadata(&self, register: &PackageRegister, config: &Config) { + fn refresh_metadata(&self, register: &mut PackageRegister, config: &Config) { // If `--all` is specified use all installed packages let packages = match self.all { // Only filter exclude packages when all is specified @@ -190,23 +190,31 @@ impl UpdateArgs { }; for package_id in packages { - let Some(package_version) = register.get_package_version(package_id) else { + let Some(package_version) = register.get_package_version_mut(package_id) else { error!(msg: "Expected package version {} to exist, skipping refresh", package_id.style()); continue; }; + // Create repository provider for package let repository = Repository::new( &package_version.metadata_repository_url, &package_version.metadata_repository_provider, ); - let Some(provider) = provider::create_metadata_provider(&repository) else { error!(msg: "Cannot create provider for repository"); continue; }; + // Refresh metadata let local_meta = package_version.get_local_metadata(); let updated_metadata = local_meta.refresh(&provider).unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); + package_version.update_metadata_refresh(updated_metadata); + + // Save register to store updated timestamps + register + .save_to(&PackageRegister::get_path(&config.prefix_directory)) + .unwrap_or_exit_msg("Error while saving register", 1); + println!("{package_id}: {updated_metadata}"); } } diff --git a/src/installer/installer.rs b/src/installer/installer.rs index b7169335..315cc012 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -243,9 +243,20 @@ impl<'a> Installer<'a> { ); self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; + let installed_package_version = match self.register.get_package_version_mut(&package_id) { + Some(installed_package_version) => installed_package_version, + None => { + return Err(InstallerError::UnreachableError { + msg: "Package version cannot be found eventhough it was inserted right before".to_string(), + }); + }, + }; + // Refresh the local metadata for the new package let local_metadata = LocalMetaHandler::new(&package_id, &install_directory); - local_metadata.refresh(self.repository_manager.get_metadata_provider(&install_meta.repository_id)?)?; + let updated_metadata = local_metadata.refresh(self.repository_manager.get_metadata_provider(&install_meta.repository_id)?)?; + installed_package_version.update_metadata_refresh(updated_metadata); + self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; self.execute_postinstall(&package_id, install_meta, &install_directory, &script_args)?; diff --git a/src/register/installed_package_version.rs b/src/register/installed_package_version.rs index 747e196b..0b785439 100644 --- a/src/register/installed_package_version.rs +++ b/src/register/installed_package_version.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use std::{collections::HashSet, path::PathBuf}; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::{config::Repository, installer::types::PackageId, register::metadata::LocalMetaHandler, repositories::types::Licenses}; @@ -37,6 +38,13 @@ pub struct InstalledPackageVersion { #[serde(default)] pub revisions: Vec, + + // The default on the `last_metadata_refresh` and `last_metadata_change` is required to ensure backwards compatibility + #[serde(default)] + pub last_metadata_refresh: DateTime, + + #[serde(default)] + pub last_metadata_change: DateTime, } fn is_repository_provider_default(value: &String) -> bool { @@ -48,4 +56,14 @@ impl InstalledPackageVersion { pub fn get_local_metadata(&self) -> LocalMetaHandler<'_> { LocalMetaHandler::new(&self.package_id, &self.install_path) } + + // Updates the `last_metadata_refresh` and the `last_metadata_change` based on the `updated` paramter. + pub fn update_metadata_refresh(&mut self, updated_metadata: bool) { + let now = Utc::now(); + + self.last_metadata_refresh = now; + if updated_metadata { + self.last_metadata_change = now; + } + } } diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 953cdca4..5dada4f7 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -43,8 +43,6 @@ pub struct LocalMetadata { #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub script_args: HashMap, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub revisions: Vec, pub deprecation: Option, pub skip_symlinking: bool, @@ -212,7 +210,6 @@ impl<'a> LocalMetaHandler<'a> { test_requirements: target_meta.test_requirements.clone(), external_test_files, script_args, - revisions: package_version_meta.revisions.clone(), deprecation, skip_symlinking: target_meta.skip_symlinking.unwrap_or(package_version_meta.skip_symlinking), conflicts_with: package_meta.conflicts_with.clone(), diff --git a/src/register/package_register.rs b/src/register/package_register.rs index cf218059..680ea5bb 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -5,6 +5,7 @@ use std::{ path::{Path, PathBuf}, }; +use chrono::DateTime; use serde::{Deserialize, Serialize}; use crate::{ @@ -126,6 +127,8 @@ impl PackageRegister { dependents: HashSet::new(), install_path: install_path.into(), revisions: package_version.revisions.clone(), + last_metadata_refresh: DateTime::default(), // Initialize to UNIX epoch + last_metadata_change: DateTime::default(), // Initialize to UNIX epoch }; self.add_package_raw( @@ -388,6 +391,8 @@ pub mod tests { dependents, install_path: "-".into(), revisions: Vec::new(), + last_metadata_refresh: DateTime::default(), + last_metadata_change: DateTime::default(), } } From de868accc21b606e8a238e3dbabdc60c995cc4d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 20:33:53 +0200 Subject: [PATCH 10/20] Move license field from register to local metadata --- src/cli/commands/info.rs | 6 +++++- src/cli/commands/init.rs | 3 +-- src/register/installed_package_version.rs | 5 +---- src/register/metadata/handler.rs | 6 +++++- src/register/package_register.rs | 2 -- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/cli/commands/info.rs b/src/cli/commands/info.rs index 6bc8980a..60020dd3 100644 --- a/src/cli/commands/info.rs +++ b/src/cli/commands/info.rs @@ -207,12 +207,16 @@ impl InfoArgs { None => not_found::register_package_version(package_id, register), }; + // Retrieve local metadata of package + let local_meta_handler = package_version.get_local_metadata(); + let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); + println!("{}", package_id.style()); println!("{}", package.description.italic().cyan()); let mut pair_aligner = PairAligner::new(); pair_aligner.add("Homepage", package.homepage.display()); - pair_aligner.add("License", package_version.license.style()); + pair_aligner.add("License", local_metadata.license.style()); pair_aligner.add("Install path", package_version.install_path.display()); pair_aligner.add("Active", package.active_version == package_id.version); pair_aligner.add("Symlinked", package.symlinked); diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index a1e5150b..afffc18f 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -18,7 +18,7 @@ use crate::{ }, platforms::{DEFAULT_CONFIG_DIR, DEFAULT_PREFIX, permissions}, register::{installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, - repositories::{provider, types::Licenses}, + repositories::provider, utils::{ constants::{DEFAULT_METADATA_REPOSITORY_PROVIDER, DEFAULT_METADATA_REPOSITORY_URL}, packit_version::packit_version, @@ -109,7 +109,6 @@ impl HandleCommand for InitArgs { let installed_package_version = InstalledPackageVersion { package_id: package_id.clone(), - license: Licenses::Single("GPL-3.0-only".into()), metadata_repository_provider: DEFAULT_METADATA_REPOSITORY_PROVIDER.into(), metadata_repository_url: DEFAULT_METADATA_REPOSITORY_URL.into(), prebuilds_repository_url: None, diff --git a/src/register/installed_package_version.rs b/src/register/installed_package_version.rs index 0b785439..e6efbfde 100644 --- a/src/register/installed_package_version.rs +++ b/src/register/installed_package_version.rs @@ -4,7 +4,7 @@ use std::{collections::HashSet, path::PathBuf}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::{config::Repository, installer::types::PackageId, register::metadata::LocalMetaHandler, repositories::types::Licenses}; +use crate::{config::Repository, installer::types::PackageId, register::metadata::LocalMetaHandler}; /// Represents a specific package version which is installed on the system. #[cfg_attr(test, derive(PartialEq))] @@ -12,9 +12,6 @@ use crate::{config::Repository, installer::types::PackageId, register::metadata: pub struct InstalledPackageVersion { pub package_id: PackageId, - #[serde(skip_serializing_if = "Licenses::is_unknown", default)] - pub license: Licenses, - #[serde(default = "Repository::default_repository_provider")] #[serde(skip_serializing_if = "is_repository_provider_default")] pub metadata_repository_provider: String, diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 5dada4f7..65fb76f7 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -18,7 +18,7 @@ use crate::{ register::metadata::error::{LocalMetadataError, Result}, repositories::{ provider::MetadataProvider, - types::{DeprecationInfo, PackageMeta, PackageVersionMeta, Requirement, TargetBounds}, + types::{DeprecationInfo, Licenses, PackageMeta, PackageVersionMeta, Requirement, TargetBounds}, }, utils::ioerror::IOResultExt, }; @@ -31,6 +31,9 @@ const METADATA_FILENAME: &str = "metadata.toml"; pub struct LocalMetadata { pub required_packit_version: Option, + #[serde(default, skip_serializing_if = "Licenses::is_unknown")] + pub license: Licenses, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub dependencies: Vec, @@ -206,6 +209,7 @@ impl<'a> LocalMetaHandler<'a> { Ok(LocalMetadata { required_packit_version: required_packit_version.clone(), + license: package_version_meta.license.clone(), dependencies, test_requirements: target_meta.test_requirements.clone(), external_test_files, diff --git a/src/register/package_register.rs b/src/register/package_register.rs index 680ea5bb..9e11ab56 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -118,7 +118,6 @@ impl PackageRegister { let installed_package_version = InstalledPackageVersion { package_id: PackageId::new(package.name.clone(), package_version.version.clone()), - license: package_version.license.clone(), metadata_repository_url: source_repository.url.clone(), metadata_repository_provider: source_repository.provider.clone(), prebuilds_repository_url, @@ -382,7 +381,6 @@ pub mod tests { ) -> InstalledPackageVersion { InstalledPackageVersion { package_id, - license: Licenses::Unknown, metadata_repository_provider: "-".to_string(), metadata_repository_url: "-".to_string(), prebuilds_repository_url: None, From 6b87a7e0f553d632d81d2375f1ddec321d8969a3 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Sep 2026 23:46:02 +0200 Subject: [PATCH 11/20] Add prebuild information to local metadata --- src/cli/commands/util/package.rs | 46 +++++++---------------------- src/register/metadata/handler.rs | 30 +++++++++++++++++-- src/register/metadata/mod.rs | 1 + src/repositories/types/prebuilds.rs | 18 ++++++++++- 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/cli/commands/util/package.rs b/src/cli/commands/util/package.rs index 71402b85..96223da7 100644 --- a/src/cli/commands/util/package.rs +++ b/src/cli/commands/util/package.rs @@ -12,12 +12,12 @@ use crate::{ display::{Spinner, logging::error, not_found, styled::Styled}, parameter_checks, }, - config::{Config, Repository}, + config::Config, installer::types::{OptionalPackageId, PackageId}, packager, platforms::Target, register::package_register::PackageRegister, - repositories::{provider, types::PrebuildsList}, + repositories::types::PrebuildsList, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -86,40 +86,14 @@ impl PackageArgs { None => not_found::register_package_version(package_id, register), }; - // TODO: should we also store prebuilds.toml, or require the repository to be available? - // Create metadata provider - let repository = Repository::new( - &package_version.metadata_repository_url, - &package_version.metadata_repository_provider, - ); - let Some(provider) = provider::create_metadata_provider(&repository) else { - error!(msg: "Cannot create provider for {}, skipping packaging", package_id.style()); - return; - }; - - // Request package metadata - let package_meta = match provider.read_package(&package_id.name) { - Ok(package_meta) => package_meta, - Err(e) => { - error!(e, "Cannot read package metadata of {}, skipping packaging", package_id.style()); - return; - }, - }; - - // Request prebuilds list - let prebuilds_list = match provider.read_prebuilds_list(&package_id.name, &package_id.version) { - Ok(Some(prebuilds_list)) => prebuilds_list, - Ok(None) => PrebuildsList::default(package_meta.supported_versions.keys()), - Err(e) => { - error!(e, "Cannot read prebuild list for {}, skipping packaging", package_id.style()); - return; - }, - }; + // Get local metadata to get prebuild information + let local_meta_handler = package_version.get_local_metadata(); + let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); - // Retrieve `prebuild_id` to use - let Some((prebuild_id, prebuild_meta)) = prebuilds_list.get_best_prebuild(&Target::current()) else { - error!(msg: "Cannot find prebuild to create for {}, skipping packaging", package_id.style()); - return; + // Get prebuild information from local metadata, or use default + let (prebuild_id, prebuild_meta) = match local_metadata.prebuild { + Some(prebuild) => (prebuild.id, prebuild.info), + None => PrebuildsList::default_for_target(&Target::current()), }; // Automatically create the destination directory @@ -130,7 +104,7 @@ impl PackageArgs { let spinner = Spinner::new(spinner_message); spinner.show(); let revisions = package_version.revisions.len() as u64; - packager::package(config, package_id, destination, revisions, prebuild_id, prebuild_meta).unwrap_or_exit(1); + packager::package(config, package_id, destination, revisions, &prebuild_id, &prebuild_meta).unwrap_or_exit(1); spinner.finish(); } } diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 65fb76f7..beecc631 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -18,7 +18,7 @@ use crate::{ register::metadata::error::{LocalMetadataError, Result}, repositories::{ provider::MetadataProvider, - types::{DeprecationInfo, Licenses, PackageMeta, PackageVersionMeta, Requirement, TargetBounds}, + types::{DeprecationInfo, Licenses, PackageMeta, PackageVersionMeta, PrebuildMeta, PrebuildsList, Requirement, TargetBounds}, }, utils::ioerror::IOResultExt, }; @@ -51,6 +51,17 @@ pub struct LocalMetadata { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub conflicts_with: Vec, + + pub prebuild: Option, +} + +/// Represents a prebuild in local metadata. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LocalPrebuildMetadata { + pub id: String, + + #[serde(flatten)] + pub info: PrebuildMeta, } /// Handler which handles the reading and refreshing of local metadata. @@ -113,8 +124,9 @@ impl<'a> LocalMetaHandler<'a> { let package_version_meta = provider.read_package_version(&self.package_id.name, &self.package_id.version)?; let target_bounds = package_version_meta.get_best_target(&Target::current())?; let target_meta = package_version_meta.get_target(&target_bounds)?; + let prebuilds_list = provider.read_prebuilds_list(&self.package_id.name, &self.package_id.version)?; - let local_metadata = self.create_local_metadata(&package_meta, &package_version_meta, &target_bounds)?; + let local_metadata = self.create_local_metadata(&package_meta, &package_version_meta, &target_bounds, prebuilds_list)?; let local_meta_str = toml::ser::to_string(&local_metadata)?; let mut updated = false; @@ -163,7 +175,7 @@ impl<'a> LocalMetaHandler<'a> { // Download uninstall script if target_meta.use_uninstall.unwrap_or(package_version_meta.use_uninstall.unwrap_or(false)) { - let uninstall_script_path = package_version_meta.get_test_script_path(&target_bounds)?; + let uninstall_script_path = package_version_meta.get_uninstall_script_path(&target_bounds)?; let uninstall_script_destination = metadata_dir.join(format!("uninstall.{SCRIPT_EXTENSION}")); let new_file = self.request_file(provider, &uninstall_script_path, true)?; if self.write_file_if_changed(&before_files, &mut after_files, uninstall_script_destination, new_file)? { @@ -188,6 +200,7 @@ impl<'a> LocalMetaHandler<'a> { package_meta: &PackageMeta, package_version_meta: &PackageVersionMeta, target_bounds: &TargetBounds, + prebuilds_list: Option, ) -> Result { let target_meta = package_version_meta.get_target(target_bounds)?; @@ -207,6 +220,16 @@ impl<'a> LocalMetaHandler<'a> { let deprecation = package_meta.deprecation.as_ref().or(package_version_meta.deprecation.as_ref()).cloned(); + let mut prebuild = None; + if let Some(prebuilds_list) = prebuilds_list { + if let Some((prebuild_id, prebuild_meta)) = prebuilds_list.get_best_prebuild(&Target::current()) { + prebuild = Some(LocalPrebuildMetadata { + id: prebuild_id.clone(), + info: prebuild_meta.clone(), + }); + } + } + Ok(LocalMetadata { required_packit_version: required_packit_version.clone(), license: package_version_meta.license.clone(), @@ -217,6 +240,7 @@ impl<'a> LocalMetaHandler<'a> { deprecation, skip_symlinking: target_meta.skip_symlinking.unwrap_or(package_version_meta.skip_symlinking), conflicts_with: package_meta.conflicts_with.clone(), + prebuild, }) } diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 4a85abb1..5c6ce890 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -4,3 +4,4 @@ mod handler; pub use handler::LocalMetaHandler; pub use handler::LocalMetadata; +pub use handler::LocalPrebuildMetadata; diff --git a/src/repositories/types/prebuilds.rs b/src/repositories/types/prebuilds.rs index d53ce586..03432b69 100644 --- a/src/repositories/types/prebuilds.rs +++ b/src/repositories/types/prebuilds.rs @@ -28,7 +28,7 @@ pub struct PrebuildsList { } /// Represents the information about a prebuild in the `prebuilds.toml` file. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct PrebuildMeta { targets: Vec, @@ -95,4 +95,20 @@ impl PrebuildsList { Self { prebuilds } } + + /// Creates the default prebuild id and meta pair for the given target. + pub fn default_for_target(target: &Target) -> (String, PrebuildMeta) { + let target_bounds = TargetBounds { + name: TargetName::Architecture(target.architecture.clone()), + addition: None, + version_intervals: VersionIntervals::default(), + }; + + let prebuild_meta = PrebuildMeta { + targets: vec![target_bounds], + exclude_paths: Vec::new(), + }; + + (target.architecture.to_string(), prebuild_meta) + } } From adeed3d855626cc797e55fcea52a59ac22abe64e Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Sep 2026 23:57:47 +0200 Subject: [PATCH 12/20] Move local metadata to /metadata --- src/cli/commands/info.rs | 12 +++++++++--- src/cli/commands/init.rs | 2 +- src/cli/commands/link.rs | 12 +++++++++--- src/cli/commands/update.rs | 2 +- src/cli/commands/util/package.rs | 2 +- src/installer/installer.rs | 4 ++-- src/integrity/verifier/package.rs | 2 +- src/register/installed_package_version.rs | 9 ++++++--- src/register/metadata/handler.rs | 23 ++++++++++++----------- 9 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/cli/commands/info.rs b/src/cli/commands/info.rs index 60020dd3..da0addf7 100644 --- a/src/cli/commands/info.rs +++ b/src/cli/commands/info.rs @@ -100,7 +100,7 @@ impl HandleCommand for InfoArgs { // Show package version specific information if let Some(package_version) = package_version { let package_id = PackageId::new(package.name.clone(), package_version.clone()); - self.display_package_version_info(&package_id, ®ister, installed_package); + self.display_package_version_info(&package_id, ®ister, &config, installed_package); return; } @@ -201,14 +201,20 @@ impl InfoArgs { } /// Displays the package version info, also checking for the verbose flag for some info. - fn display_package_version_info(&self, package_id: &PackageId, register: &PackageRegister, package: &InstalledPackage) { + fn display_package_version_info( + &self, + package_id: &PackageId, + register: &PackageRegister, + config: &Config, + package: &InstalledPackage, + ) { let package_version = match register.get_package_version(package_id) { Some(package) => package, None => not_found::register_package_version(package_id, register), }; // Retrieve local metadata of package - let local_meta_handler = package_version.get_local_metadata(); + let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); println!("{}", package_id.style()); diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index afffc18f..d33f0790 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -171,7 +171,7 @@ impl HandleCommand for InitArgs { // Fetch Packit metadata from the default repository let updated = installed_package_version - .get_local_metadata() + .get_local_metadata(&prefix_directory) .refresh(&provider) .unwrap_or_exit_msg("Packit cannot be initialized: error while retrieving Packit metadata", 1); diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index a7c241cb..d0b1d561 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -65,7 +65,7 @@ impl HandleCommand for LinkArgs { .unwrap_or_exit_msg("Unable to retrieve active version of package", 1); // Check if linking is allowed, exit if force is not enabled - if !self.linking_allowed(®ister, package, package_version) { + if !self.linking_allowed(®ister, &config, package, package_version) { if !self.force { println!("Try '--force' if you are sure you want to link, note that this can result in issues"); exit(1); @@ -94,7 +94,13 @@ impl HandleCommand for LinkArgs { impl LinkArgs { /// Checks if linking is allowed and shows a message when it is not allowed or cannot be checked. /// Returns true if linking is allowed, false otherwise. - fn linking_allowed(&self, register: &PackageRegister, package: &InstalledPackage, package_version: &InstalledPackageVersion) -> bool { + fn linking_allowed( + &self, + register: &PackageRegister, + config: &Config, + package: &InstalledPackage, + package_version: &InstalledPackageVersion, + ) -> bool { let conflicts = register.get_conflicting_packages(&self.package_name, &package.conflicts_with); if !conflicts.is_empty() { warning!("The package has conflicts with other packages, cancelling linking"); @@ -103,7 +109,7 @@ impl LinkArgs { return false; } - let local_meta_handler = package_version.get_local_metadata(); + let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Unable to read local metadata", 1); // Skip if the local metadata defines skip_symlinking diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index db934980..5988f010 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -206,7 +206,7 @@ impl UpdateArgs { }; // Refresh metadata - let local_meta = package_version.get_local_metadata(); + let local_meta = package_version.get_local_metadata(&config.prefix_directory); let updated_metadata = local_meta.refresh(&provider).unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); package_version.update_metadata_refresh(updated_metadata); diff --git a/src/cli/commands/util/package.rs b/src/cli/commands/util/package.rs index 96223da7..49714235 100644 --- a/src/cli/commands/util/package.rs +++ b/src/cli/commands/util/package.rs @@ -87,7 +87,7 @@ impl PackageArgs { }; // Get local metadata to get prebuild information - let local_meta_handler = package_version.get_local_metadata(); + let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); // Get prebuild information from local metadata, or use default diff --git a/src/installer/installer.rs b/src/installer/installer.rs index 315cc012..b31db14e 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -253,7 +253,7 @@ impl<'a> Installer<'a> { }; // Refresh the local metadata for the new package - let local_metadata = LocalMetaHandler::new(&package_id, &install_directory); + let local_metadata = LocalMetaHandler::new(&package_id, &self.config.prefix_directory); let updated_metadata = local_metadata.refresh(self.repository_manager.get_metadata_provider(&install_meta.repository_id)?)?; installed_package_version.update_metadata_refresh(updated_metadata); self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; @@ -780,7 +780,7 @@ impl<'a> Installer<'a> { fn run_uninstall_script(&self, installed_package_version: &InstalledPackageVersion) -> Result<()> { let package_id = &installed_package_version.package_id; - let local_meta_handler = installed_package_version.get_local_metadata(); + let local_meta_handler = installed_package_version.get_local_metadata(&self.config.prefix_directory); let local_metadata = local_meta_handler.read_metadata()?; // Copy uninstall script to tempfile if it exists diff --git a/src/integrity/verifier/package.rs b/src/integrity/verifier/package.rs index 7e2d8d98..3f3204d6 100644 --- a/src/integrity/verifier/package.rs +++ b/src/integrity/verifier/package.rs @@ -630,7 +630,7 @@ fn check_package_test(package_id: &PackageId, register: &PackageRegister, config let package_version = register.get_package_version(package_id).expect("Expected package to exist"); - let local_meta_handler = package_version.get_local_metadata(); + let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); let local_metadata = local_meta_handler.read_metadata()?; // Copy test script to tempfile if it exists diff --git a/src/register/installed_package_version.rs b/src/register/installed_package_version.rs index e6efbfde..d9e2b366 100644 --- a/src/register/installed_package_version.rs +++ b/src/register/installed_package_version.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{collections::HashSet, path::PathBuf}; +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -50,8 +53,8 @@ fn is_repository_provider_default(value: &String) -> bool { impl InstalledPackageVersion { /// Gets the local metadata handler for the installed package version. - pub fn get_local_metadata(&self) -> LocalMetaHandler<'_> { - LocalMetaHandler::new(&self.package_id, &self.install_path) + pub fn get_local_metadata<'a>(&'a self, prefix_dir: &'a Path) -> LocalMetaHandler<'a> { + LocalMetaHandler::new(&self.package_id, prefix_dir) } // Updates the `last_metadata_refresh` and the `last_metadata_change` based on the `updated` paramter. diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index beecc631..a7e5ca5f 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -23,7 +23,6 @@ use crate::{ utils::ioerror::IOResultExt, }; -pub const DIRECTORY_NAME: &str = ".packit"; const METADATA_FILENAME: &str = "metadata.toml"; /// Represents the local metadata. @@ -67,22 +66,24 @@ pub struct LocalPrebuildMetadata { /// Handler which handles the reading and refreshing of local metadata. pub struct LocalMetaHandler<'a> { package_id: &'a PackageId, - package_install_dir: &'a Path, + prefix_dir: &'a Path, } impl<'a> LocalMetaHandler<'a> { /// Creates a new `LocalMetaHandler` for the given package. - pub fn new(package_id: &'a PackageId, package_install_dir: &'a Path) -> Self { - Self { - package_id, - package_install_dir, - } + pub fn new(package_id: &'a PackageId, prefix_dir: &'a Path) -> Self { + Self { package_id, prefix_dir } + } + + /// Gets the base path of the local metadata storage for the current package. + fn get_base_path(&self) -> PathBuf { + self.prefix_dir.join("metadata").join(&self.package_id.name).join(self.package_id.version.to_string()) } /// Reads the local metadata file from the storage of the given package. /// Returns the `LocalMetadata` parsed from the storage. pub fn read_metadata(&self) -> Result { - let path = self.package_install_dir.join(DIRECTORY_NAME).join(METADATA_FILENAME); + let path = self.get_base_path().join(METADATA_FILENAME); if !path.exists() { return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); } @@ -94,7 +95,7 @@ impl<'a> LocalMetaHandler<'a> { /// Reads the specified local metadata file from the storage of the given package. /// Returns the file as string. pub fn read_file(&self, file: &str) -> Result { - let path = self.package_install_dir.join(DIRECTORY_NAME).join(file); + let path = self.get_base_path().join(file); if !path.exists() { return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); } @@ -106,7 +107,7 @@ impl<'a> LocalMetaHandler<'a> { /// Reads the specified local metadata file from the storage of the given package. /// Returns the file as bytes. pub fn read_file_bytes(&self, file: &str) -> Result { - let path = self.package_install_dir.join(DIRECTORY_NAME).join(file); + let path = self.get_base_path().join(file); if !path.exists() { return Err(LocalMetadataError::LocalMetadataFileNotFound { file_path: path }); } @@ -118,7 +119,7 @@ impl<'a> LocalMetaHandler<'a> { /// Refreshes the local metadata of the given package. /// Returns true if the metadata was changed, false otherwise. pub fn refresh(&self, provider: &Box) -> Result { - let metadata_dir = self.package_install_dir.join(DIRECTORY_NAME); + let metadata_dir = self.get_base_path(); let package_meta = provider.read_package(&self.package_id.name)?; let package_version_meta = provider.read_package_version(&self.package_id.name, &self.package_id.version)?; From b9a0014e045ec5d0cad43a3454741704653d1f2a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 6 Sep 2026 11:15:49 +0200 Subject: [PATCH 13/20] Add local metadata removal to uninstall --- src/installer/installer.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/installer/installer.rs b/src/installer/installer.rs index b31db14e..717df230 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -643,8 +643,8 @@ impl<'a> Installer<'a> { } // Remove entire package directory if there is only one version, otherwise only remove the package version directory - let installed_versions = self.register.get_all_package_versions(&package_id.name); - let directory = match installed_versions.len() { + let installed_versions = self.register.get_all_package_versions(&package_id.name).len(); + let directory = match installed_versions { 1 => self.config.prefix_directory.join("packages").join(&package_id.name), _ => self.config.prefix_directory.join("packages").join(&package_id.name).join(package_id.version.to_string()), }; @@ -695,15 +695,21 @@ impl<'a> Installer<'a> { } // Delete the determined directory - if let Some(directory) = directory.to_str() { - debug!("Removing the package directory: {directory}"); - } + debug!("Removing the package directory: {}", directory.display()); fs::remove_dir_all(&directory).err_with_path("remove dirs", &directory)?; // Remove package from the register debug!("Removing {} from the package register", package_id.style()); self.register.remove_package_version(&package_id); + // Remove local metadata + let local_metadata_path = match installed_versions { + 1 => self.config.prefix_directory.join("metadata").join(&package_id.name), + _ => self.config.prefix_directory.join("metadata").join(&package_id.name).join(package_id.version.to_string()), + }; + debug!("Removing the local metadata directory: {}", local_metadata_path.display()); + fs::remove_dir_all(&local_metadata_path).err_with_path("remove dirs", &directory)?; + Ok(vec![package_id]) } @@ -772,6 +778,11 @@ impl<'a> Installer<'a> { debug!("Removing {} from the package register", package_name.style()); self.register.remove_package(package_name); + // Remove local metadata + let local_metadata_path = self.config.prefix_directory.join("metadata").join(package_name); + debug!("Removing the local metadata directory: {}", local_metadata_path.display()); + fs::remove_dir_all(&local_metadata_path).err_with_path("remove dirs", &directory)?; + Ok(uninstalled) } From 9698d306aa690f7af356e65122b847b8cfcf3b8b Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 6 Sep 2026 21:51:46 +0200 Subject: [PATCH 14/20] Move conflict handling from register to local metadata --- src/cli/commands/info.rs | 9 +- src/cli/commands/init.rs | 9 +- src/cli/commands/link.rs | 15 ++- src/installer/installer.rs | 8 +- src/register/installed_package.rs | 10 +- src/register/metadata/handler.rs | 21 +++- src/register/metadata/mod.rs | 3 + src/register/metadata/register_ext.rs | 51 ++++++++ src/register/package_register.rs | 166 +++++++++++--------------- 9 files changed, 168 insertions(+), 124 deletions(-) create mode 100644 src/register/metadata/register_ext.rs diff --git a/src/cli/commands/info.rs b/src/cli/commands/info.rs index da0addf7..1e0fe1d5 100644 --- a/src/cli/commands/info.rs +++ b/src/cli/commands/info.rs @@ -3,6 +3,7 @@ use crate::{ cli::display::{standard_print::DisplayJoined, styled::MapStyled}, installer::types::PackageName, platforms::{DEFAULT_CONFIG_DIR, OsVersion, Target}, + register::metadata::LocalMetaHandler, repositories::manager::RepositoryManager, utils::packit_version::{current_packit_version, packit_version_name}, }; @@ -104,7 +105,7 @@ impl HandleCommand for InfoArgs { return; } - self.display_package_info(&package.name, installed_package); + self.display_package_info(&config, &package.name, installed_package); } } @@ -178,7 +179,7 @@ impl InfoArgs { } /// Displays package info. - fn display_package_info(&self, package_name: &PackageName, package: &InstalledPackage) { + fn display_package_info(&self, config: &Config, package_name: &PackageName, package: &InstalledPackage) { // Sort installed versions for display let mut installed_versions: Vec<_> = package.versions.keys().collect(); installed_versions.sort(); @@ -195,8 +196,10 @@ impl InfoArgs { println!(); if self.verbose { + let conflicts = LocalMetaHandler::read_package_conflicts(package, &config.prefix_directory) + .unwrap_or_exit_msg("Error while reading local metadata", 1); print!("Conflicts with: "); - standard_print::print_list_or_none(package.conflicts_with.iter()); + standard_print::print_list_or_none(conflicts.iter()); } } diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index d33f0790..5591e37e 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -127,14 +127,7 @@ impl HandleCommand for InitArgs { let package_homepage = Some("https://github.com/pack-it/packit".into()); // Add Packit to register - register.add_package_raw( - installed_package_version, - active, - symlinked, - package_description, - package_homepage, - Vec::new(), - ); + register.add_package_raw(installed_package_version, active, symlinked, package_description, package_homepage); // Save register register diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index d0b1d561..a3b33019 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -16,7 +16,10 @@ use crate::{ config::Config, installer::{Symlinker, types::PackageName}, register::{ - installed_package::InstalledPackage, installed_package_version::InstalledPackageVersion, package_register::PackageRegister, + installed_package::InstalledPackage, + installed_package_version::InstalledPackageVersion, + metadata::{LocalMetaHandler, PackageRegisterExt}, + package_register::PackageRegister, }, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -101,7 +104,15 @@ impl LinkArgs { package: &InstalledPackage, package_version: &InstalledPackageVersion, ) -> bool { - let conflicts = register.get_conflicting_packages(&self.package_name, &package.conflicts_with); + // Read and get conflicts + let package_conflicts: Vec<_> = LocalMetaHandler::read_package_conflicts(package, &config.prefix_directory) + .unwrap_or_exit_msg("Error while reading local metadata", 1) + .into_iter() + .collect(); + let conflicts = register + .get_conflicting_packages(&self.package_name, &package_conflicts, &config.prefix_directory) + .unwrap_or_exit_msg("Error while reading local metadata", 1); + if !conflicts.is_empty() { warning!("The package has conflicts with other packages, cancelling linking"); println!("Conflicting packages:"); diff --git a/src/installer/installer.rs b/src/installer/installer.rs index 717df230..4e5c214c 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -27,7 +27,7 @@ use crate::{ platforms::{DEFAULT_PREFIX, Target, permissions, symlink}, register::{ installed_package_version::InstalledPackageVersion, - metadata::{LocalMetaHandler, error::LocalMetadataError}, + metadata::{LocalMetaHandler, PackageRegisterExt, error::LocalMetadataError}, package_register::PackageRegister, }, repositories::{ @@ -435,7 +435,11 @@ impl<'a> Installer<'a> { }; // Check if the package has conflicting packages - let conflicts = self.register.get_conflicting_packages(&package_id.name, &install_meta.package_metadata.conflicts_with); + let conflicts = self.register.get_conflicting_packages( + &package_id.name, + &install_meta.package_metadata.conflicts_with, + &self.config.prefix_directory, + )?; if !conflicts.is_empty() { warning!("Skipping symlinking because of conflicting packages:"); standard_print::print_list(conflicts.iter().map_styled()); diff --git a/src/register/installed_package.rs b/src/register/installed_package.rs index b673ea91..ea219117 100644 --- a/src/register/installed_package.rs +++ b/src/register/installed_package.rs @@ -3,10 +3,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use crate::{ - installer::types::{PackageName, Version}, - register::installed_package_version::InstalledPackageVersion, -}; +use crate::{installer::types::Version, register::installed_package_version::InstalledPackageVersion}; /// Represents a package that is installed on the system, holding package specific info /// and a mapping from the installed versions to package versions. @@ -21,9 +18,6 @@ pub struct InstalledPackage { #[serde(skip_serializing_if = "Option::is_none")] pub homepage: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub conflicts_with: Vec, } impl InstalledPackage { @@ -33,7 +27,6 @@ impl InstalledPackage { symlinked: bool, package_description: String, package_homepage: Option, - conflicts_with: Vec, ) -> Self { let version = package_version.package_id.version.clone(); Self { @@ -42,7 +35,6 @@ impl InstalledPackage { active_version: version, description: package_description, homepage: package_homepage, - conflicts_with, } } diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index a7e5ca5f..5f4df284 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -15,7 +15,10 @@ use crate::{ types::{Dependency, PackageId, PackageName, Version}, }, platforms::Target, - register::metadata::error::{LocalMetadataError, Result}, + register::{ + installed_package::InstalledPackage, + metadata::error::{LocalMetadataError, Result}, + }, repositories::{ provider::MetadataProvider, types::{DeprecationInfo, Licenses, PackageMeta, PackageVersionMeta, PrebuildMeta, PrebuildsList, Requirement, TargetBounds}, @@ -75,6 +78,22 @@ impl<'a> LocalMetaHandler<'a> { Self { package_id, prefix_dir } } + /// Gets the conflicts of the given package. + /// Returns a list of conflicts defined in the metadata, which can contain packages that are not installed. + pub fn read_package_conflicts(package: &InstalledPackage, prefix_dir: &Path) -> Result> { + let mut conflicts = HashSet::new(); + + for version in package.versions.values() { + let metadata = version.get_local_metadata(prefix_dir).read_metadata()?; + + for conflict in metadata.conflicts_with { + conflicts.insert(conflict); + } + } + + Ok(conflicts) + } + /// Gets the base path of the local metadata storage for the current package. fn get_base_path(&self) -> PathBuf { self.prefix_dir.join("metadata").join(&self.package_id.name).join(self.package_id.version.to_string()) diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 5c6ce890..545496d6 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod error; mod handler; +mod register_ext; pub use handler::LocalMetaHandler; pub use handler::LocalMetadata; pub use handler::LocalPrebuildMetadata; + +pub use register_ext::PackageRegisterExt; diff --git a/src/register/metadata/register_ext.rs b/src/register/metadata/register_ext.rs new file mode 100644 index 00000000..3d5038c4 --- /dev/null +++ b/src/register/metadata/register_ext.rs @@ -0,0 +1,51 @@ +use std::path::Path; + +use crate::{ + installer::types::PackageName, + register::{ + metadata::{LocalMetaHandler, error::Result}, + package_register::PackageRegister, + }, +}; + +/// Extension trait for local metadata interaction with register. +pub trait PackageRegisterExt { + /// Checks if a package has conflicts with any package that is installed. + /// Returns a list of the names of all conflicting packages. + fn get_conflicting_packages( + &self, + package_name: &PackageName, + package_conflicts: &[PackageName], + prefix_dir: &Path, + ) -> Result>; +} + +impl PackageRegisterExt for PackageRegister { + fn get_conflicting_packages( + &self, + package_name: &PackageName, + package_conflicts: &[PackageName], + prefix_dir: &Path, + ) -> Result> { + let mut conflicting_packages = Vec::new(); + + for (name, package) in self.iterate_packages() { + if !package.symlinked || name == package_name { + continue; + } + + // Check if the package specifies this package as conflict + if package_conflicts.contains(name) { + conflicting_packages.push(name.clone()); + } + + // Check if this package specifies the package as conflict + let conflicts = LocalMetaHandler::read_package_conflicts(package, prefix_dir)?; + if conflicts.contains(package_name) { + conflicting_packages.push(name.clone()); + } + } + + Ok(conflicting_packages) + } +} diff --git a/src/register/package_register.rs b/src/register/package_register.rs index 9e11ab56..47820014 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -136,7 +136,6 @@ impl PackageRegister { symlinked, package.description.clone(), package.homepage.clone(), - package.conflicts_with.clone(), ) } @@ -149,7 +148,6 @@ impl PackageRegister { symlinked: bool, package_description: String, package_homepage: Option, - conflicts_with: Vec, ) { // Add the package as a dependent in its dependencies for dependency in &installed_package_version.dependencies { @@ -168,13 +166,7 @@ impl PackageRegister { None => { self.packages.insert( package_name.clone(), - InstalledPackage::new( - installed_package_version, - symlinked, - package_description, - package_homepage, - conflicts_with, - ), + InstalledPackage::new(installed_package_version, symlinked, package_description, package_homepage), ); }, }; @@ -314,30 +306,6 @@ impl PackageRegister { latest } - /// Checks if a package has conflicts with any package that is installed. - /// Returns a list of the names of all conflicting packages. - pub fn get_conflicting_packages(&self, package_name: &PackageName, package_conflicts: &[PackageName]) -> Vec<&PackageName> { - let mut conflicts = Vec::new(); - - for (name, package) in &self.packages { - if !package.symlinked || name == package_name { - continue; - } - - // Check if the package specifies this package as conflict - if package_conflicts.contains(name) { - conflicts.push(name); - } - - // Check if this package specifies the package as conflict - if package.conflicts_with.contains(package_name) { - conflicts.push(name); - } - } - - conflicts - } - /// Returns an iterator, which iterates over all nested installed package version values. pub fn iterate_all(&self) -> impl Iterator { self.packages.values().flat_map(|p| p.versions.values()) @@ -408,7 +376,6 @@ pub mod tests { active_version, description: "-".to_string(), homepage: None, - conflicts_with: Vec::new(), } } @@ -684,69 +651,70 @@ pub mod tests { assert!(package_ids.contains(&create_package_id("F@6"))); } - #[test] - fn conflicts() { - let mut register = create_register(); - - let conflicting = register.get_conflicting_packages(&create_package_name("F"), &[create_package_name("E")]); - assert_eq!(conflicting, [&create_package_name("E")]); - - let package_id = create_package_id("new_package@2.90"); - let mut package_meta = create_package_meta(&package_id); - package_meta.conflicts_with.push(create_package_name("E")); - let package_version_meta = create_package_version_meta(&package_id); - let dependency_ids = HashSet::new(); - - // Add the package - register.add_package( - &package_meta, - &package_version_meta, - dependency_ids, - &Repository::new("-", "-"), - &PathBuf::from("-"), - true, - false, - false, - ); - - let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); - assert_eq!(conflicting, [&create_package_name("new_package")]); - - let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[create_package_name("E")]); - assert_eq!(conflicting, [&create_package_name("new_package")]); - } - - #[test] - fn conflicts_no_symlink() { - let mut register = create_register(); - - let package_id = create_package_id("new_package@2.90"); - let mut package_meta = create_package_meta(&package_id); - package_meta.conflicts_with.push(create_package_name("E")); - let package_version_meta = create_package_version_meta(&package_id); - let dependency_ids = HashSet::new(); - - // Add the package - register.add_package( - &package_meta, - &package_version_meta, - dependency_ids, - &Repository::new("-", "-"), - &PathBuf::from("-"), - false, - false, - false, - ); - - let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); - assert_eq!(conflicting, &[] as &[&PackageName]); - } - - #[test] - fn no_conflicts() { - let register = create_register(); - - let conflicting: Vec<&PackageName> = register.get_conflicting_packages(&create_package_name("F"), &[]); - assert_eq!(conflicting, &[] as &[&PackageName]); - } + // TODO: move tests + // #[test] + // fn conflicts() { + // let mut register = create_register(); + + // let conflicting = register.get_conflicting_packages(&create_package_name("F"), &[create_package_name("E")]); + // assert_eq!(conflicting, [&create_package_name("E")]); + + // let package_id = create_package_id("new_package@2.90"); + // let mut package_meta = create_package_meta(&package_id); + // package_meta.conflicts_with.push(create_package_name("E")); + // let package_version_meta = create_package_version_meta(&package_id); + // let dependency_ids = HashSet::new(); + + // // Add the package + // register.add_package( + // &package_meta, + // &package_version_meta, + // dependency_ids, + // &Repository::new("-", "-"), + // &PathBuf::from("-"), + // true, + // false, + // false, + // ); + + // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); + // assert_eq!(conflicting, [&create_package_name("new_package")]); + + // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[create_package_name("E")]); + // assert_eq!(conflicting, [&create_package_name("new_package")]); + // } + + // #[test] + // fn conflicts_no_symlink() { + // let mut register = create_register(); + + // let package_id = create_package_id("new_package@2.90"); + // let mut package_meta = create_package_meta(&package_id); + // package_meta.conflicts_with.push(create_package_name("E")); + // let package_version_meta = create_package_version_meta(&package_id); + // let dependency_ids = HashSet::new(); + + // // Add the package + // register.add_package( + // &package_meta, + // &package_version_meta, + // dependency_ids, + // &Repository::new("-", "-"), + // &PathBuf::from("-"), + // false, + // false, + // false, + // ); + + // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); + // assert_eq!(conflicting, &[] as &[&PackageName]); + // } + + // #[test] + // fn no_conflicts() { + // let register = create_register(); + + // let conflicting: Vec<&PackageName> = register.get_conflicting_packages(&create_package_name("F"), &[]); + // assert_eq!(conflicting, &[] as &[&PackageName]); + // } } From 14e38b55ed7f414c1548f99b6843858dbe570882 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 6 Sep 2026 22:08:26 +0200 Subject: [PATCH 15/20] Refactor coupling between register and local metadata --- src/cli/commands/info.rs | 6 +-- src/cli/commands/init.rs | 6 +-- src/cli/commands/link.rs | 30 +++++------- src/cli/commands/update.rs | 4 +- src/cli/commands/util/package.rs | 4 +- src/installer/installer.rs | 14 +++--- src/integrity/verifier/package.rs | 6 ++- src/register/installed_package_version.rs | 12 +---- src/register/metadata/handler.rs | 57 ++++++++++++++++++++--- src/register/metadata/mod.rs | 3 -- src/register/metadata/register_ext.rs | 51 -------------------- src/register/package_register.rs | 2 +- 12 files changed, 86 insertions(+), 109 deletions(-) delete mode 100644 src/register/metadata/register_ext.rs diff --git a/src/cli/commands/info.rs b/src/cli/commands/info.rs index 1e0fe1d5..5ce53a25 100644 --- a/src/cli/commands/info.rs +++ b/src/cli/commands/info.rs @@ -196,8 +196,8 @@ impl InfoArgs { println!(); if self.verbose { - let conflicts = LocalMetaHandler::read_package_conflicts(package, &config.prefix_directory) - .unwrap_or_exit_msg("Error while reading local metadata", 1); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory); + let conflicts = local_meta_handler.read_package_conflicts(package).unwrap_or_exit_msg("Error while reading local metadata", 1); print!("Conflicts with: "); standard_print::print_list_or_none(conflicts.iter()); } @@ -217,7 +217,7 @@ impl InfoArgs { }; // Retrieve local metadata of package - let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); println!("{}", package_id.style()); diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index 5591e37e..1722f4d4 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -17,7 +17,7 @@ use crate::{ types::{PackageId, PackageName, Version}, }, platforms::{DEFAULT_CONFIG_DIR, DEFAULT_PREFIX, permissions}, - register::{installed_package_version::InstalledPackageVersion, package_register::PackageRegister}, + register::{installed_package_version::InstalledPackageVersion, metadata::LocalMetaHandler, package_register::PackageRegister}, repositories::provider, utils::{ constants::{DEFAULT_METADATA_REPOSITORY_PROVIDER, DEFAULT_METADATA_REPOSITORY_URL}, @@ -163,8 +163,8 @@ impl HandleCommand for InitArgs { }; // Fetch Packit metadata from the default repository - let updated = installed_package_version - .get_local_metadata(&prefix_directory) + let updated = LocalMetaHandler::new(&prefix_directory) + .get_package(&package_id) .refresh(&provider) .unwrap_or_exit_msg("Packit cannot be initialized: error while retrieving Packit metadata", 1); diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index a3b33019..60eb9ec1 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -14,13 +14,11 @@ use crate::{ }, }, config::Config, - installer::{Symlinker, types::PackageName}, - register::{ - installed_package::InstalledPackage, - installed_package_version::InstalledPackageVersion, - metadata::{LocalMetaHandler, PackageRegisterExt}, - package_register::PackageRegister, + installer::{ + Symlinker, + types::{PackageId, PackageName}, }, + register::{installed_package::InstalledPackage, metadata::LocalMetaHandler, package_register::PackageRegister}, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -68,7 +66,7 @@ impl HandleCommand for LinkArgs { .unwrap_or_exit_msg("Unable to retrieve active version of package", 1); // Check if linking is allowed, exit if force is not enabled - if !self.linking_allowed(®ister, &config, package, package_version) { + if !self.linking_allowed(®ister, &config, package, &package_version.package_id) { if !self.force { println!("Try '--force' if you are sure you want to link, note that this can result in issues"); exit(1); @@ -97,20 +95,16 @@ impl HandleCommand for LinkArgs { impl LinkArgs { /// Checks if linking is allowed and shows a message when it is not allowed or cannot be checked. /// Returns true if linking is allowed, false otherwise. - fn linking_allowed( - &self, - register: &PackageRegister, - config: &Config, - package: &InstalledPackage, - package_version: &InstalledPackageVersion, - ) -> bool { + fn linking_allowed(&self, register: &PackageRegister, config: &Config, package: &InstalledPackage, package_id: &PackageId) -> bool { // Read and get conflicts - let package_conflicts: Vec<_> = LocalMetaHandler::read_package_conflicts(package, &config.prefix_directory) + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory); + let package_conflicts: Vec<_> = local_meta_handler + .read_package_conflicts(package) .unwrap_or_exit_msg("Error while reading local metadata", 1) .into_iter() .collect(); - let conflicts = register - .get_conflicting_packages(&self.package_name, &package_conflicts, &config.prefix_directory) + let conflicts = local_meta_handler + .get_conflicting_packages(®ister, &self.package_name, &package_conflicts) .unwrap_or_exit_msg("Error while reading local metadata", 1); if !conflicts.is_empty() { @@ -120,7 +114,7 @@ impl LinkArgs { return false; } - let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(&package_id); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Unable to read local metadata", 1); // Skip if the local metadata defines skip_symlinking diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index 5988f010..f5cd1e5c 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -20,7 +20,7 @@ use crate::{ types::{OptionalPackageId, PackageName, Version}, }, platforms::Target, - register::package_register::PackageRegister, + register::{metadata::LocalMetaHandler, package_register::PackageRegister}, repositories::{manager::RepositoryManager, provider}, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -206,7 +206,7 @@ impl UpdateArgs { }; // Refresh metadata - let local_meta = package_version.get_local_metadata(&config.prefix_directory); + let local_meta = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); let updated_metadata = local_meta.refresh(&provider).unwrap_or_exit_msg(&format!("Cannot refresh metadata of {package_id}"), 1); package_version.update_metadata_refresh(updated_metadata); diff --git a/src/cli/commands/util/package.rs b/src/cli/commands/util/package.rs index 49714235..4a5c7a39 100644 --- a/src/cli/commands/util/package.rs +++ b/src/cli/commands/util/package.rs @@ -16,7 +16,7 @@ use crate::{ installer::types::{OptionalPackageId, PackageId}, packager, platforms::Target, - register::package_register::PackageRegister, + register::{metadata::LocalMetaHandler, package_register::PackageRegister}, repositories::types::PrebuildsList, utils::unwrap_or_exit::UnwrapOrExit, }; @@ -87,7 +87,7 @@ impl PackageArgs { }; // Get local metadata to get prebuild information - let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Error while reading local metadata", 1); // Get prebuild information from local metadata, or use default diff --git a/src/installer/installer.rs b/src/installer/installer.rs index 4e5c214c..2775193d 100644 --- a/src/installer/installer.rs +++ b/src/installer/installer.rs @@ -27,7 +27,7 @@ use crate::{ platforms::{DEFAULT_PREFIX, Target, permissions, symlink}, register::{ installed_package_version::InstalledPackageVersion, - metadata::{LocalMetaHandler, PackageRegisterExt, error::LocalMetadataError}, + metadata::{LocalMetaHandler, error::LocalMetadataError}, package_register::PackageRegister, }, repositories::{ @@ -253,7 +253,7 @@ impl<'a> Installer<'a> { }; // Refresh the local metadata for the new package - let local_metadata = LocalMetaHandler::new(&package_id, &self.config.prefix_directory); + let local_metadata = LocalMetaHandler::new(&self.config.prefix_directory).get_package(&package_id); let updated_metadata = local_metadata.refresh(self.repository_manager.get_metadata_provider(&install_meta.repository_id)?)?; installed_package_version.update_metadata_refresh(updated_metadata); self.register.save_to(&PackageRegister::get_path(&self.config.prefix_directory))?; @@ -435,11 +435,9 @@ impl<'a> Installer<'a> { }; // Check if the package has conflicting packages - let conflicts = self.register.get_conflicting_packages( - &package_id.name, - &install_meta.package_metadata.conflicts_with, - &self.config.prefix_directory, - )?; + let local_meta_handler = LocalMetaHandler::new(&self.config.prefix_directory); + let conflicts = + local_meta_handler.get_conflicting_packages(self.register, &package_id.name, &install_meta.package_metadata.conflicts_with)?; if !conflicts.is_empty() { warning!("Skipping symlinking because of conflicting packages:"); standard_print::print_list(conflicts.iter().map_styled()); @@ -795,7 +793,7 @@ impl<'a> Installer<'a> { fn run_uninstall_script(&self, installed_package_version: &InstalledPackageVersion) -> Result<()> { let package_id = &installed_package_version.package_id; - let local_meta_handler = installed_package_version.get_local_metadata(&self.config.prefix_directory); + let local_meta_handler = LocalMetaHandler::new(&self.config.prefix_directory).get_package(package_id); let local_metadata = local_meta_handler.read_metadata()?; // Copy uninstall script to tempfile if it exists diff --git a/src/integrity/verifier/package.rs b/src/integrity/verifier/package.rs index 3f3204d6..e9a500fa 100644 --- a/src/integrity/verifier/package.rs +++ b/src/integrity/verifier/package.rs @@ -16,7 +16,9 @@ use crate::{ packager, platforms::Target, register::{ - installed_package_version::InstalledPackageVersion, metadata::error::LocalMetadataError, package_register::PackageRegister, + installed_package_version::InstalledPackageVersion, + metadata::{LocalMetaHandler, error::LocalMetadataError}, + package_register::PackageRegister, }, repositories::{ provider::{self, create_metadata_provider}, @@ -630,7 +632,7 @@ fn check_package_test(package_id: &PackageId, register: &PackageRegister, config let package_version = register.get_package_version(package_id).expect("Expected package to exist"); - let local_meta_handler = package_version.get_local_metadata(&config.prefix_directory); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); let local_metadata = local_meta_handler.read_metadata()?; // Copy test script to tempfile if it exists diff --git a/src/register/installed_package_version.rs b/src/register/installed_package_version.rs index d9e2b366..aac2fe78 100644 --- a/src/register/installed_package_version.rs +++ b/src/register/installed_package_version.rs @@ -1,13 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{ - collections::HashSet, - path::{Path, PathBuf}, -}; +use std::{collections::HashSet, path::PathBuf}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::{config::Repository, installer::types::PackageId, register::metadata::LocalMetaHandler}; +use crate::{config::Repository, installer::types::PackageId}; /// Represents a specific package version which is installed on the system. #[cfg_attr(test, derive(PartialEq))] @@ -52,11 +49,6 @@ fn is_repository_provider_default(value: &String) -> bool { } impl InstalledPackageVersion { - /// Gets the local metadata handler for the installed package version. - pub fn get_local_metadata<'a>(&'a self, prefix_dir: &'a Path) -> LocalMetaHandler<'a> { - LocalMetaHandler::new(&self.package_id, prefix_dir) - } - // Updates the `last_metadata_refresh` and the `last_metadata_change` based on the `updated` paramter. pub fn update_metadata_refresh(&mut self, updated_metadata: bool) { let now = Utc::now(); diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 5f4df284..15be61eb 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -18,6 +18,7 @@ use crate::{ register::{ installed_package::InstalledPackage, metadata::error::{LocalMetadataError, Result}, + package_register::PackageRegister, }, repositories::{ provider::MetadataProvider, @@ -68,23 +69,35 @@ pub struct LocalPrebuildMetadata { /// Handler which handles the reading and refreshing of local metadata. pub struct LocalMetaHandler<'a> { - package_id: &'a PackageId, prefix_dir: &'a Path, } +pub struct LocalMetaPackageHandler<'a> { + prefix_dir: &'a Path, + package_id: &'a PackageId, +} + impl<'a> LocalMetaHandler<'a> { - /// Creates a new `LocalMetaHandler` for the given package. - pub fn new(package_id: &'a PackageId, prefix_dir: &'a Path) -> Self { - Self { package_id, prefix_dir } + /// Creates a new `LocalMetaHandler`. + pub fn new(prefix_dir: &'a Path) -> Self { + Self { prefix_dir } + } + + /// Gets the local metadata handler for the given package. + pub fn get_package(&self, package_id: &'a PackageId) -> LocalMetaPackageHandler<'a> { + LocalMetaPackageHandler { + prefix_dir: self.prefix_dir, + package_id, + } } /// Gets the conflicts of the given package. /// Returns a list of conflicts defined in the metadata, which can contain packages that are not installed. - pub fn read_package_conflicts(package: &InstalledPackage, prefix_dir: &Path) -> Result> { + pub fn read_package_conflicts(&self, package: &InstalledPackage) -> Result> { let mut conflicts = HashSet::new(); for version in package.versions.values() { - let metadata = version.get_local_metadata(prefix_dir).read_metadata()?; + let metadata = self.get_package(&version.package_id).read_metadata()?; for conflict in metadata.conflicts_with { conflicts.insert(conflict); @@ -94,6 +107,38 @@ impl<'a> LocalMetaHandler<'a> { Ok(conflicts) } + /// Checks if a package has conflicts with any package that is installed. + /// Returns a list of the names of all conflicting packages. + pub fn get_conflicting_packages( + &self, + register: &PackageRegister, + package_name: &PackageName, + package_conflicts: &[PackageName], + ) -> Result> { + let mut conflicting_packages = Vec::new(); + + for (name, package) in register.iterate_packages() { + if !package.symlinked || name == package_name { + continue; + } + + // Check if the package specifies this package as conflict + if package_conflicts.contains(name) { + conflicting_packages.push(name.clone()); + } + + // Check if this package specifies the package as conflict + let conflicts = self.read_package_conflicts(package)?; + if conflicts.contains(package_name) { + conflicting_packages.push(name.clone()); + } + } + + Ok(conflicting_packages) + } +} + +impl<'a> LocalMetaPackageHandler<'a> { /// Gets the base path of the local metadata storage for the current package. fn get_base_path(&self) -> PathBuf { self.prefix_dir.join("metadata").join(&self.package_id.name).join(self.package_id.version.to_string()) diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 545496d6..5c6ce890 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -1,10 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod error; mod handler; -mod register_ext; pub use handler::LocalMetaHandler; pub use handler::LocalMetadata; pub use handler::LocalPrebuildMetadata; - -pub use register_ext::PackageRegisterExt; diff --git a/src/register/metadata/register_ext.rs b/src/register/metadata/register_ext.rs deleted file mode 100644 index 3d5038c4..00000000 --- a/src/register/metadata/register_ext.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::path::Path; - -use crate::{ - installer::types::PackageName, - register::{ - metadata::{LocalMetaHandler, error::Result}, - package_register::PackageRegister, - }, -}; - -/// Extension trait for local metadata interaction with register. -pub trait PackageRegisterExt { - /// Checks if a package has conflicts with any package that is installed. - /// Returns a list of the names of all conflicting packages. - fn get_conflicting_packages( - &self, - package_name: &PackageName, - package_conflicts: &[PackageName], - prefix_dir: &Path, - ) -> Result>; -} - -impl PackageRegisterExt for PackageRegister { - fn get_conflicting_packages( - &self, - package_name: &PackageName, - package_conflicts: &[PackageName], - prefix_dir: &Path, - ) -> Result> { - let mut conflicting_packages = Vec::new(); - - for (name, package) in self.iterate_packages() { - if !package.symlinked || name == package_name { - continue; - } - - // Check if the package specifies this package as conflict - if package_conflicts.contains(name) { - conflicting_packages.push(name.clone()); - } - - // Check if this package specifies the package as conflict - let conflicts = LocalMetaHandler::read_package_conflicts(package, prefix_dir)?; - if conflicts.contains(package_name) { - conflicting_packages.push(name.clone()); - } - } - - Ok(conflicting_packages) - } -} diff --git a/src/register/package_register.rs b/src/register/package_register.rs index 47820014..38fcd53c 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -651,7 +651,7 @@ pub mod tests { assert!(package_ids.contains(&create_package_id("F@6"))); } - // TODO: move tests + // TODO: move tests, problem is that local metadata requires IO // #[test] // fn conflicts() { // let mut register = create_register(); From 3c0ea0a5a5e13bcbc3a016f07ea5e9954c7cd9ac Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 7 Sep 2026 23:42:56 +0200 Subject: [PATCH 16/20] Review changes --- src/cli/commands/init.rs | 2 +- src/cli/commands/link.rs | 7 +--- src/cli/commands/update.rs | 10 ++++- src/integrity/verifier/package.rs | 43 +++++-------------- src/register/metadata/handler.rs | 18 ++++---- src/register/metadata/mod.rs | 4 ++ src/register/package_register.rs | 69 +------------------------------ src/repositories/types/package.rs | 6 +-- 8 files changed, 38 insertions(+), 121 deletions(-) diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index 1722f4d4..7738e2f5 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -152,7 +152,7 @@ impl HandleCommand for InitArgs { exit(1); }; - // Create the repository provider to fetch Packit metadata from + // Create the repository provider to fetch Packit metadata let repository = Repository::new( &installed_package_version.metadata_repository_url, &installed_package_version.metadata_repository_provider, diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index 60eb9ec1..f36f7ff6 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -98,11 +98,8 @@ impl LinkArgs { fn linking_allowed(&self, register: &PackageRegister, config: &Config, package: &InstalledPackage, package_id: &PackageId) -> bool { // Read and get conflicts let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory); - let package_conflicts: Vec<_> = local_meta_handler - .read_package_conflicts(package) - .unwrap_or_exit_msg("Error while reading local metadata", 1) - .into_iter() - .collect(); + let package_conflicts = + local_meta_handler.read_package_conflicts(package).unwrap_or_exit_msg("Error while reading local metadata", 1); let conflicts = local_meta_handler .get_conflicting_packages(®ister, &self.package_name, &package_conflicts) .unwrap_or_exit_msg("Error while reading local metadata", 1); diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index f5cd1e5c..74b28386 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -48,8 +48,12 @@ pub struct UpdateArgs { exclude: Vec, /// Only refresh the local metadata - #[arg(long)] + #[arg(long, default_value = "false", conflicts_with = "skip_refresh")] refresh_only: bool, + + /// Skips refreshing of the local metadata + #[arg(long, default_value = "false", conflicts_with = "refresh_only")] + skip_refresh: bool, } impl HandleCommand for UpdateArgs { @@ -140,7 +144,9 @@ impl HandleCommand for UpdateArgs { } // Refresh metadata of all given packages - self.refresh_metadata(&mut register, &config); + if !self.skip_refresh { + self.refresh_metadata(&mut register, &config); + } } } diff --git a/src/integrity/verifier/package.rs b/src/integrity/verifier/package.rs index e9a500fa..372be797 100644 --- a/src/integrity/verifier/package.rs +++ b/src/integrity/verifier/package.rs @@ -73,46 +73,23 @@ fn check_package_alterations(package_id: &PackageId, register: &PackageRegister, disable_prebuilds: false, }; - // TODO: prebuild list and prebuild provider are needed here - // Create providers - let Some(provider) = provider::create_metadata_provider(&repository) else { - warning!("Cannot create metadata provider for {}, skipping check", package_id.style()); - return Ok(false); - }; + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); + let local_metadata = local_meta_handler.read_metadata()?; + + // Create prebuild provider let Some(prebuild_provider) = provider::create_prebuild_provider(&repository) else { warning!("Cannot create prebuild provider for {}, skipping check", package_id.style()); return Ok(false); }; - // Request package metadata - let package_meta = match provider.read_package(&package_id.name) { - Ok(package_meta) => package_meta, - Err(e) => { - warning!("Cannot read package metadata of {}, skipping check", package_id.style()); - debug!(err: e, "Retrieving package metadata failed"); - return Ok(false); - }, - }; - - // Request prebuilds list - let prebuilds_list = match provider.read_prebuilds_list(&package_id.name, &package_id.version) { - Ok(Some(prebuilds_list)) => prebuilds_list, - Ok(None) => PrebuildsList::default(package_meta.supported_versions.keys()), - Err(e) => { - warning!("Cannot read prebuild list for {}, skipping check", package_id.style()); - debug!(err: e, "Retrieving prebuilds list failed"); - return Ok(false); - }, - }; - - // Retrieve `prebuild_id` to use - let Some((prebuild_id, prebuild_meta)) = prebuilds_list.get_best_prebuild(&Target::current()) else { - warning!("Cannot find prebuild to create for {}, skipping packaging", package_id.style()); - return Ok(false); + // Get prebuild information from local metadata, or use default + let (prebuild_id, prebuild_meta) = match local_metadata.prebuild { + Some(prebuild) => (prebuild.id, prebuild.info), + None => PrebuildsList::default_for_target(&Target::current()), }; let revision = package_version.revisions.len() as u64; - let prebuild_file_meta = match prebuild_provider.get_prebuild_meta(package_id, revision, prebuild_id) { + let prebuild_file_meta = match prebuild_provider.get_prebuild_meta(package_id, revision, &prebuild_id) { Ok(prebuild_file_meta) => prebuild_file_meta, Err(e) => { warning!( @@ -125,7 +102,7 @@ fn check_package_alterations(package_id: &PackageId, register: &PackageRegister, }; let install_directory = config.prefix_directory.join("packages").join(&package_id.name).join(package_id.version.to_string()); - let compressed = packager::compress(&install_directory, prebuild_meta)?; + let compressed = packager::compress(&install_directory, &prebuild_meta)?; let checksum = Checksum::from_bytes(&compressed); Ok(checksum != prebuild_file_meta.checksum) diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 15be61eb..3319bc3c 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -52,8 +52,8 @@ pub struct LocalMetadata { pub deprecation: Option, pub skip_symlinking: bool, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub conflicts_with: Vec, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub conflicts_with: HashSet, pub prebuild: Option, } @@ -113,9 +113,9 @@ impl<'a> LocalMetaHandler<'a> { &self, register: &PackageRegister, package_name: &PackageName, - package_conflicts: &[PackageName], - ) -> Result> { - let mut conflicting_packages = Vec::new(); + package_conflicts: &HashSet, + ) -> Result> { + let mut conflicting_packages = HashSet::new(); for (name, package) in register.iterate_packages() { if !package.symlinked || name == package_name { @@ -124,13 +124,13 @@ impl<'a> LocalMetaHandler<'a> { // Check if the package specifies this package as conflict if package_conflicts.contains(name) { - conflicting_packages.push(name.clone()); + conflicting_packages.insert(name.clone()); } // Check if this package specifies the package as conflict let conflicts = self.read_package_conflicts(package)?; if conflicts.contains(package_name) { - conflicting_packages.push(name.clone()); + conflicting_packages.insert(name.clone()); } } @@ -249,7 +249,7 @@ impl<'a> LocalMetaPackageHandler<'a> { } // Remove files that are not needed anymore - let removed_files: Vec<_> = before_files.iter().filter(|x| !after_files.contains(x)).collect(); + let removed_files = before_files.iter().filter(|x| !after_files.contains(x)); for removed_file in removed_files { fs::remove_file(removed_file).err_with_path("remove", removed_file)?; updated = true; @@ -347,7 +347,7 @@ impl<'a> LocalMetaPackageHandler<'a> { if before_files.contains(&destination) { let old_content = fs::read(&destination).err_with_path("read", &destination)?; - // If the file did not change, skip writing and store it as new file + // If the file did not change, skip writing and store it in `after_files` if new_content == old_content { after_files.push(destination); return Ok(false); diff --git a/src/register/metadata/mod.rs b/src/register/metadata/mod.rs index 5c6ce890..009f03ae 100644 --- a/src/register/metadata/mod.rs +++ b/src/register/metadata/mod.rs @@ -3,5 +3,9 @@ pub mod error; mod handler; pub use handler::LocalMetaHandler; + +#[expect(unused_imports)] pub use handler::LocalMetadata; + +#[expect(unused_imports)] pub use handler::LocalPrebuildMetadata; diff --git a/src/register/package_register.rs b/src/register/package_register.rs index 38fcd53c..13cc9d97 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -453,7 +453,7 @@ pub mod tests { homepage: None, versions: vec![package_id.version.clone()], required_packit_version: None, - conflicts_with: Vec::new(), + conflicts_with: HashSet::new(), supported_versions: HashMap::from([(current_target_bounds, version_intervals)]), deprecation: None, } @@ -650,71 +650,4 @@ pub mod tests { assert!(package_ids.contains(&create_package_id("F@5"))); assert!(package_ids.contains(&create_package_id("F@6"))); } - - // TODO: move tests, problem is that local metadata requires IO - // #[test] - // fn conflicts() { - // let mut register = create_register(); - - // let conflicting = register.get_conflicting_packages(&create_package_name("F"), &[create_package_name("E")]); - // assert_eq!(conflicting, [&create_package_name("E")]); - - // let package_id = create_package_id("new_package@2.90"); - // let mut package_meta = create_package_meta(&package_id); - // package_meta.conflicts_with.push(create_package_name("E")); - // let package_version_meta = create_package_version_meta(&package_id); - // let dependency_ids = HashSet::new(); - - // // Add the package - // register.add_package( - // &package_meta, - // &package_version_meta, - // dependency_ids, - // &Repository::new("-", "-"), - // &PathBuf::from("-"), - // true, - // false, - // false, - // ); - - // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); - // assert_eq!(conflicting, [&create_package_name("new_package")]); - - // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[create_package_name("E")]); - // assert_eq!(conflicting, [&create_package_name("new_package")]); - // } - - // #[test] - // fn conflicts_no_symlink() { - // let mut register = create_register(); - - // let package_id = create_package_id("new_package@2.90"); - // let mut package_meta = create_package_meta(&package_id); - // package_meta.conflicts_with.push(create_package_name("E")); - // let package_version_meta = create_package_version_meta(&package_id); - // let dependency_ids = HashSet::new(); - - // // Add the package - // register.add_package( - // &package_meta, - // &package_version_meta, - // dependency_ids, - // &Repository::new("-", "-"), - // &PathBuf::from("-"), - // false, - // false, - // false, - // ); - - // let conflicting = register.get_conflicting_packages(&create_package_name("E"), &[]); - // assert_eq!(conflicting, &[] as &[&PackageName]); - // } - - // #[test] - // fn no_conflicts() { - // let register = create_register(); - - // let conflicting: Vec<&PackageName> = register.get_conflicting_packages(&create_package_name("F"), &[]); - // assert_eq!(conflicting, &[] as &[&PackageName]); - // } } diff --git a/src/repositories/types/package.rs b/src/repositories/types/package.rs index bb6670e1..32b63c58 100644 --- a/src/repositories/types/package.rs +++ b/src/repositories/types/package.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; @@ -21,8 +21,8 @@ pub struct PackageMeta { pub versions: Vec, pub required_packit_version: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub conflicts_with: Vec, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub conflicts_with: HashSet, pub supported_versions: HashMap, #[serde(skip_serializing_if = "Option::is_none")] From 665c68daccda2d4ddfa38f3cdffea2e310742a04 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Sep 2026 00:17:55 +0200 Subject: [PATCH 17/20] Fix non-deterministic local metadata serialization --- src/cli/commands/update.rs | 6 +++++- src/register/metadata/handler.rs | 6 +++++- src/utils/mod.rs | 1 + src/utils/serialization.rs | 35 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/utils/serialization.rs diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index 74b28386..53661341 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -183,6 +183,7 @@ impl UpdateArgs { filtered_updatables.into_iter().map(OptionalPackageId::from).collect() } + /// Refreshes the metadata for the packages given in the command. fn refresh_metadata(&self, register: &mut PackageRegister, config: &Config) { // If `--all` is specified use all installed packages let packages = match self.all { @@ -221,7 +222,10 @@ impl UpdateArgs { .save_to(&PackageRegister::get_path(&config.prefix_directory)) .unwrap_or_exit_msg("Error while saving register", 1); - println!("{package_id}: {updated_metadata}"); + // Show message + if updated_metadata { + println!("Updated local metadata of {}", package_id.style()); + } } } } diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 3319bc3c..744cdf5a 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -24,7 +24,7 @@ use crate::{ provider::MetadataProvider, types::{DeprecationInfo, Licenses, PackageMeta, PackageVersionMeta, PrebuildMeta, PrebuildsList, Requirement, TargetBounds}, }, - utils::ioerror::IOResultExt, + utils::{ioerror::IOResultExt, serialization}, }; const METADATA_FILENAME: &str = "metadata.toml"; @@ -44,15 +44,18 @@ pub struct LocalMetadata { pub test_requirements: Vec, #[serde(default, skip_serializing_if = "HashSet::is_empty")] + #[serde(serialize_with = "serialization::serialize_set_sorted")] pub external_test_files: HashSet, #[serde(default, skip_serializing_if = "HashMap::is_empty")] + #[serde(serialize_with = "serialization::serialize_map_sorted")] pub script_args: HashMap, pub deprecation: Option, pub skip_symlinking: bool, #[serde(default, skip_serializing_if = "HashSet::is_empty")] + #[serde(serialize_with = "serialization::serialize_set_sorted")] pub conflicts_with: HashSet, pub prebuild: Option, @@ -72,6 +75,7 @@ pub struct LocalMetaHandler<'a> { prefix_dir: &'a Path, } +/// Handler which handles the reading and refreshing of local metadata for a specific package. pub struct LocalMetaPackageHandler<'a> { prefix_dir: &'a Path, package_id: &'a PackageId, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 174a3785..0e143511 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,5 +8,6 @@ pub mod packit_version; pub mod patches; pub mod reading; pub mod requests; +pub mod serialization; pub mod tree; pub mod unwrap_or_exit; diff --git a/src/utils/serialization.rs b/src/utils/serialization.rs new file mode 100644 index 00000000..9b002b64 --- /dev/null +++ b/src/utils/serialization.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-only +use std::collections::{HashMap, HashSet}; + +use serde::{Serialize, ser::SerializeMap}; + +/// Serializes a `HashMap` and ensures a sorted result. +pub fn serialize_map_sorted(map: &HashMap, serializer: S) -> Result +where + S: serde::Serializer, + K: Serialize + Ord, + V: Serialize, +{ + let mut entries: Vec<_> = map.iter().collect(); + entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + + let mut map_serializer = serializer.serialize_map(Some(entries.len()))?; + + for (key, value) in entries { + map_serializer.serialize_entry(key, value)?; + } + + map_serializer.end() +} + +/// Serializes a `HashSet` and ensures a sorted result. +pub fn serialize_set_sorted(set: &HashSet, serializer: S) -> Result +where + S: serde::Serializer, + T: Serialize + Ord, +{ + let mut values: Vec<_> = set.iter().collect(); + values.sort(); + + values.serialize(serializer) +} From 4e794064a7a949f1409b684651738daa369ed7e9 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Sep 2026 00:38:19 +0200 Subject: [PATCH 18/20] Update documentation --- docs/README.md | 19 +++++++++++-------- docs/commands/update.md | 8 +++++++- docs/register.md | 15 +++++++++++++++ docs/structure.md | 4 ++++ 4 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 docs/register.md diff --git a/docs/README.md b/docs/README.md index f833da45..eaf819d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,11 +4,14 @@ The docs folder contains all documentation of Packit. ## Available files -| File | Short description | -| --------------------------------------------- | ------------------------------------------------------------------------------ | -| [Repository metadata](./metadata.md) | Describes the structure of metadata repositories and all available fields. | -| [Metadata checks](./meta-checks.md) | Describes how the metadata checks work. | -| [Tests](./tests.md) | Describes how Packit's tests work and how to run them. | -| [Structure and Configuration](./structure.md) | Describes Packit's file structure and how to configure Packit. | -| [Verifier](./verifier.md) | Describes how to use the verifier and repairer, and its structure in the code. | -| [Commands](./commands) | Contains explanations and examples of all Packit commands. | +| File | Short description | +| ------------------------------------------------- | -------------------------------------------------------------------------- | +| [Repository metadata](./metadata.md) | Describes the structure of metadata repositories and all available fields. | +| [Metadata checks](./meta-checks.md) | Describes how the metadata checks work. | +| [Tests](./tests.md) | Describes how Packit's tests work and how to run them. | +| [Structure and Configuration](./structure.md) | Describes Packit's file structure and how to configure Packit. | +| [Verifier](./verifier.md) | Describes how to use the verifier and repairer, and its structure in the code. | +| [Commands](./commands) | Contains explanations and examples of all Packit commands. | +| [Build Environment](./build-env.md) | Describes the environment that is used for building packages. | +| [Installation Process](./installation-process.md) | Describes the full process that Packit executes when installing a package. | +| [Register](./register.md) | Explains the use of the `Register.toml` file and `metadata` directory. | diff --git a/docs/commands/update.md b/docs/commands/update.md index 5ca032af..179d05dc 100644 --- a/docs/commands/update.md +++ b/docs/commands/update.md @@ -1,7 +1,7 @@ # Update The `update` command has the following command line syntax:
-`pit update [[@] ...] [--new-version ] [--all] [--exclude ...]` +`pit update [[@] ...] [--new-version ] [--all] [--exclude ...] [--refresh-only] [--skip-refresh]` ## Basic update The `update` command updates the specified packages, using the following syntax:
@@ -34,3 +34,9 @@ The `--all` flag can be used to update all latest installed versions to the late ### `--exclude ...` The `--exclude` flag can be used to exclude certain packages when using the `--all` flag. + +### `--refresh-only` +The `--refresh-only` flag can be used to only refresh the local metadata of the specified packages. It cannot be used together with the `--skip-refresh` flag. + +### `--skip-refresh` +The `--skip-refresh` flag can be used to skip the refreshing of the local metadata of the packages. It cannot be used together with the `--refresh-only` flag. diff --git a/docs/register.md b/docs/register.md new file mode 100644 index 00000000..2ed9db3b --- /dev/null +++ b/docs/register.md @@ -0,0 +1,15 @@ +# Register and Local Metadata + +This file explains the `Register.toml` file and the local metadata storage. + +# `Register.toml` +The `Register.toml` file stores all information that Packit needs to keep track of which packages are installed, which dependencies they have and where they were installed from. + +This file stores package information, for example the active version and the symlinked state. + +It also stores package version information, which consists of installation path, dependencies, source metadata repository and other fields that describe the package installation. + +# Local Metadata +The local metadata is stored in the `metadata` directory. This storage consists of the relevant package metadata that is required for all operations on packages that require the metadata. By storing this data locally, we do not rely on a remote repository. If the device is offline or the repository is not available anymore, the packages keep working as before. + +This storage is structured in the same way as the `packages` directory, it contains directories for each package, with directories for each version of the package inside. Inside these version directories, the metadata for that version is stored. Scripts and other files that are needed are downloaded to this directory. The `metadata.toml` file stores all relevant metadata fields of the package. diff --git a/docs/structure.md b/docs/structure.md index 96ce889d..3b7f3ded 100644 --- a/docs/structure.md +++ b/docs/structure.md @@ -5,6 +5,7 @@ This file explains the Packit structure and configuration. ## File Structure The Packit directory, which contains Packit data is called the [prefix](#prefix) directory. This directory contains the following files and directories: - [Register.toml](#registertoml) +- [metadata](#local-metadata) - [packages](#packages) - [bin, gnubin, lib, include and share](#symlinks) - [active](#active-packages) @@ -21,6 +22,9 @@ The prefix directory of Packit contains all data of installed packages.
### Register.toml The `Register.toml` file is located inside the prefix and stores information about all installed packages. This file is managed by Packit and should not be changed directly. +### Local Metadata +The `metadata` directory contains all of the local metadata of the installed packages. This directory is managed by Packit and should not be changed directly. + ### Packages All installed packages will go in `/packages///`. From dda47c9588ccdeb00330d8c1141980db02e5c5e0 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Sep 2026 00:39:13 +0200 Subject: [PATCH 19/20] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a796ecaf..0b39331a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - The `--exclude` flag for the `pit util package` command, to exclude certain packages when using the `--all` flag. - New and improved documentation. - The notice fields in the metadata to show a message before or after an installation. +- The local metadata storage, which stores the metadata of installed packages. ### Changes - The build tests are now turned off by default and can be turned on with `--execute-build-test` (`--skip-build-test` is removed). From b8397cf3068c6804815a093a142ac3af1f508764 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Sep 2026 00:43:44 +0200 Subject: [PATCH 20/20] Fix clippy warnings --- src/cli/commands/link.rs | 4 ++-- src/cli/commands/uninstall.rs | 6 +++--- src/cli/commands/update.rs | 2 +- src/register/metadata/handler.rs | 6 ++++-- src/utils/serialization.rs | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/cli/commands/link.rs b/src/cli/commands/link.rs index f36f7ff6..c7e86679 100644 --- a/src/cli/commands/link.rs +++ b/src/cli/commands/link.rs @@ -101,7 +101,7 @@ impl LinkArgs { let package_conflicts = local_meta_handler.read_package_conflicts(package).unwrap_or_exit_msg("Error while reading local metadata", 1); let conflicts = local_meta_handler - .get_conflicting_packages(®ister, &self.package_name, &package_conflicts) + .get_conflicting_packages(register, &self.package_name, &package_conflicts) .unwrap_or_exit_msg("Error while reading local metadata", 1); if !conflicts.is_empty() { @@ -111,7 +111,7 @@ impl LinkArgs { return false; } - let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(&package_id); + let local_meta_handler = LocalMetaHandler::new(&config.prefix_directory).get_package(package_id); let local_metadata = local_meta_handler.read_metadata().unwrap_or_exit_msg("Unable to read local metadata", 1); // Skip if the local metadata defines skip_symlinking diff --git a/src/cli/commands/uninstall.rs b/src/cli/commands/uninstall.rs index 4aec571f..f3cb4aa6 100644 --- a/src/cli/commands/uninstall.rs +++ b/src/cli/commands/uninstall.rs @@ -93,7 +93,7 @@ impl UninstallArgs { for dependent in &missing_dependents { // If none of the given packages covers the dependent throw an error and exit - if !parameter_checks::contains_package_id(&self.packages, &dependent) { + if !parameter_checks::contains_package_id(&self.packages, dependent) { error!( msg: "{} cannot be uninstalled, because it is a dependency of the following packages:", package.style() @@ -119,13 +119,13 @@ impl UninstallArgs { return package.get_versions().iter().flat_map(|p| p.dependents.iter().cloned()).collect(); } - not_found::register_package(&optional_id.name, ®ister) + not_found::register_package(&optional_id.name, register) }; if let Some(package_version) = register.get_package_version(&package_id) { return package_version.dependents.iter().cloned().collect(); } - not_found::register_package_version(&package_id, ®ister) + not_found::register_package_version(&package_id, register) } } diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs index 53661341..fab64fa4 100644 --- a/src/cli/commands/update.rs +++ b/src/cli/commands/update.rs @@ -193,7 +193,7 @@ impl UpdateArgs { error!(msg: "No packages specified to refresh"); exit(1); }, - false => ¶meter_checks::expand_optional_ids(®ister, &config, &self.packages), + false => ¶meter_checks::expand_optional_ids(register, config, &self.packages), }; for package_id in packages { diff --git a/src/register/metadata/handler.rs b/src/register/metadata/handler.rs index 744cdf5a..9304d766 100644 --- a/src/register/metadata/handler.rs +++ b/src/register/metadata/handler.rs @@ -186,6 +186,7 @@ impl<'a> LocalMetaPackageHandler<'a> { /// Refreshes the local metadata of the given package. /// Returns true if the metadata was changed, false otherwise. + #[expect(clippy::borrowed_box)] pub fn refresh(&self, provider: &Box) -> Result { let metadata_dir = self.get_base_path(); @@ -316,8 +317,9 @@ impl<'a> LocalMetaPackageHandler<'a> { /// Requests a file from the given provider. /// If the file cannot be found, it returns an `LocalMetadataError::MetadataFileNotFound`, or None if the file is not required. /// Returns the bytes of the file if it can be found. + #[expect(clippy::borrowed_box)] fn request_file(&self, provider: &Box, file_path: &str, required: bool) -> Result> { - let Some(bytes) = provider.read_file_bytes(&self.package_id.name, &file_path)? else { + let Some(bytes) = provider.read_file_bytes(&self.package_id.name, file_path)? else { if !required { return Ok(None); } @@ -336,7 +338,7 @@ impl<'a> LocalMetaPackageHandler<'a> { /// Returns true if the file changed, false otherwise. fn write_file_if_changed( &self, - before_files: &Vec, + before_files: &[PathBuf], after_files: &mut Vec, destination: PathBuf, new_content: Option, diff --git a/src/utils/serialization.rs b/src/utils/serialization.rs index 9b002b64..d87176c2 100644 --- a/src/utils/serialization.rs +++ b/src/utils/serialization.rs @@ -11,7 +11,7 @@ where V: Serialize, { let mut entries: Vec<_> = map.iter().collect(); - entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + entries.sort_by_key(|(x, _)| *x); let mut map_serializer = serializer.serialize_map(Some(entries.len()))?;