diff --git a/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift b/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift index fb2a5767ca..ab6c1d0577 100644 --- a/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift +++ b/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift @@ -7,13 +7,14 @@ import ObjcSupport @_cdecl("swift_inspect_asset_catalog") // Insepects the asset catalog and writes the results to a JSON file // in the xcarchive containing the asset catalog. -public func swift_inspect_asset_catalog(_ path: UnsafePointer) { +public func swift_inspect_asset_catalog(_ path: UnsafePointer, outputPath: UnsafePointer) { let pathString = String(cString: path) + let outputPathString = String(cString: outputPath) if #available(macOS 13.0, *) { let supportedVersions = [13, 14, 15] let version = ProcessInfo.processInfo.operatingSystemVersion if supportedVersions.contains(version.majorVersion) { - AssetUtil.disect(file: URL(filePath: pathString)) + AssetUtil.disect(file: URL(filePath: pathString), outputURL: URL(filePath: outputPathString)) } else { print("Skipping asset catalog inspection on unsupported macOS version \(version)") } @@ -48,8 +49,8 @@ typealias objectiveCMethodImp = @convention(c) (AnyObject, Selector, UnsafeRawPo >? enum AssetUtil { - private static func createResultsPath(assetPath: URL) throws -> URL { - var archiveURL = assetPath + private static func createResultsPath(assetURL: URL, outputURL: URL) throws -> URL { + var archiveURL = assetURL var tailComponents: [String] = [] while archiveURL.pathExtension != "xcarchive" && archiveURL.pathComponents.count > 1 { tailComponents.insert(archiveURL.lastPathComponent, at: 0) @@ -58,11 +59,10 @@ enum AssetUtil { if archiveURL.pathExtension != "xcarchive" { throw Error.pathError } - let parsedRoot = archiveURL.appendingPathComponent("ParsedAssets", - isDirectory: true) + let destDir = tailComponents .dropLast() - .reduce(parsedRoot) { partial, next in + .reduce(outputURL) { partial, next in partial.appendingPathComponent(next, isDirectory: true) } try! FileManager.default.createDirectory(at: destDir, @@ -70,7 +70,7 @@ enum AssetUtil { return destDir } - @discardableResult static func disect(file: URL) -> [AssetCatalogEntry] { + @discardableResult static func disect(file: URL, outputURL: URL) -> [AssetCatalogEntry] { var assets: [AssetCatalogEntry] = [] var colorLength: UInt = 0 var colorCount = 0 @@ -154,7 +154,7 @@ enum AssetUtil { )) let data = try! JSONEncoder().encode(assets) - let folder = try! createResultsPath(assetPath: file) + let folder = try! createResultsPath(assetURL: file, outputURL: outputURL) let url = folder .appendingPathComponent("Assets") .appendingPathExtension("json") diff --git a/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift b/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift index 234a6cc871..4c59b725df 100644 --- a/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift +++ b/apple-catalog-parsing/native/swift/AssetCatalogParser/Tests/AssetCatalogParserTests/AssetCatalogParserTests.swift @@ -6,7 +6,7 @@ struct AssetCatalogParserTests { @Test func testParseAssets() throws { let archivePath = try #require(Bundle.module.path(forResource: "test", ofType: "xcarchive")) let url = URL(filePath: "\(archivePath)/Products/Applications/DemoApp.app/Assets.car") - let results = AssetUtil.disect(file: url) + let results = AssetUtil.disect(file: url, outputURL: URL(filePath: "\(archivePath)/ParsedAssets")) #expect(results.count == 2) } } diff --git a/apple-catalog-parsing/src/asset_catalog.rs b/apple-catalog-parsing/src/asset_catalog.rs index 9d7759ffdb..20bc43fb87 100644 --- a/apple-catalog-parsing/src/asset_catalog.rs +++ b/apple-catalog-parsing/src/asset_catalog.rs @@ -11,7 +11,10 @@ pub enum Error { } extern "C" { - fn swift_inspect_asset_catalog(msg: *const std::os::raw::c_char); + fn swift_inspect_asset_catalog( + catalog_path: *const std::os::raw::c_char, + output_path: *const std::os::raw::c_char, + ); } /// This calls out to Swift code that uses Apple APIs to convert the contents @@ -20,17 +23,19 @@ extern "C" { /// as duplicate image detection, xray, and image optimization insights. /// The path should be in an xcarchive file, results are written /// to a JSON file in the xcarchive’s ParsedAssets directory. -pub fn inspect_asset_catalog

