diff --git a/rust-tests/src/lib.rs b/rust-tests/src/lib.rs index a51461bfb..d4564cdb8 100644 --- a/rust-tests/src/lib.rs +++ b/rust-tests/src/lib.rs @@ -715,4 +715,23 @@ requirements: assert!(output.contains("No license files were copied")); assert!(output.contains("The following license files were not found: *.license")); } + + #[test] + fn test_absolute_path_license() { + let tmp = tmp("test_absolute_path_license"); + let rattler_build = rattler().build( + recipes().join("absolute_path_license"), + tmp.as_dir(), + None, + None, + ); + + // This should now succeed with absolute path support + assert!(rattler_build.status.success()); + + // Verify both the relative and absolute path license files were copied + let pkg = get_extracted_package(tmp.as_dir(), "absolute-path-license"); + assert!(pkg.join("info/licenses/LICENSE").exists()); + assert!(pkg.join("info/licenses/external_license.txt").exists()); + } } diff --git a/src/packaging.rs b/src/packaging.rs index 834f7a97b..467a8b08f 100644 --- a/src/packaging.rs +++ b/src/packaging.rs @@ -97,6 +97,7 @@ pub enum PackagingError { /// This function copies the license files to the info/licenses folder. /// License files are selected from the recipe directory and the source (work) folder. /// If the same file is found in both locations, the file from the recipe directory is used. +/// Absolute paths are also supported and will be copied directly. fn copy_license_files( output: &Output, tmp_dir_path: &Path, @@ -107,59 +108,103 @@ fn copy_license_files( let licenses_folder = tmp_dir_path.join("info/licenses/"); fs::create_dir_all(&licenses_folder)?; - let copy_dir_work = copy_dir::CopyDir::new( - &output.build_configuration.directories.work_dir, - &licenses_folder, - ) - .with_globvec(&output.recipe.about().license_file) - .use_gitignore(false) - .run()?; - - let copied_files_work_dir = copy_dir_work.copied_paths(); - - let copy_dir_recipe = copy_dir::CopyDir::new( - &output.build_configuration.directories.recipe_dir, - &licenses_folder, - ) - .with_globvec(&output.recipe.about().license_file) - .use_gitignore(false) - .overwrite(true) - .run()?; - - let copied_files_recipe_dir = copy_dir_recipe.copied_paths(); - - // if a file was copied from the recipe dir, and the work dir, we should - // issue a warning - for file in copied_files_recipe_dir { - if copied_files_work_dir.contains(file) { - let warn_str = format!( - "License file from source directory was overwritten by license file from recipe folder ({})", - file.display() - ); - tracing::warn!(warn_str); - output.record_warning(&warn_str); + // Separate absolute paths from relative glob patterns + let (absolute_paths, relative_globs): (Vec<_>, Vec<_>) = output + .recipe + .about() + .license_file + .include_globs() + .iter() + .partition(|glob| Path::new(glob.source()).is_absolute()); + + let mut copied_files = HashSet::new(); + let mut missing_globs = Vec::new(); + + // Handle absolute paths directly + for glob_with_source in &absolute_paths { + let abs_path = Path::new(glob_with_source.source()); + + if abs_path.exists() { + // Get the file name to use as destination + let file_name = abs_path.file_name().ok_or_else(|| { + PackagingError::IoError(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "Invalid absolute path for license file: {}", + abs_path.display() + ), + )) + })?; + + let dest_path = licenses_folder.join(file_name); + fs::copy(abs_path, &dest_path)?; + copied_files.insert(dest_path); + } else { + missing_globs.push(glob_with_source.source().to_string()); } } - let copied_files = copied_files_recipe_dir - .iter() - .chain(copied_files_work_dir) - .map(PathBuf::from) - .collect::>(); + // Only process relative globs if there are any + if !relative_globs.is_empty() { + // Create a new GlobVec with only relative patterns + let relative_globvec = crate::recipe::parser::GlobVec::from_vec( + relative_globs.iter().map(|g| g.source()).collect(), + None, + ); - // Check which globs didn't match any files - let mut missing_globs = Vec::new(); + let copy_dir_work = copy_dir::CopyDir::new( + &output.build_configuration.directories.work_dir, + &licenses_folder, + ) + .with_globvec(&relative_globvec) + .use_gitignore(false) + .run()?; + + let copied_files_work_dir = copy_dir_work.copied_paths(); + + let copy_dir_recipe = copy_dir::CopyDir::new( + &output.build_configuration.directories.recipe_dir, + &licenses_folder, + ) + .with_globvec(&relative_globvec) + .use_gitignore(false) + .overwrite(true) + .run()?; + + let copied_files_recipe_dir = copy_dir_recipe.copied_paths(); + + // if a file was copied from the recipe dir, and the work dir, we should + // issue a warning + for file in copied_files_recipe_dir { + if copied_files_work_dir.contains(file) { + let warn_str = format!( + "License file from source directory was overwritten by license file from recipe folder ({})", + file.display() + ); + tracing::warn!(warn_str); + output.record_warning(&warn_str); + } + } + + // Merge copied files from work and recipe dirs + copied_files.extend( + copied_files_recipe_dir + .iter() + .chain(copied_files_work_dir) + .map(PathBuf::from), + ); - // Check globs from both work and recipe dir results - for (glob_str, match_obj) in copy_dir_work.include_globs() { - if !match_obj.get_matched() { - // Check if it matched in the recipe dir - if let Some(recipe_match) = copy_dir_recipe.include_globs().get(glob_str) { - if !recipe_match.get_matched() { + // Check which globs didn't match any files + for (glob_str, match_obj) in copy_dir_work.include_globs() { + if !match_obj.get_matched() { + // Check if it matched in the recipe dir + if let Some(recipe_match) = copy_dir_recipe.include_globs().get(glob_str) { + if !recipe_match.get_matched() { + missing_globs.push(glob_str.clone()); + } + } else { missing_globs.push(glob_str.clone()); } - } else { - missing_globs.push(glob_str.clone()); } } } diff --git a/test-data/recipes/absolute_path_license/external_license.txt b/test-data/recipes/absolute_path_license/external_license.txt new file mode 100644 index 000000000..0671a33f8 --- /dev/null +++ b/test-data/recipes/absolute_path_license/external_license.txt @@ -0,0 +1 @@ +absolute path test diff --git a/test-data/recipes/absolute_path_license/recipe.yaml b/test-data/recipes/absolute_path_license/recipe.yaml new file mode 100644 index 000000000..1c67b10c8 --- /dev/null +++ b/test-data/recipes/absolute_path_license/recipe.yaml @@ -0,0 +1,27 @@ +package: + name: absolute-path-license + version: "0.1.0" + +build: + script: + # Create license file in work directory (relative path) + - echo "test content" > LICENSE + # Create license file outside work directory (absolute path) + # Use a cross-platform approach with build-time environment variable + - if: unix + then: + - mkdir -p /tmp/rattler_test_licenses + - echo "absolute path test" > /tmp/rattler_test_licenses/external_license.txt + - if: win + then: + - mkdir %TEMP%\rattler_test_licenses + - echo "absolute path test" > %TEMP%\rattler_test_licenses\external_license.txt + +about: + license_file: + - LICENSE + # Use conditional to specify the absolute path per platform + - if: unix + then: /tmp/rattler_test_licenses/external_license.txt + - if: win + then: ${{ env.get("TEMP") }}\rattler_test_licenses\external_license.txt