diff --git a/CHANGELOG.md b/CHANGELOG.md index abed7d8f..b8f8ec2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - The `config repositories add` command now has a `--unchecked` flag, which can be used to skip repository checks. - The `--pause-build` flag to the `install` command, to pause the build after build script execution. - The `switch-dependency` command, which can be used to switch dependencies of a package. +- The `license_exclude` field to `sources` in the metadata, which can be used to exclude certain paths from automatic license file detection. +- The `license_include` field to `sources` in the metadata, which can be used to include certain files in the license file copying. ### Changes - Change display of true and false values in the `info` and `search` commands. diff --git a/docs/metadata.md b/docs/metadata.md index 251b6da8..3ff83949 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -68,6 +68,8 @@ The targets.toml file can contain one or multiple sources, specified in the foll | `size` | Defines the size of the source archive in bytes. | | `mirrors` | Defines a list of mirrors which could be used to download the sourcecode if the original url is unavailable. | | `skip_unpack` | True to skip the unpack step and just download the source file, false to use the build in unpack. | +| `license_exclude` | A list of paths to skip when doing automatic license file detection. `*` can be used to skip detection entirely. | +| `license_include` | A list of files that need to be copied to the package license file directory. These files are copied before the automatic detection. | | `apply_patches_in` | Defines the directory that should be used to apply all patches in. | | `patches` | A list of patches to apply to the source. See patches section below. | diff --git a/src/builder/builder.rs b/src/builder/builder.rs index 3a068fd5..fbb3930a 100644 --- a/src/builder/builder.rs +++ b/src/builder/builder.rs @@ -223,7 +223,7 @@ impl<'a> Builder<'a> { // Copy license files let license_directory = destination_dir.as_ref().join("share").join("licenses").join(&install_meta.package_metadata.name); - self.copy_license_files(build_directory.path(), &license_directory)?; + self.copy_license_files(build_directory.path(), &license_directory, source)?; // Patch binaries BinaryPatcher::new(self.config).patch_binaries_in(destination_dir.as_ref().to_path_buf(), &package_id, installed_dependencies)?; @@ -355,7 +355,18 @@ impl<'a> Builder<'a> { /// Copies license files from the original source into the destination directory. /// Does a breadth-first search from the build directory and stops when it finds license files. /// Only traverse to depth 2, to prevent detecting third party license files. - fn copy_license_files(&self, build_directory: &Path, destination_dir: &Path) -> Result<()> { + fn copy_license_files(&self, build_directory: &Path, destination_dir: &Path, source: &Source) -> Result<()> { + // Copy all include paths first + self.copy_include_license_files(build_directory, destination_dir, source)?; + + // Skip copying entirely if exclude `*` is specified + if source.license_exclude.iter().any(|x| x == "*") { + debug!("Skipping license file copying"); + return Ok(()); + } + + let exclude_paths: Vec<_> = source.license_exclude.iter().map(|x| build_directory.join(x)).collect(); + let mut queue = VecDeque::from([(0, build_directory.to_path_buf())]); while let Some((depth, item)) = queue.pop_front() { let mut found_files = false; @@ -364,6 +375,11 @@ impl<'a> Builder<'a> { for entry in fs::read_dir(&item).err_with_path("read", &item)? { let entry = entry.err_with_path("iterate", &item)?; + // Skip paths that should be excluded + if exclude_paths.contains(&entry.path()) { + continue; + } + let metadata = entry.metadata().err_with_path("read metadata of", entry.path())?; // If the entry is a directory, add it to the queue @@ -389,13 +405,23 @@ impl<'a> Builder<'a> { } found_files = true; + debug!( + "Found license file at '{}'", + entry.path().strip_prefix(build_directory).unwrap_or(build_directory).display() + ); + + // Check if file was already copied (for example using `license_include`) + let destination_path = destination_dir.join(entry.file_name()); + if destination_path.exists() { + debug!("License file with same name is already copied, skipping file"); + } // Create destination directory if it does not exist if !destination_dir.exists() { fs::create_dir_all(destination_dir).err_with_path("create dirs", destination_dir)?; } - fs::copy(entry.path(), destination_dir.join(entry.file_name())).err_with_path("copy", entry.path())?; + fs::copy(entry.path(), destination_path).err_with_path("copy", entry.path())?; break; } } @@ -410,4 +436,38 @@ impl<'a> Builder<'a> { debug!("Unable to find license files for package"); Ok(()) } + + /// Copies license files that are listed as `license_include` into the destination directory. + fn copy_include_license_files(&self, build_directory: &Path, destination_dir: &Path, source: &Source) -> Result<()> { + for include_path_str in &source.license_include { + let include_path = build_directory.join(include_path_str); + + // Skip if the path does not exist + if !include_path.exists() { + warning!("Specified license file include path '{include_path_str}' does not exist"); + continue; + } + + // Check if the path is a file + let metadata = fs::metadata(&include_path).err_with_path("read metadata", &include_path)?; + if !metadata.is_file() { + warning!("Specified license file include path '{include_path_str}' is not a file"); + continue; + } + + let Some(file_name) = include_path.file_name() else { + warning!("Specified license file include path '{include_path_str}' is not a valid path"); + continue; + }; + + // Create destination directory if it does not exist + if !destination_dir.exists() { + fs::create_dir_all(destination_dir).err_with_path("create dirs", destination_dir)?; + } + + fs::copy(&include_path, destination_dir.join(file_name)).err_with_path("copy", include_path)?; + } + + Ok(()) + } } diff --git a/src/register/package_register.rs b/src/register/package_register.rs index 059c3e71..13296436 100644 --- a/src/register/package_register.rs +++ b/src/register/package_register.rs @@ -492,6 +492,8 @@ pub mod tests { size: FileSize(0), mirrors: Vec::new(), skip_unpack: false, + license_exclude: Vec::new(), + license_include: Vec::new(), apply_patches_in: None, patches: HashMap::new(), }), diff --git a/src/repositories/types/common.rs b/src/repositories/types/common.rs index 2a4d7af5..e3642ff7 100644 --- a/src/repositories/types/common.rs +++ b/src/repositories/types/common.rs @@ -31,6 +31,12 @@ pub struct Source { #[serde(default, skip_serializing_if = "<&bool>::not")] pub skip_unpack: bool, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub license_exclude: Vec, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub license_include: Vec, pub apply_patches_in: Option, #[serde(default, deserialize_with = "Source::deserialize_patches", skip_serializing_if = "HashMap::is_empty")]