idb_companion/Utility/FBIDBStorageManager.m (543 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBIDBStorageManager.h"
#import "FBIDBError.h"
#import "FBXCTestDescriptor.h"
#import "FBXCTestRunFileReader.h"
NSString *const IdbTestBundlesFolder = @"idb-test-bundles";
NSString *const IdbApplicationsFolder = @"idb-applications";
NSString *const IdbDylibsFolder = @"idb-dylibs";
NSString *const IdbDsymsFolder = @"idb-dsyms";
NSString *const IdbFrameworksFolder = @"idb-frameworks";
@implementation FBInstalledArtifact
- (instancetype)initWithName:(NSString *)name uuid:(NSUUID *)uuid path:(NSURL *)path
{
self = [super init];
if (!self) {
return nil;
}
_name = name;
_uuid = uuid;
_path = path;
return self;
}
@end
@implementation FBIDBStorage
#pragma mark Initializers
- (instancetype)initWithTarget:(id<FBiOSTarget>)target basePath:(NSURL *)basePath queue:(dispatch_queue_t)queue logger:(id<FBControlCoreLogger>)logger
{
self = [super init];
if (!self) {
return nil;
}
_target = target;
_basePath = basePath;
_queue = queue;
_logger = logger;
return self;
}
#pragma mark Methods
- (BOOL)clean:(NSError **)error
{
for (NSURL *url in [NSFileManager.defaultManager contentsOfDirectoryAtURL:self.basePath includingPropertiesForKeys:nil options:0 error:nil]) {
if (![NSFileManager.defaultManager removeItemAtPath:url.path error:error]) {
return NO;
}
}
return YES;
}
- (id<FBFileContainer>)asFileContainer
{
return [FBFileContainer fileContainerForBasePath:self.basePath.path];
}
#pragma mark Properties
- (NSDictionary<NSString *, NSString *> *)replacementMapping
{
NSMutableDictionary<NSString *, NSString *> *replacementMapping = NSMutableDictionary.dictionary;
for (NSURL *url in [NSFileManager.defaultManager contentsOfDirectoryAtURL:self.basePath includingPropertiesForKeys:nil options:0 error:nil]) {
replacementMapping[url.lastPathComponent] = url.path;
}
return replacementMapping;
}
@end
@implementation FBFileStorage
- (nullable FBInstalledArtifact *)saveFile:(NSURL *)url error:(NSError **)error
{
return [self copyInto:self.basePath from:url error:error];
}
- (nullable FBInstalledArtifact *)saveFileInUniquePath:(NSURL *)url error:(NSError **)error
{
NSURL *baseURL = self.basePath;
baseURL = [baseURL URLByAppendingPathComponent:NSUUID.UUID.UUIDString];
if (![NSFileManager.defaultManager createDirectoryAtURL:baseURL withIntermediateDirectories:YES attributes:nil error:error]) {
return nil;
}
return [self copyInto:baseURL from:url error:error];
}
#pragma mark Private Methods
- (nullable FBInstalledArtifact *)copyInto:(NSURL *)basePath from:(NSURL *)fromURL error:(NSError **)error
{
NSURL *destination = [basePath URLByAppendingPathComponent:fromURL.lastPathComponent];
[self.logger logFormat:@"Persisting %@ to %@", fromURL.lastPathComponent, destination];
if (![NSFileManager.defaultManager copyItemAtURL:fromURL toURL:destination error:error]) {
return nil;
}
[self.logger logFormat:@"Persisted %@", destination.lastPathComponent];
return [[FBInstalledArtifact alloc] initWithName:destination.lastPathComponent uuid:nil path:destination];
}
@end
@implementation FBBundleStorage
#pragma mark Initializers
- (instancetype)initWithTarget:(id<FBiOSTarget>)target basePath:(NSURL *)basePath queue:(dispatch_queue_t)queue logger:(id<FBControlCoreLogger>)logger relocateLibraries:(BOOL)relocateLibraries
{
self = [super initWithTarget:target basePath:basePath queue:queue logger:logger];
if (!self) {
return nil;
}
_relocateLibraries = relocateLibraries;
return self;
}
#pragma mark Public
- (BOOL)checkArchitecture:(FBBundleDescriptor *)bundle error:(NSError **)error
{
NSSet<NSString *> *bundleArchs = bundle.binary.architectures;
NSString *targetArch = self.target.architecture;
const BOOL containsExactArch = [bundleArchs containsObject:targetArch];
// arm64 binaries are acceptable on arm64e devices, but arm64e is not yet available
const BOOL arm64eEquivalent = [targetArch isEqualToString:@"arm64e"] && [bundleArchs containsObject:@"arm64"];
if (!(containsExactArch || arm64eEquivalent)) {
return [[FBIDBError
describeFormat:@"Targets architecture %@ not in the bundles supported architectures: %@", targetArch, bundleArchs.allObjects]
failBool:error];
}
return YES;
}
- (FBFuture<FBInstalledArtifact *> *)saveBundle:(FBBundleDescriptor *)bundle
{
// Check that the bundle matches the architecture of the target.
NSError *error = nil;
if (![self checkArchitecture:bundle error:&error]) {
return [FBFuture futureWithError:error];
}
// Where the bundle will be copied to.
NSURL *storageDirectory = [self.basePath URLByAppendingPathComponent:bundle.identifier];
if (![self prepareDirectoryWithURL:storageDirectory error:&error]) {
return [FBFuture futureWithError:error];
}
// Copy over bundle
NSURL *sourceBundlePath = [NSURL fileURLWithPath:bundle.path];
NSURL *destinationBundlePath = [storageDirectory URLByAppendingPathComponent:sourceBundlePath.lastPathComponent];
[self.logger logFormat:@"Persisting %@ to %@", bundle.identifier, destinationBundlePath];
if (![NSFileManager.defaultManager copyItemAtURL:sourceBundlePath toURL:destinationBundlePath error:&error]) {
return [FBFuture futureWithError:error];
}
[self.logger logFormat:@"Persisted %@", bundle.identifier];
FBInstalledArtifact *artifact = [[FBInstalledArtifact alloc] initWithName:bundle.identifier uuid:bundle.binary.uuid path:destinationBundlePath];
if (!self.relocateLibraries || ![self.target requiresBundlesToBeSigned]) {
return [FBFuture futureWithResult:artifact];
}
bundle = [FBBundleDescriptor bundleFromPath:destinationBundlePath.path error:&error];
if (!bundle) {
return [FBFuture futureWithError:error];
}
FBCodesignProvider *provider = [FBCodesignProvider codeSignCommandWithAdHocIdentityWithLogger:self.logger];
return [[bundle
updatePathsForRelocationWithCodesign:provider logger:self.logger queue:self.queue]
mapReplace:artifact];
}
#pragma mark Properties
- (NSSet<NSString *> *)persistedBundleIDs
{
return [NSSet setWithArray:([NSFileManager.defaultManager contentsOfDirectoryAtPath:self.basePath.path error:nil] ?: @[])];
}
- (NSDictionary<NSString *, FBBundleDescriptor *> *)persistedBundles
{
NSMutableDictionary<NSString *, FBBundleDescriptor *> *mapping = [NSMutableDictionary dictionary];
for (NSURL *directory in [NSFileManager.defaultManager enumeratorAtURL:self.basePath includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:nil]) {
NSString *key = directory.lastPathComponent;
NSError *error = nil;
NSURL *bundlePath = [FBStorageUtils findUniqueFileInDirectory:directory error:nil];
if (!bundlePath) {
continue;
}
FBBundleDescriptor *bundle = [FBBundleDescriptor bundleFromPath:bundlePath.path error:&error];
if (!bundle) {
[self.logger logFormat:@"Failed to get bundle info for bundle at path %@", bundlePath];
}
mapping[key] = bundle;
}
return mapping;
}
- (NSDictionary<NSString *, NSString *> *)replacementMapping
{
NSDictionary<NSString *, FBBundleDescriptor *> *persistedBundles = self.persistedBundles;
NSMutableDictionary<NSString *, NSString *> *mapping = NSMutableDictionary.dictionary;
for (NSString *name in persistedBundles) {
FBBundleDescriptor *bundle = persistedBundles[name];
if (bundle.identifier) {
mapping[bundle.identifier] = bundle.path;
}
if (bundle.binary.uuid) {
mapping[bundle.binary.uuid.UUIDString] = bundle.path;
}
}
return mapping;
}
#pragma mark Private
- (BOOL)prepareDirectoryWithURL:(NSURL *)url error:(NSError **)error
{
// Clear old test
if ([NSFileManager.defaultManager fileExistsAtPath:url.path]) {
if (![NSFileManager.defaultManager removeItemAtURL:url error:error]) {
return NO;
}
}
// Recreate directory
if (![NSFileManager.defaultManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
return NO;
}
return YES;
}
@end
static NSString *const XctestExtension = @"xctest";
static NSString *const XctestRunExtension = @"xctestrun";
@implementation FBXCTestBundleStorage
#pragma mark Public
- (FBFuture<FBInstalledArtifact *> *)saveBundleOrTestRunFromBaseDirectory:(NSURL *)baseDirectory
{
// Find .xctest or .xctestrun in directory.
NSError *error = nil;
NSDictionary<NSString *, NSSet<NSURL *> *> *buckets = [FBStorageUtils bucketFilesWithExtensions:[NSSet setWithArray:@[XctestExtension, XctestRunExtension]] inDirectory:baseDirectory error:&error];
if (!buckets) {
return [FBFuture futureWithError:error];
}
NSArray<NSURL *> *bucket = buckets[XctestExtension].allObjects;
NSURL *xctestBundleURL = bucket.firstObject;
if (bucket.count > 1) {
return [[FBControlCoreError
describeFormat:@"Multiple files with .xctest extension: %@", [FBCollectionInformation oneLineDescriptionFromArray:bucket]]
failFuture];
}
bucket = buckets[XctestRunExtension].allObjects;
NSURL *xctestrunURL = bucket.firstObject;
if (bucket.count > 1) {
return [[FBControlCoreError
describeFormat:@"Multiple files with .xctestrun extension: %@", [FBCollectionInformation oneLineDescriptionFromArray:bucket]]
failFuture];
}
if (!xctestBundleURL && !xctestrunURL) {
return [[FBIDBError
describeFormat:@"Neither a .xctest bundle or .xctestrun file provided: %@", [FBCollectionInformation oneLineDescriptionFromDictionary:buckets]]
failFuture];
}
if (xctestBundleURL) {
return [self saveTestBundle:xctestBundleURL];
}
if (xctestrunURL) {
return [self saveTestRun:xctestrunURL];
}
return [[FBIDBError
describeFormat:@".xctest bundle (%@) or .xctestrun (%@) file was not saved", xctestBundleURL, xctestrunURL]
failFuture];
}
- (FBFuture<FBInstalledArtifact *> *)saveBundleOrTestRun:(NSURL *)filePath
{
// save .xctest or .xctestrun
if ([filePath.pathExtension isEqualToString:XctestExtension]) {
return [self saveTestBundle:filePath];
}
if ([filePath.pathExtension isEqualToString:XctestRunExtension]) {
return [self saveTestRun:filePath];
}
return [[FBControlCoreError
describeFormat:@"The path extension (%@) of the provided bundle (%@) is not .xctest or .xctestrun", filePath.pathExtension, filePath]
failFuture];
}
- (NSSet<id<FBXCTestDescriptor>> *)listTestDescriptorsWithError:(NSError **)error
{
NSMutableSet<id<FBXCTestDescriptor>> *testDescriptors = [[NSMutableSet alloc] init];
// Get xctest bundles
NSSet<NSURL *> *testURLS = [self listTestBundlesWithError:error];
if (!testURLS) {
return nil;
} else if (error) {
*error = nil;
}
// Get xctestrun files
NSSet<NSURL *> *xcTestRunURLS = [self listXCTestRunFilesWithError:error];
if (!xcTestRunURLS) {
return nil;
} else if (error) {
*error = nil;
}
// Get info out of xctest bundles
for (NSURL *testURL in testURLS) {
FBBundleDescriptor *bundle = [FBBundleDescriptor bundleWithFallbackIdentifierFromPath:testURL.path error:error];
if (!bundle) {
if (error) {
[self.logger.error log:(*error).description];
}
continue;
}
id<FBXCTestDescriptor> testDescriptor = [[FBXCTestBootstrapDescriptor alloc] initWithURL:testURL name:bundle.name testBundle:bundle];
[testDescriptors addObject:testDescriptor];
}
// Get info out of xctestrun files
for (NSURL *xcTestRunURL in xcTestRunURLS) {
NSArray<id<FBXCTestDescriptor>> *descriptors = [self getXCTestRunDescriptorsFromURL:xcTestRunURL error:error];
if (!descriptors) {
return nil;
}
[testDescriptors addObjectsFromArray:descriptors];
}
return testDescriptors;
}
- (id<FBXCTestDescriptor>)testDescriptorWithID:(NSString *)bundleId error:(NSError **)error
{
NSSet<id<FBXCTestDescriptor>> *testDescriptors = [self listTestDescriptorsWithError:error];
for (id<FBXCTestDescriptor> testDescriptor in testDescriptors) {
if ([[testDescriptor testBundleID] isEqualToString: bundleId]) {
return testDescriptor;
}
}
return [[FBIDBError describeFormat:@"Couldn't find test with id: %@", bundleId] fail:error];
}
#pragma mark Private
- (NSSet<NSURL *> *)listTestBundlesWithError:(NSError **)error
{
return [self listXCTestContentsWithExtension:XctestExtension error:error];
}
- (NSSet<NSURL *> *)listXCTestRunFilesWithError:(NSError **)error
{
return [self listXCTestContentsWithExtension:XctestRunExtension error:error];
}
- (NSURL *)xctestBundleWithID:(NSString *)bundleID error:(NSError **)error
{
NSURL *directory = [self.basePath URLByAppendingPathComponent:bundleID];
return [FBStorageUtils findFileWithExtension:XctestExtension atURL:directory error:error];
}
- (NSSet<NSURL *> *)listXCTestContentsWithExtension:(NSString *)extention error:(NSError **)error
{
NSArray<NSURL *> *directories = [NSFileManager.defaultManager
contentsOfDirectoryAtURL:self.basePath
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
error:error];
if (!directories) {
return [[FBIDBError describe:@"Error reading test bundle base directory"] fail:error];
}
NSMutableSet<NSURL *> *tests = [NSMutableSet setWithCapacity:directories.count];
for (NSURL *innerDirectory in directories) {
NSURL *bundleURL = [FBStorageUtils findFileWithExtension:extention atURL:innerDirectory error:error];
if (bundleURL) {
[tests addObject:bundleURL];
}
}
return tests;
}
- (id<FBXCTestDescriptor>)testDescriptorWithURL:(NSURL *)url error:(NSError **)error
{
NSSet<id<FBXCTestDescriptor>> *testDescriptors = [self listTestDescriptorsWithError:error];
for (id<FBXCTestDescriptor> testDescriptor in testDescriptors) {
if ([[[testDescriptor url] absoluteString] isEqualToString:[url absoluteString]]) {
return testDescriptor;
}
}
return [[FBIDBError describeFormat:@"Couldn't find test with url: %@", url] fail:error];
}
- (nullable NSArray<id<FBXCTestDescriptor>> *)getXCTestRunDescriptorsFromURL:(NSURL *)xctestrunURL error:(NSError **)error
{
NSDictionary<NSString *, id> *contentDict = [FBXCTestRunFileReader readContentsOf:xctestrunURL expandPlaceholderWithPath:self.target.auxillaryDirectory error:error];
if (!contentDict) {
return nil;
}
NSDictionary<NSString *, NSNumber *> *xctestrunMetadata = contentDict[@"__xctestrun_metadata__"];
// The legacy format of xctestrun file does not contain __xctestrun_metadata__
if (xctestrunMetadata) {
[self.logger.info logFormat:@"Using xctestrun format version: %@", xctestrunMetadata[@"FormatVersion"]];
return [self getDescriptorsFrom:contentDict with:xctestrunURL];
} else {
[self.logger.info log:@"Using the legacy xctestrun file format"];
return [self legacyGetDescriptorsFrom:contentDict with:xctestrunURL];
}
}
// xctestrun for Xcode 11+
- (NSArray<id<FBXCTestDescriptor>> *)getDescriptorsFrom:(NSDictionary<NSString *, NSDictionary *> *)xctestrunContents with:(NSURL *)xctestrunURL
{
NSMutableArray<id<FBXCTestDescriptor>> *descriptors = [[NSMutableArray alloc] init];
for (NSString *field in xctestrunContents) {
[self.logger.info logFormat:@"Checking the %@ field to extract test descriptors", field];
if ([field isEqualToString:@"__xctestrun_metadata__"] || [field isEqualToString:@"CodeCoverageBuildableInfos"]) {
continue;
}
id<FBXCTestDescriptor> descriptor = [self getDescriptorFor:field from:xctestrunContents with:xctestrunURL];
if (descriptor != nil) {
[descriptors addObject:descriptor];
}
}
return descriptors;
}
// xctestrun before Xcode 11
- (NSArray<id<FBXCTestDescriptor>> *)legacyGetDescriptorsFrom:(NSDictionary<NSString *, NSDictionary *> *)xctestrunContents with:(NSURL *)xctestrunURL
{
NSMutableArray<id<FBXCTestDescriptor>> *descriptors = [[NSMutableArray alloc] init];
for (NSString *testTarget in xctestrunContents) {
id<FBXCTestDescriptor> descriptor = [self getDescriptorFor:testTarget from:xctestrunContents with:xctestrunURL];
if (descriptor != nil) {
[descriptors addObject:descriptor];
}
}
return descriptors;
}
- (nullable id<FBXCTestDescriptor>)getDescriptorFor:(NSString *)testTarget from:(NSDictionary<NSString *, NSDictionary *> *)xctestrunContents with:(NSURL *)xctestrunURL
{
NSError *error;
NSDictionary<NSString *, id> *testTargetProperties = [xctestrunContents objectForKey:testTarget];
NSNumber *useArtifacts = testTargetProperties[@"UseDestinationArtifacts"];
if ([useArtifacts isKindOfClass:[NSNumber class]] && [useArtifacts boolValue]) {
NSString *hostIdentifier = testTargetProperties[@"TestHostBundleIdentifier"];
NSString *testIdentifier = testTargetProperties[@"FB_TestBundleIdentifier"];
if (!hostIdentifier) {
[self.logger.error log:@"Using UseDestinationArtifacts requires TestHostBundleIdentifier"];
return nil;
}
if (!testIdentifier) {
[self.logger.error log:@"Using UseDestinationArtifacts requires FB_TestBundleIdentifier"];
return nil;
}
FBBundleDescriptor *testBundle = [[FBBundleDescriptor alloc] initWithName:testIdentifier identifier:testIdentifier path:@"" binary:nil];
FBBundleDescriptor *hostBundle = [[FBBundleDescriptor alloc] initWithName:hostIdentifier identifier:hostIdentifier path:@"" binary:nil];
return [[FBXCodebuildTestRunDescriptor alloc] initWithURL:xctestrunURL name:testTarget testBundle:testBundle testHostBundle:hostBundle];
}
NSString *testHostPath = [testTargetProperties objectForKey:@"TestHostPath"];
NSString *testBundlePath = [testTargetProperties objectForKey:@"TestBundlePath"];
// Get the bundles for test host and test app
FBBundleDescriptor *testHostBundle = [FBBundleDescriptor bundleFromPath:testHostPath error:&error];
if (!testHostBundle) {
[self.logger.error log:error.description];
return nil;
}
FBBundleDescriptor *testBundle = [FBBundleDescriptor bundleFromPath:testBundlePath error:&error];
if (!testBundle) {
[self.logger.error log:error.description];
return nil;
}
return [[FBXCodebuildTestRunDescriptor alloc] initWithURL:xctestrunURL name:testTarget testBundle:testBundle testHostBundle:testHostBundle];
}
- (FBFuture<FBInstalledArtifact *> *)saveTestBundle:(NSURL *)testBundleURL
{
// Test Bundles don't always have a bundle id, so fallback to another name if it's not there.
NSError *error = nil;
FBBundleDescriptor *bundle = [FBBundleDescriptor bundleWithFallbackIdentifierFromPath:testBundleURL.path error:&error];
if (!bundle) {
return [FBFuture futureWithError:error];
}
return [self saveBundle:bundle];
}
- (FBFuture<FBInstalledArtifact *> *)saveTestRun:(NSURL *)XCTestRunURL
{
NSError *error = nil;
// Delete old xctestrun with the same id if it exists
NSArray<id<FBXCTestDescriptor>> *descriptors = [self getXCTestRunDescriptorsFromURL:XCTestRunURL error:&error];
if (!descriptors) {
return [FBFuture futureWithError:error];
}
if (descriptors.count != 1) {
return [[FBIDBError
describeFormat:@"Expected exactly one test in the xctestrun file, got: %lu", descriptors.count]
failFuture];
}
id<FBXCTestDescriptor> descriptor = descriptors[0];
id<FBXCTestDescriptor> toDelete = [self testDescriptorWithID:descriptor.testBundleID error:&error];
if (toDelete) {
if (![NSFileManager.defaultManager removeItemAtURL:[toDelete.url URLByDeletingLastPathComponent] error:&error]) {
return [FBFuture futureWithError:error];
}
}
NSString *uuidString = [[NSUUID UUID] UUIDString];
NSURL *newPath = [self.basePath URLByAppendingPathComponent:uuidString];
if (![self prepareDirectoryWithURL:newPath error:&error]) {
return [FBFuture futureWithError:error];
}
// Get the directory containing the xctestrun file and its contents
NSURL *dir = [XCTestRunURL URLByDeletingLastPathComponent];
NSArray<NSURL *> *contents = [NSFileManager.defaultManager
contentsOfDirectoryAtURL:dir
includingPropertiesForKeys:nil
options:0
error:&error];
if (!contents) {
return [FBFuture futureWithError:error];
}
// Copy all files
for (NSURL *url in contents) {
if (![NSFileManager.defaultManager copyItemAtURL:url toURL:[newPath URLByAppendingPathComponent:url.lastPathComponent] error:&error]) {
return [FBFuture futureWithError:error];
}
}
FBInstalledArtifact *artifact = [[FBInstalledArtifact alloc] initWithName:[descriptor testBundleID] uuid:nil path:dir];
return [FBFuture futureWithResult:artifact];
}
@end
@implementation FBIDBStorageManager
#pragma mark Initializers
+ (NSURL *)prepareStoragePathWithName:(NSString *)name target:(id<FBiOSTarget>)target error:(NSError **)error
{
NSError *innerError = nil;
NSURL *xctestBasePath = [[NSURL fileURLWithPath:target.auxillaryDirectory] URLByAppendingPathComponent:name];
if (![NSFileManager.defaultManager createDirectoryAtURL:xctestBasePath withIntermediateDirectories:YES attributes:nil error:&innerError]) {
return [[[FBIDBError
describeFormat:@"Failed to create xctest storage location %@", xctestBasePath]
causedBy:innerError]
fail:error];
}
return xctestBasePath;
}
+ (nullable instancetype)managerForTarget:(id<FBiOSTarget>)target logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.bundle_storage", DISPATCH_QUEUE_SERIAL);
NSURL *basePath = [self prepareStoragePathWithName:IdbTestBundlesFolder target:target error:error];
if (!basePath) {
return nil;
}
FBXCTestBundleStorage *xctest = [[FBXCTestBundleStorage alloc] initWithTarget:target basePath:basePath queue:queue logger:logger relocateLibraries:YES];
basePath = [self prepareStoragePathWithName:IdbApplicationsFolder target:target error:error];
if (!basePath) {
return nil;
}
FBBundleStorage *application = [[FBBundleStorage alloc] initWithTarget:target basePath:basePath queue:queue logger:logger relocateLibraries:NO];
basePath = [self prepareStoragePathWithName:IdbDylibsFolder target:target error:error];
if (!basePath) {
return nil;
}
FBFileStorage *dylib = [[FBFileStorage alloc] initWithTarget:target basePath:basePath queue:queue logger:logger];
basePath = [self prepareStoragePathWithName:IdbDsymsFolder target:target error:error];
if (!basePath) {
return nil;
}
FBFileStorage *dsym = [[FBFileStorage alloc] initWithTarget:target basePath:basePath queue:queue logger:logger];
basePath = [self prepareStoragePathWithName:IdbFrameworksFolder target:target error:error];
if (!basePath) {
return nil;
}
FBBundleStorage *framework = [[FBBundleStorage alloc] initWithTarget:target basePath:basePath queue:queue logger:logger relocateLibraries:YES];
return [[self alloc] initWithXctest:xctest application:application dylib:dylib dsym:dsym framework:framework logger:logger];
}
- (instancetype)initWithXctest:(FBXCTestBundleStorage *)xctest application:(FBBundleStorage *)application dylib:(FBFileStorage *)dylib dsym:(FBFileStorage *)dsym framework:(FBBundleStorage *)framework logger:(id<FBControlCoreLogger>)logger
{
self = [super init];
if (!self) {
return nil;
}
_xctest = xctest;
_application = application;
_dylib = dylib;
_dsym = dsym;
_framework = framework;
_logger = logger;
return self;
}
#pragma mark Public Methods
- (BOOL)clean:(NSError **)error
{
return [self.xctest clean:error] && [self.application clean:error] && [self.dylib clean:error] && [self.dsym clean:error] && [self.framework clean:error];
}
- (NSArray<NSString *> *)interpolateArgumentReplacements:(NSArray<NSString *> *)arguments
{
[self.logger logFormat:@"Original arguments: %@", arguments];
NSDictionary<NSString *, NSString *> *nameToPath = [self replacementMapping];
[self.logger logFormat:@"Existing replacement mapping: %@", nameToPath];
NSMutableArray<NSString *> *interpolatedArguments = [NSMutableArray arrayWithArray:arguments];
[arguments enumerateObjectsUsingBlock:^(NSString *argument, NSUInteger idx, BOOL *stop) {
[interpolatedArguments replaceObjectAtIndex:idx withObject:nameToPath[argument] ?: argument];
}];
[self.logger logFormat:@"Interpolated arguments: %@", interpolatedArguments];
return interpolatedArguments;
}
- (NSDictionary<NSString *, NSString *> *)replacementMapping
{
NSMutableDictionary<NSString *, NSString *> *combined = NSMutableDictionary.dictionary;
for (NSDictionary<NSString *, NSString *> *replacementMapping in @[self.application.replacementMapping, self.dylib.replacementMapping, self.framework.replacementMapping, self.dsym.replacementMapping]) {
[combined addEntriesFromDictionary:replacementMapping];
}
return combined;
}
@end