(path: P) -> Result<(), Error> +pub fn inspect_asset_catalog

(catalog_path: P, output_path: P) -> Result<(), Error> where P: AsRef, { - let c_string = CString::new(path.as_ref().as_os_str().as_bytes())?; - let string_ptr = c_string.as_ptr(); + let catalog_c_string = CString::new(catalog_path.as_ref().as_os_str().as_bytes())?; + let output_path_c_string = CString::new(output_path.as_ref().as_os_str().as_bytes())?; + let catalog_string_ptr = catalog_c_string.as_ptr(); + let output_string_ptr = output_path_c_string.as_ptr(); unsafe { // The string pointed to is immutable, in Swift we cannot change it. // We ensure this by using "UnsafePointer" in Swift which is // immutable (as opposed to "UnsafeMutablePointer"). - swift_inspect_asset_catalog(string_ptr); + swift_inspect_asset_catalog(catalog_string_ptr, output_string_ptr); } Ok(()) } diff --git a/src/commands/mobile_app/upload.rs b/src/commands/mobile_app/upload.rs index 25beb9fffa..ef69e5198c 100644 --- a/src/commands/mobile_app/upload.rs +++ b/src/commands/mobile_app/upload.rs @@ -1,15 +1,10 @@ use std::borrow::Cow; -#[cfg(not(windows))] -use std::fs; use std::io::Write as _; -#[cfg(not(windows))] -use std::os::unix::fs::PermissionsExt as _; use std::path::Path; use anyhow::{anyhow, bail, Context as _, Result}; use clap::{Arg, ArgAction, ArgMatches, Command}; use indicatif::ProgressStyle; -use itertools::Itertools as _; use log::{debug, info, warn}; use sha1_smol::Digest; use symbolic::common::ByteView; @@ -21,14 +16,13 @@ use crate::config::Config; use crate::utils::args::ArgExt as _; use crate::utils::chunks::{upload_chunks, Chunk, ASSEMBLE_POLL_INTERVAL}; use crate::utils::fs::get_sha1_checksums; -#[cfg(all(target_os = "macos", target_arch = "aarch64"))] use crate::utils::fs::TempDir; use crate::utils::fs::TempFile; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use crate::utils::mobile_app::{ handle_asset_catalogs, ipa_to_xcarchive, is_apple_app, is_ipa_file, }; -use crate::utils::mobile_app::{is_aab_file, is_apk_file, is_zip_file}; +use crate::utils::mobile_app::{is_aab_file, is_apk_file, is_zip_file, normalize_directory}; use crate::utils::progress::ProgressBar; use crate::utils::vcs; @@ -95,11 +89,6 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let byteview = ByteView::open(path)?; debug!("Loaded file with {} bytes", byteview.len()); - #[cfg(all(target_os = "macos", target_arch = "aarch64"))] - if is_apple_app(path) { - handle_asset_catalogs(path); - } - validate_is_mobile_app(path, &byteview)?; let normalized_zip = if path.is_file() { @@ -107,7 +96,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { handle_file(path, &byteview)? } else if path.is_dir() { debug!("Normalizing directory: {}", path.display()); - normalize_directory(path).with_context(|| { + handle_directory(path).with_context(|| { format!( "Failed to generate uploadable bundle for directory {}", path.display() @@ -187,9 +176,9 @@ fn handle_file(path: &Path, byteview: &ByteView) -> Result { #[cfg(all(target_os = "macos", target_arch = "aarch64"))] if is_zip_file(byteview) && is_ipa_file(byteview)? { debug!("Converting IPA file to XCArchive structure"); - let temp_dir = TempDir::create()?; - return ipa_to_xcarchive(path, byteview, &temp_dir) - .and_then(|path| normalize_directory(&path)) + let archive_temp_dir = TempDir::create()?; + return ipa_to_xcarchive(path, byteview, &archive_temp_dir) + .and_then(|path| handle_directory(&path)) .with_context(|| format!("Failed to process IPA file {}", path.display())); } @@ -276,65 +265,13 @@ fn normalize_file(path: &Path, bytes: &[u8]) -> Result { Ok(temp_file) } -// For XCArchive directories, we'll zip the entire directory -fn normalize_directory(path: &Path) -> Result { - debug!("Creating normalized zip for directory: {}", path.display()); - - let temp_file = TempFile::create()?; - let mut zip = ZipWriter::new(temp_file.open()?); - - let mut file_count = 0; - - // 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. - let entries = walkdir::WalkDir::new(path) - .follow_links(true) - .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.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 - // but the last modified time being different will cause checksums to be different. - let options = SimpleFileOptions::default() - .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()); - - #[cfg(not(windows))] - // On Unix, we need to preserve the file permissions. - let options = options.unix_permissions(fs::metadata(&entry_path)?.permissions().mode()); - - 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; +fn handle_directory(path: &Path) -> Result { + let temp_dir = TempDir::create()?; + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + if is_apple_app(path) { + handle_asset_catalogs(path, temp_dir.path()); } - - zip.finish()?; - debug!( - "Successfully created normalized zip for directory with {} files", - file_count - ); - Ok(temp_file) + normalize_directory(path, temp_dir.path()) } fn upload_file( @@ -470,7 +407,7 @@ mod tests { 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 result_zip = normalize_directory(&test_dir, temp_dir.path())?; let zip_file = fs::File::open(result_zip.path())?; let mut archive = ZipArchive::new(zip_file)?; let file = archive.by_index(0)?; @@ -478,4 +415,68 @@ mod tests { assert_eq!(file_path, "MyApp.xcarchive/Products/app.txt"); Ok(()) } + + #[test] + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + fn test_xcarchive_upload_includes_parsed_assets() -> Result<()> { + // Test that XCArchive uploads include parsed asset catalogs + let xcarchive_path = Path::new("tests/integration/_fixtures/mobile_app/archive.xcarchive"); + + // Process the XCArchive directory + let result = handle_directory(xcarchive_path)?; + + // Verify the resulting zip contains parsed assets + let zip_file = fs::File::open(result.path())?; + let mut archive = ZipArchive::new(zip_file)?; + + let mut has_parsed_assets = false; + for i in 0..archive.len() { + let file = archive.by_index(i)?; + let file_name = file + .enclosed_name() + .ok_or(anyhow!("Failed to get file name"))?; + if file_name.to_string_lossy().contains("ParsedAssets") { + has_parsed_assets = true; + break; + } + } + + assert!( + has_parsed_assets, + "XCArchive upload should include parsed asset catalogs" + ); + Ok(()) + } + + #[test] + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + fn test_ipa_upload_includes_parsed_assets() -> Result<()> { + // Test that IPA uploads handle missing asset catalogs gracefully + let ipa_path = Path::new("tests/integration/_fixtures/mobile_app/ipa_with_asset.ipa"); + let byteview = ByteView::open(ipa_path)?; + + // Process the IPA file - this should work even without asset catalogs + let result = handle_file(ipa_path, &byteview)?; + + let zip_file = fs::File::open(result.path())?; + let mut archive = ZipArchive::new(zip_file)?; + + let mut has_parsed_assets = false; + for i in 0..archive.len() { + let file = archive.by_index(i)?; + let file_name = file + .enclosed_name() + .ok_or(anyhow!("Failed to get file name"))?; + if file_name.to_string_lossy().contains("ParsedAssets") { + has_parsed_assets = true; + break; + } + } + + assert!( + has_parsed_assets, + "XCArchive upload should include parsed asset catalogs" + ); + Ok(()) + } } diff --git a/src/utils/mobile_app/apple.rs b/src/utils/mobile_app/apple.rs index e511569900..0fe0db4c73 100644 --- a/src/utils/mobile_app/apple.rs +++ b/src/utils/mobile_app/apple.rs @@ -12,11 +12,13 @@ use std::io::Cursor; use walkdir::WalkDir; use zip::ZipArchive; -pub fn handle_asset_catalogs(path: &Path) { +pub fn handle_asset_catalogs(archive_path: &Path, output_path: &Path) { // Find all asset catalogs - let cars = find_car_files(path); + let cars = find_car_files(archive_path); for car in &cars { - if let Err(e) = apple_catalog_parsing::inspect_asset_catalog(car) { + if let Err(e) = + apple_catalog_parsing::inspect_asset_catalog(car, &output_path.to_path_buf()) + { eprintln!("Failed to inspect asset catalog {}: {e}", car.display()); } } diff --git a/src/utils/mobile_app/mod.rs b/src/utils/mobile_app/mod.rs index a28e756905..2936a783b1 100644 --- a/src/utils/mobile_app/mod.rs +++ b/src/utils/mobile_app/mod.rs @@ -2,10 +2,12 @@ #[cfg(all(target_os = "macos", target_arch = "aarch64"))] mod apple; +mod normalize; mod validation; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] pub use self::apple::{handle_asset_catalogs, ipa_to_xcarchive}; +pub use self::normalize::normalize_directory; pub use self::validation::{is_aab_file, is_apk_file, is_zip_file}; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] pub use self::validation::{is_apple_app, is_ipa_file}; diff --git a/src/utils/mobile_app/normalize.rs b/src/utils/mobile_app/normalize.rs new file mode 100644 index 0000000000..e06be75ed7 --- /dev/null +++ b/src/utils/mobile_app/normalize.rs @@ -0,0 +1,100 @@ +#[cfg(not(windows))] +use std::fs; +use std::fs::File; +use std::io::Write as _; +#[cfg(not(windows))] +use std::os::unix::fs::PermissionsExt as _; +use std::path::{Path, PathBuf}; + +use crate::utils::fs::TempFile; +use anyhow::Result; +use itertools::Itertools as _; +use log::debug; +use symbolic::common::ByteView; +use walkdir::WalkDir; +use zip::write::SimpleFileOptions; +use zip::{DateTime, ZipWriter}; + +fn sort_entries(path: &Path) -> Result> { + Ok(WalkDir::new(path) + .follow_links(true) + .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(); + Ok((entry_path, relative_path)) + }) + .collect::>>()? + .into_iter() + .sorted_by(|(_, a), (_, b)| a.cmp(b))) +} + +fn add_entries_to_zip( + zip: &mut ZipWriter, + entries: impl Iterator, + directory_name: &str, +) -> Result { + let mut file_count = 0; + + // 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 + // but the last modified time being different will cause checksums to be different. + let options = SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Stored) + .last_modified_time(DateTime::default()); + + for (entry_path, relative_path) in entries { + #[cfg(not(windows))] + // On Unix, we need to preserve the file permissions. + let options = options.unix_permissions(fs::metadata(&entry_path)?.permissions().mode()); + + let zip_path = format!("{directory_name}/{}", relative_path.to_string_lossy()); + + zip.start_file(zip_path, options)?; + let file_byteview = ByteView::open(&entry_path)?; + zip.write_all(file_byteview.as_slice())?; + file_count += 1; + } + + Ok(file_count) +} + +// For XCArchive directories, we'll zip the entire directory +pub fn normalize_directory(path: &Path, parsed_assets_path: &Path) -> Result { + debug!("Creating normalized zip for directory: {}", path.display()); + + let temp_file = TempFile::create()?; + let mut zip = ZipWriter::new(temp_file.open()?); + + let directory_name = path.file_name().expect("Failed to get basename"); + + // 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. + let entries = sort_entries(path)?; + let mut file_count = add_entries_to_zip(&mut zip, entries, &directory_name.to_string_lossy())?; + + // Add parsed assets to the zip in a "ParsedAssets" directory + if parsed_assets_path.exists() { + debug!( + "Adding parsed assets from: {}", + parsed_assets_path.display() + ); + + let parsed_assets_entries = sort_entries(parsed_assets_path)?; + file_count += add_entries_to_zip( + &mut zip, + parsed_assets_entries, + &format!("{}/ParsedAssets", directory_name.to_string_lossy()), + )?; + } + + zip.finish()?; + debug!( + "Successfully created normalized zip for directory with {} files", + file_count + ); + Ok(temp_file) +} diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Info.plist b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Info.plist new file mode 100644 index 0000000000..b90b1e0705 --- /dev/null +++ b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Info.plist @@ -0,0 +1,33 @@ + + + + + ApplicationProperties + + ApplicationPath + Applications/DemoApp.app + Architectures + + arm64 + + CFBundleIdentifier + com.emerge.DemoApp + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + SigningIdentity + Apple Development: Noah Martin (5TV9RX846L) + Team + 62J2XHNK9T + + ArchiveVersion + 2 + CreationDate + 2025-07-31T20:04:22Z + Name + DemoApp + SchemeName + DemoApp + + diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon60x60@2x.png b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon60x60@2x.png new file mode 100644 index 0000000000..810237fd0d Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon60x60@2x.png differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon76x76@2x~ipad.png b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon76x76@2x~ipad.png new file mode 100644 index 0000000000..3bc6723681 Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/AppIcon76x76@2x~ipad.png differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Assets.car b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Assets.car new file mode 100644 index 0000000000..86bbc641ac Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Assets.car differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/DemoApp b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/DemoApp new file mode 100755 index 0000000000..8cf2467416 Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/DemoApp differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/Info.plist b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/Info.plist new file mode 100644 index 0000000000..88ca0c85fc Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/Info.plist differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/TestFmwk b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/TestFmwk new file mode 100755 index 0000000000..229e5788f2 Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/TestFmwk differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/_CodeSignature/CodeResources b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000000..6aceabda98 --- /dev/null +++ b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestFmwk.framework/_CodeSignature/CodeResources @@ -0,0 +1,101 @@ + + + + + files + + Info.plist + + r72eSb0zQ3RgQz8r7w0cUEzm9LE= + + + files2 + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/Info.plist b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/Info.plist new file mode 100644 index 0000000000..25aa9ab7ec Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/Info.plist differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/TestingFmwk b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/TestingFmwk new file mode 100755 index 0000000000..1ed874cb9d Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/TestingFmwk differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/_CodeSignature/CodeResources b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000000..3013c9e1db --- /dev/null +++ b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Frameworks/TestingFmwk.framework/_CodeSignature/CodeResources @@ -0,0 +1,101 @@ + + + + + files + + Info.plist + + krUne5gOLxPTIhMzJPiYgTyiPZo= + + + files2 + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Info.plist b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Info.plist new file mode 100644 index 0000000000..5caa0ffde1 Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/Info.plist differ diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/PkgInfo b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/PkgInfo new file mode 100644 index 0000000000..bd04210fb4 --- /dev/null +++ b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/_CodeSignature/CodeResources b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/_CodeSignature/CodeResources new file mode 100644 index 0000000000..cbce49b37e --- /dev/null +++ b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/_CodeSignature/CodeResources @@ -0,0 +1,216 @@ + + + + + files + + AppIcon60x60@2x.png + + vg6yn/s7+V3vFjHub0DoiWmVbbQ= + + AppIcon76x76@2x~ipad.png + + D05hHPWNKB/rLmPmiC7YSXcAP7Y= + + Assets.car + + JV7dm4e4EDz7f1Q7yeO4sDsJ9qo= + + Frameworks/TestFmwk.framework/Info.plist + + r72eSb0zQ3RgQz8r7w0cUEzm9LE= + + Frameworks/TestFmwk.framework/TestFmwk + + bPQhLhhP7nQQH5w8rzuAeDg+xm4= + + Frameworks/TestFmwk.framework/_CodeSignature/CodeResources + + FjLKCkG+ewJkN1o4hr436scGYFI= + + Frameworks/TestingFmwk.framework/Info.plist + + krUne5gOLxPTIhMzJPiYgTyiPZo= + + Frameworks/TestingFmwk.framework/TestingFmwk + + +q80d5G0GJZ46CPHQ0irEkPD6NQ= + + Frameworks/TestingFmwk.framework/_CodeSignature/CodeResources + + INIZ+j9ItJ/ft1wr56qQICe8dfg= + + Info.plist + + s7lt3Xk+DUN677xLlMbVo4WR66k= + + PkgInfo + + n57qDP4tZfLD1rCS43W0B4LQjzE= + + embedded.mobileprovision + + LZRWMwUda4NFSoGSE93lPXwVEL8= + + + files2 + + AppIcon60x60@2x.png + + hash2 + + LTSXWUm+dlKijHcwjOUwN4kMeG0dAiM+xXsyyaAgPNg= + + + AppIcon76x76@2x~ipad.png + + hash2 + + n8Oh0VUDVJ8WV4dX2talYreRUCxrJ4cllnq8j17zSvs= + + + Assets.car + + hash2 + + DETDy1ii8NwQEanY+/93IFfhl95MPEEAKGXfTI2XYTQ= + + + Frameworks/TestFmwk.framework/Info.plist + + hash2 + + i2s07aCbaKyW3uG5wHGZT6CxYIgXs0aefwD43bTI0UQ= + + + Frameworks/TestFmwk.framework/TestFmwk + + hash2 + + uE4cHz+YER1HLKO48HPtKhcO8uzkGEgiyKms8YK7Qds= + + + Frameworks/TestFmwk.framework/_CodeSignature/CodeResources + + hash2 + + /wLpgx6+eyDeDLwP8dF7NqF5tLB28yQAdAanajcimGQ= + + + Frameworks/TestingFmwk.framework/Info.plist + + hash2 + + bPqWdvUeX3l2XXBLGeC/Dq8eH9Fds5IJq9bA7hPVvfg= + + + Frameworks/TestingFmwk.framework/TestingFmwk + + hash2 + + /5pD/Bbn4wd0SEV8U1/OANwsHhMIQKDNbcdOGYp0Vmg= + + + Frameworks/TestingFmwk.framework/_CodeSignature/CodeResources + + hash2 + + Sgtk8zgB3SNOoGW0UcfiWxNMSGWfyKBYwfEfq/eEKwQ= + + + embedded.mobileprovision + + hash2 + + 7PfY1dZJeU44OsyTkDYgMETP+N1xDaG4YloSx0VW/28= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/embedded.mobileprovision b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/embedded.mobileprovision new file mode 100644 index 0000000000..d42866e33e Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/archive.xcarchive/Products/Applications/DemoApp.app/embedded.mobileprovision differ diff --git a/tests/integration/_fixtures/mobile_app/ipa_with_asset.ipa b/tests/integration/_fixtures/mobile_app/ipa_with_asset.ipa new file mode 100644 index 0000000000..c5e3f36d41 Binary files /dev/null and b/tests/integration/_fixtures/mobile_app/ipa_with_asset.ipa differ