Skip to content

Commit 8cc67cf

Browse files
committed
Fix: symlinks in normalized upload
1 parent 775f60f commit 8cc67cf

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

src/commands/build/upload.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,4 +651,48 @@ mod tests {
651651
);
652652
Ok(())
653653
}
654+
655+
#[test]
656+
#[cfg(not(windows))]
657+
fn test_normalize_directory_preserves_symlinks() -> Result<()> {
658+
use std::os::unix::fs::symlink;
659+
660+
let temp_dir = crate::utils::fs::TempDir::create()?;
661+
let test_dir = temp_dir.path().join("TestApp.xcarchive");
662+
fs::create_dir_all(test_dir.join("Products"))?;
663+
664+
// Create a regular file
665+
fs::write(test_dir.join("Products").join("app.txt"), "test content")?;
666+
667+
// Create a symlink pointing to the regular file
668+
let symlink_path = test_dir.join("Products").join("app_link.txt");
669+
symlink("app.txt", &symlink_path)?;
670+
671+
let result_zip = normalize_directory(&test_dir, temp_dir.path())?;
672+
let zip_file = fs::File::open(result_zip.path())?;
673+
let mut archive = ZipArchive::new(zip_file)?;
674+
675+
// Check that both the regular file and symlink are in the zip
676+
let mut has_regular_file = false;
677+
let mut has_symlink = false;
678+
679+
for i in 0..archive.len() {
680+
let file = archive.by_index(i)?;
681+
let file_name = file.name();
682+
683+
if file_name == "TestApp.xcarchive/Products/app.txt" {
684+
has_regular_file = true;
685+
// Verify it's actually a regular file, not a symlink
686+
assert!(!file.is_symlink(), "app.txt should be a regular file, not a symlink");
687+
} else if file_name == "TestApp.xcarchive/Products/app_link.txt" {
688+
has_symlink = true;
689+
// Verify it's actually a symlink
690+
assert!(file.is_symlink(), "app_link.txt should be a symlink in the zip");
691+
}
692+
}
693+
694+
assert!(has_regular_file, "Regular file should be in zip");
695+
assert!(has_symlink, "Symlink should be preserved in zip");
696+
Ok(())
697+
}
654698
}

src/utils/build/normalize.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ use zip::{DateTime, ZipWriter};
1717

1818
fn sort_entries(path: &Path) -> Result<impl Iterator<Item = (PathBuf, PathBuf)>> {
1919
Ok(WalkDir::new(path)
20-
.follow_links(true)
2120
.into_iter()
2221
.filter_map(Result::ok)
23-
.filter(|entry| entry.path().is_file())
22+
.filter(|entry| {
23+
let path = entry.path();
24+
// Include both regular files and symlinks
25+
path.is_file() || path.is_symlink()
26+
})
2427
.map(|entry| {
2528
let entry_path = entry.into_path();
2629
let relative_path = entry_path.strip_prefix(path)?.to_owned();
@@ -52,9 +55,19 @@ fn add_entries_to_zip(
5255

5356
let zip_path = format!("{directory_name}/{}", relative_path.to_string_lossy());
5457

55-
zip.start_file(zip_path, options)?;
56-
let file_byteview = ByteView::open(&entry_path)?;
57-
zip.write_all(file_byteview.as_slice())?;
58+
if entry_path.is_symlink() {
59+
// Handle symlinks by reading the target path and writing it as a symlink
60+
let target = std::fs::read_link(&entry_path)?;
61+
let target_str = target.to_string_lossy();
62+
63+
// Create a symlink entry in the zip
64+
zip.add_symlink(zip_path, &target_str, options)?;
65+
} else {
66+
// Handle regular files
67+
zip.start_file(zip_path, options)?;
68+
let file_byteview = ByteView::open(&entry_path)?;
69+
zip.write_all(file_byteview.as_slice())?;
70+
}
5871
file_count += 1;
5972
}
6073

0 commit comments

Comments
 (0)