From 311a5e00330dee18d7d6548d3d8ad357b91fd42e Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Tue, 8 Jul 2025 14:08:49 -0700 Subject: [PATCH 1/7] bug(launchpad): Ensure mobile artifact's zip structures are preserved --- src/commands/mobile_app/upload.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index 082a1582e6..ec9fd1bee0 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -242,6 +242,13 @@ fn normalize_directory(path: &Path) -> Result { let mut file_count = 0; + // Get the directory name to preserve in the zip structure + let dir_name = path + .file_name() + .ok_or_else(|| anyhow!("Failed to get directory name"))? + .to_str() + .ok_or_else(|| anyhow!("Directory name is not valid UTF-8"))?; + // Collect and sort entries for deterministic ordering // This is important to ensure stable sha1 checksums for the zip file as // an optimization is used to avoid re-uploading the same chunks if they're already on the server. @@ -253,7 +260,9 @@ fn normalize_directory(path: &Path) -> Result { .map(|entry| { let entry_path = entry.into_path(); let relative_path = entry_path.strip_prefix(path)?.to_owned(); - Ok((entry_path, relative_path)) + // Preserve the directory structure by including the directory name + let full_relative_path = Path::new(dir_name).join(relative_path); + Ok((entry_path, full_relative_path)) }) .collect::>>()? .into_iter() From dbd4d5d3f9ccd06432bc4adb16d75f1fe289fd0c Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Wed, 9 Jul 2025 07:00:54 -0700 Subject: [PATCH 2/7] address feedback --- src/commands/mobile_app/upload.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index ec9fd1bee0..6f6f19a3bb 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -257,16 +257,10 @@ fn normalize_directory(path: &Path) -> Result { .into_iter() .filter_map(Result::ok) .filter(|entry| entry.path().is_file()) - .map(|entry| { - let entry_path = entry.into_path(); - let relative_path = entry_path.strip_prefix(path)?.to_owned(); - // Preserve the directory structure by including the directory name - let full_relative_path = Path::new(dir_name).join(relative_path); - Ok((entry_path, full_relative_path)) - }) + .map(|entry| Ok(entry.into_path())) .collect::>>()? .into_iter() - .sorted_by(|(_, a), (_, b)| a.cmp(b)); + .sorted_by(|a, b| a.cmp(b)); // Need to set the last modified time to a fixed value to ensure consistent checksums // This is important as an optimization to avoid re-uploading the same chunks if they're already on the server @@ -275,10 +269,12 @@ fn normalize_directory(path: &Path) -> Result { .compression_method(zip::CompressionMethod::Stored) .last_modified_time(DateTime::default()); - for (entry_path, relative_path) in entries { - debug!("Adding file to zip: {}", relative_path.display()); + for entry_path in entries { + let relative_path = entry_path.strip_prefix(path)?.to_owned(); + let full_relative_path = Path::new(dir_name).join(relative_path); + debug!("Adding file to zip: {}", full_relative_path.display()); - zip.start_file(relative_path.to_string_lossy(), options)?; + zip.start_file(full_relative_path.to_string_lossy(), options)?; let file_byteview = ByteView::open(&entry_path)?; zip.write_all(file_byteview.as_slice())?; file_count += 1; From b26bda47be35a6dd7ae8cfa0530f3e78d4744da6 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Wed, 9 Jul 2025 07:11:07 -0700 Subject: [PATCH 3/7] further simplify --- src/commands/mobile_app/upload.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index 6f6f19a3bb..7307930e54 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -242,13 +242,6 @@ fn normalize_directory(path: &Path) -> Result { let mut file_count = 0; - // Get the directory name to preserve in the zip structure - let dir_name = path - .file_name() - .ok_or_else(|| anyhow!("Failed to get directory name"))? - .to_str() - .ok_or_else(|| anyhow!("Directory name is not valid UTF-8"))?; - // Collect and sort entries for deterministic ordering // This is important to ensure stable sha1 checksums for the zip file as // an optimization is used to avoid re-uploading the same chunks if they're already on the server. @@ -270,11 +263,12 @@ fn normalize_directory(path: &Path) -> Result { .last_modified_time(DateTime::default()); for entry_path in entries { - let relative_path = entry_path.strip_prefix(path)?.to_owned(); - let full_relative_path = Path::new(dir_name).join(relative_path); - debug!("Adding file to zip: {}", full_relative_path.display()); + let zip_path = entry_path.strip_prefix( + path.parent().ok_or_else(|| anyhow!("Failed to get parent directory"))? + )?.to_owned(); + debug!("Adding file to zip: {}", zip_path.display()); - zip.start_file(full_relative_path.to_string_lossy(), options)?; + zip.start_file(zip_path.to_string_lossy(), options)?; let file_byteview = ByteView::open(&entry_path)?; zip.write_all(file_byteview.as_slice())?; file_count += 1; From 391c3630e0629a03b18cd2fe00e9c648cc978f3a Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Fri, 11 Jul 2025 13:23:42 -0700 Subject: [PATCH 4/7] feedback --- src/commands/mobile_app/upload.rs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index 7307930e54..f0037c82f3 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -250,9 +250,7 @@ fn normalize_directory(path: &Path) -> Result { .into_iter() .filter_map(Result::ok) .filter(|entry| entry.path().is_file()) - .map(|entry| Ok(entry.into_path())) - .collect::>>()? - .into_iter() + .map(|entry| entry.into_path()) .sorted_by(|a, b| a.cmp(b)); // Need to set the last modified time to a fixed value to ensure consistent checksums @@ -400,3 +398,27 @@ fn poll_assemble( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use zip::ZipArchive; + + #[test] + fn test_normalize_directory_preserves_top_level_directory_name() -> Result<()> { + let temp_dir = crate::utils::fs::TempDir::create()?; + let test_dir = temp_dir.path().join("MyApp.xcarchive"); + fs::create_dir_all(&test_dir.join("Products"))?; + fs::write(test_dir.join("Products").join("app.txt"), "test content")?; + + let result_zip = normalize_directory(&test_dir)?; + let zip_file = fs::File::open(result_zip.path())?; + let mut archive = ZipArchive::new(zip_file)?; + let file = archive.by_index(0)?; + let file_path = file.name(); + + assert_eq!(file_path, "MyApp.xcarchive/Products/app.txt"); + Ok(()) + } +} From b9d924c7cbf7cced18c4a1d356257f2a1b5831ec Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Fri, 11 Jul 2025 13:30:42 -0700 Subject: [PATCH 5/7] lint error --- src/commands/mobile_app/upload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index f0037c82f3..c95805c3a1 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -409,7 +409,7 @@ mod tests { fn test_normalize_directory_preserves_top_level_directory_name() -> Result<()> { let temp_dir = crate::utils::fs::TempDir::create()?; let test_dir = temp_dir.path().join("MyApp.xcarchive"); - fs::create_dir_all(&test_dir.join("Products"))?; + fs::create_dir_all(test_dir.join("Products"))?; fs::write(test_dir.join("Products").join("app.txt"), "test content")?; let result_zip = normalize_directory(&test_dir)?; From a91eba921ec251e7361c34a1ac2e682d98b45537 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Fri, 11 Jul 2025 14:49:20 -0700 Subject: [PATCH 6/7] skip test for windows --- src/commands/mobile_app/upload.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index c95805c3a1..ad05f7d6f8 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -399,6 +399,7 @@ fn poll_assemble( Ok(()) } +#[cfg(not(windows))] #[cfg(test)] mod tests { use super::*; From 22b3eb4b8eb9de739c54a43db2c7616cefa82b92 Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Mon, 14 Jul 2025 06:30:16 -0700 Subject: [PATCH 7/7] feedback --- src/commands/mobile_app/upload.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index ad05f7d6f8..c94c7ddecc 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -250,8 +250,16 @@ fn normalize_directory(path: &Path) -> Result { .into_iter() .filter_map(Result::ok) .filter(|entry| entry.path().is_file()) - .map(|entry| entry.into_path()) - .sorted_by(|a, b| a.cmp(b)); + .map(|entry| { + let entry_path = entry.into_path(); + let relative_path = entry_path.strip_prefix( + path.parent().ok_or_else(|| anyhow!("Cannot determine parent directory for path: {}", path.display()))? + )?.to_owned(); + Ok((entry_path, relative_path)) + }) + .collect::>>()? + .into_iter() + .sorted_by(|(_, a), (_, b)| a.cmp(b)); // Need to set the last modified time to a fixed value to ensure consistent checksums // This is important as an optimization to avoid re-uploading the same chunks if they're already on the server @@ -260,13 +268,10 @@ fn normalize_directory(path: &Path) -> Result { .compression_method(zip::CompressionMethod::Stored) .last_modified_time(DateTime::default()); - for entry_path in entries { - let zip_path = entry_path.strip_prefix( - path.parent().ok_or_else(|| anyhow!("Failed to get parent directory"))? - )?.to_owned(); - debug!("Adding file to zip: {}", zip_path.display()); + for (entry_path, relative_path) in entries { + debug!("Adding file to zip: {}", relative_path.display()); - zip.start_file(zip_path.to_string_lossy(), options)?; + zip.start_file(relative_path.to_string_lossy(), options)?; let file_byteview = ByteView::open(&entry_path)?; zip.write_all(file_byteview.as_slice())?; file_count += 1; @@ -418,7 +423,6 @@ mod tests { let mut archive = ZipArchive::new(zip_file)?; let file = archive.by_index(0)?; let file_path = file.name(); - assert_eq!(file_path, "MyApp.xcarchive/Products/app.txt"); Ok(()) }