Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions rust-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
137 changes: 91 additions & 46 deletions src/packaging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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::<HashSet<PathBuf>>();
// 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());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
absolute path test
27 changes: 27 additions & 0 deletions test-data/recipes/absolute_path_license/recipe.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading