Move Weak Framework Loading to FBControlCore

Summary: This will allow Framework loading by XCTBootstrap & Others.

Reviewed By: marekcirkos

Differential Revision: D3092590

fb-gh-sync-id: c69645f21aa0d602e267fb76fc2d1e0cfe0aada7
shipit-source-id: c69645f21aa0d602e267fb76fc2d1e0cfe0aada7
This commit is contained in:
Lawrence Lomax 2016-03-24 03:38:41 -07:00 коммит произвёл Facebook Github Bot 9
Родитель 4839d2f346
Коммит 8887a1ed05
5 изменённых файлов: 168 добавлений и 106 удалений

Просмотреть файл

@ -30,5 +30,6 @@
#import <FBControlCore/FBTaskExecutor+Private.h>
#import <FBControlCore/FBTaskExecutor.h>
#import <FBControlCore/FBTerminationHandle.h>
#import <FBControlCore/FBWeakFrameworkLoader.h>
#import <FBControlCore/NSPredicate+FBControlCore.h>
#import <FBControlCore/NSRunLoop+FBControlCore.h>

Просмотреть файл

@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@protocol FBControlCoreLogger;
/**
A Utility Class for loading weak-linked Frameworks at runtime.
*/
@interface FBWeakFrameworkLoader : NSObject
/**
Loads a Mapping of Private Frameworks.
Will avoid re-loading allready loaded Frameworks.
@param classMapping a mapping of Class Name to Framework Path. The Framework path is resolved relative to the current Developer Directory.
@param logger a logger for logging framework loading activities.
@param error an error out for any error that occurs.
@return YES if successful, NO otherwise.
*/
+ (BOOL)loadPrivateFrameworks:(NSDictionary<NSString *, NSString *> *)classMapping logger:(id<FBControlCoreLogger>)logger error:(NSError **)error;
@end

Просмотреть файл

@ -0,0 +1,115 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "FBWeakFrameworkLoader.h"
#import "FBCollectionInformation.h"
#import "FBControlCoreError.h"
#import "FBControlCoreGlobalConfiguration.h"
#import "FBControlCoreLogger.h"
@implementation FBWeakFrameworkLoader
// A Mapping of Class Names to the Frameworks that they belong to. This serves to:
// 1) Represent the Frameworks that FBControlCore is dependent on via their classes
// 2) Provide a path to the relevant Framework.
// 3) Provide a class for sanity checking the Framework load.
// 4) Provide a class that can be checked before the Framework load to avoid re-loading the same
// Framework if others have done so before.
// 5) Provide a sanity check that any preloaded Private Frameworks match the current xcode-select version
+ (BOOL)loadPrivateFrameworks:(NSDictionary<NSString *, NSString *> *)classMapping logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
static BOOL hasLoaded = NO;
if (hasLoaded) {
return YES;
}
// This will assert if the directory could not be found.
NSString *developerDirectory = FBControlCoreGlobalConfiguration.developerDirectory;
[logger logFormat:@"Using Developer Directory %@", developerDirectory];
for (NSString *className in classMapping) {
NSString *relativePath = classMapping[className];
NSString *path = [[developerDirectory stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
// The Class exists, therefore has been loaded
if (NSClassFromString(className)) {
[logger logFormat:@"%@ is already loaded, skipping load of framework %@", className, path];
NSError *innerError = nil;
if (![self verifyDeveloperDirectoryForPrivateClass:className developerDirectory:developerDirectory logger:logger error:&innerError]) {
return [FBControlCoreError failBoolWithError:innerError errorOut:error];
}
continue;
}
// Otherwise load the Framework.
[logger logFormat:@"%@ is not loaded. Loading %@ at path %@", className, path.lastPathComponent, path];
NSError *innerError = nil;
if (![self loadFrameworkAtPath:path logger:logger error:&innerError]) {
return [FBControlCoreError failBoolWithError:innerError errorOut:error];
}
[logger logFormat:@"Loaded %@ from %@", className, path];
}
// We're done with loading Frameworks.
hasLoaded = YES;
[logger logFormat:@"Loaded All Private Frameworks %@", [FBCollectionInformation oneLineDescriptionFromArray:classMapping.allValues atKeyPath:@"lastPathComponent"]];
return YES;
}
+ (BOOL)loadFrameworkAtPath:(NSString *)path logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
NSBundle *bundle = [NSBundle bundleWithPath:path];
if (!bundle) {
return [[FBControlCoreError
describeFormat:@"Failed to load the bundle for path %@", path]
failBool:error];
}
NSError *innerError = nil;
if (![bundle loadAndReturnError:&innerError]) {
return [[FBControlCoreError
describeFormat:@"Failed to load the the Framework Bundle %@", bundle]
failBool:error];
}
[logger logFormat:@"Successfully loaded %@", path.lastPathComponent];
return YES;
}
/**
Given that it is possible for FBControlCore.framework to be loaded after any of the
Private Frameworks upon which it depends, it's possible that these Frameworks may have
been loaded from a different Developer Directory.
In order to prevent crazy behaviour from arising, FBControlCore will check the
directories of these Frameworks match the one that is currently set.
*/
+ (BOOL)verifyDeveloperDirectoryForPrivateClass:(NSString *)className developerDirectory:(NSString *)developerDirectory logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(className)];
if (!bundle) {
return [[FBControlCoreError
describeFormat:@"Could not obtain Framework bundle for class named %@", className]
failBool:error];
}
// Developer Directory is: /Applications/Xcode.app/Contents/Developer
// The common base path is: is: /Applications/Xcode.app
NSString *basePath = [[developerDirectory stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
if (![bundle.bundlePath hasPrefix:basePath]) {
return [[FBControlCoreError
describeFormat:@"Expected Framework %@ to be loaded for Developer Directory at path %@, but was loaded from %@", bundle.bundlePath.lastPathComponent, bundle.bundlePath, developerDirectory]
failBool:error];
}
[logger logFormat:@"%@ has correct path of %@", className, basePath];
return YES;
}
@end

Просмотреть файл

@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
877123F31BDA797800530B1E /* video0.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 877123F21BDA797800530B1E /* video0.mp4 */; };
AA017F581BD7787300F45E9D /* libShimulator.dylib in Resources */ = {isa = PBXBuildFile; fileRef = AA017F4C1BD7784700F45E9D /* libShimulator.dylib */; };
AA0F6F2A1CA3DCF700926518 /* FBWeakFrameworkLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA0F6F281CA3DCF700926518 /* FBWeakFrameworkLoader.h */; settings = {ATTRIBUTES = (Public, ); }; };
AA0F6F2B1CA3DCF700926518 /* FBWeakFrameworkLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0F6F291CA3DCF700926518 /* FBWeakFrameworkLoader.m */; };
AA111CCE1BBE7C5A0054AFDD /* CoreSimulatorDoubles.m in Sources */ = {isa = PBXBuildFile; fileRef = AA111CCD1BBE7C5A0054AFDD /* CoreSimulatorDoubles.m */; };
AA19DA861C7740BB009BB89B /* FBSimulatorPoolTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = AA19DA851C7740BB009BB89B /* FBSimulatorPoolTestCase.m */; };
AA19DA881C77450A009BB89B /* FBSimulatorPool+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA19DA871C77450A009BB89B /* FBSimulatorPool+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -347,6 +349,8 @@
1DD70E29B6970A5500000000 /* CoreSimulator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = CoreSimulator.framework; path = Library/PrivateFrameworks/CoreSimulator.framework; sourceTree = DEVELOPER_DIR; };
877123F21BDA797800530B1E /* video0.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = video0.mp4; sourceTree = "<group>"; };
AA017F4C1BD7784700F45E9D /* libShimulator.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libShimulator.dylib; sourceTree = "<group>"; };
AA0F6F281CA3DCF700926518 /* FBWeakFrameworkLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBWeakFrameworkLoader.h; sourceTree = "<group>"; };
AA0F6F291CA3DCF700926518 /* FBWeakFrameworkLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBWeakFrameworkLoader.m; sourceTree = "<group>"; };
AA111CCC1BBE7C5A0054AFDD /* CoreSimulatorDoubles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreSimulatorDoubles.h; sourceTree = "<group>"; };
AA111CCD1BBE7C5A0054AFDD /* CoreSimulatorDoubles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreSimulatorDoubles.m; sourceTree = "<group>"; };
AA19DA841C7740BB009BB89B /* FBSimulatorPoolTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSimulatorPoolTestCase.h; sourceTree = "<group>"; };
@ -1531,6 +1535,8 @@
EEBD60581C9062E900298A07 /* FBControlCoreLogger.m */,
AACA33561C96F8D100DC9704 /* FBFileFinder.h */,
AACA33571C96F8D100DC9704 /* FBFileFinder.m */,
AA0F6F281CA3DCF700926518 /* FBWeakFrameworkLoader.h */,
AA0F6F291CA3DCF700926518 /* FBWeakFrameworkLoader.m */,
AA84EFFC1C9FE162000CDA41 /* NSPredicate+FBControlCore.h */,
AA84EFFD1C9FE162000CDA41 /* NSPredicate+FBControlCore.m */,
EEBD60591C9062E900298A07 /* NSRunLoop+FBControlCore.h */,
@ -1666,6 +1672,7 @@
AAEA3AA61C90BB62004F8409 /* FBLogSearch.h in Headers */,
EEBD607C1C9062E900298A07 /* FBConcurrentCollectionOperations.h in Headers */,
EEBD60781C9062E900298A07 /* FBCapacityQueue.h in Headers */,
AA0F6F2A1CA3DCF700926518 /* FBWeakFrameworkLoader.h in Headers */,
EEBD60821C9062E900298A07 /* FBControlCoreLogger.h in Headers */,
EEBD60971C908FA200298A07 /* FBJSONConversion.h in Headers */,
EEBD606F1C9062E900298A07 /* FBTaskExecutor+Convenience.h in Headers */,
@ -2065,6 +2072,7 @@
AA84EFFF1C9FE162000CDA41 /* NSPredicate+FBControlCore.m in Sources */,
EEBD60951C908F8500298A07 /* FBCollectionInformation.m in Sources */,
EEBD60771C9062E900298A07 /* FBBinaryParser.m in Sources */,
AA0F6F2B1CA3DCF700926518 /* FBWeakFrameworkLoader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

Просмотреть файл

@ -63,64 +63,6 @@
#pragma mark Framework Loading
+ (BOOL)loadPrivateFrameworks:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
static BOOL hasLoaded = NO;
if (hasLoaded) {
return YES;
}
// This will assert if the directory could not be found.
NSString *developerDirectory = FBControlCoreGlobalConfiguration.developerDirectory;
// A Mapping of Class Names to the Frameworks that they belong to. This serves to:
// 1) Represent the Frameworks that FBSimulatorControl is dependent on via their classes
// 2) Provide a path to the relevant Framework.
// 3) Provide a class for sanity checking the Framework load.
// 4) Provide a class that can be checked before the Framework load to avoid re-loading the same
// Framework if others have done so before.
// 5) Provide a sanity check that any preloaded Private Frameworks match the current xcode-select version
NSDictionary *classMapping = @{
@"SimDevice" : @"Library/PrivateFrameworks/CoreSimulator.framework",
@"SimDeviceFramebufferService" : @"Library/PrivateFrameworks/SimulatorKit.framework",
@"DVTDevice" : @"../SharedFrameworks/DVTFoundation.framework",
@"DTiPhoneSimulatorApplicationSpecifier" : @"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework"
};
[logger logFormat:@"Using Developer Directory %@", developerDirectory];
for (NSString *className in classMapping) {
NSString *relativePath = classMapping[className];
NSString *path = [[developerDirectory stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
// The Class exists, therefore has been loaded
if (NSClassFromString(className)) {
[logger logFormat:@"%@ is already loaded, skipping load of framework %@", className, path];
NSError *innerError = nil;
if (![self verifyDeveloperDirectoryForPrivateClass:className developerDirectory:developerDirectory logger:logger error:&innerError]) {
return [FBSimulatorError failBoolWithError:innerError errorOut:error];
}
continue;
}
// Otherwise load the Framework.
[logger logFormat:@"%@ is not loaded. Loading %@ at path %@", className, path.lastPathComponent, path];
NSError *innerError = nil;
if (![self loadFrameworkAtPath:path logger:logger error:&innerError]) {
return [FBSimulatorError failBoolWithError:innerError errorOut:error];
}
[logger logFormat:@"Loaded %@ from %@", className, path];
}
// We're done with loading Frameworks.
hasLoaded = YES;
[logger logFormat:@"Loaded All Private Frameworks %@", [FBCollectionInformation oneLineDescriptionFromArray:classMapping.allValues atKeyPath:@"lastPathComponent"]];
// Set CoreSimulator Logging since it is now loaded.
[self setCoreSimulatorLoggingEnabled:FBControlCoreGlobalConfiguration.debugLoggingEnabled];
return YES;
}
+ (void)loadPrivateFrameworksOrAbort
{
id<FBControlCoreLogger> logger = FBControlCoreGlobalConfiguration.defaultLogger;
@ -133,6 +75,20 @@
abort();
}
+ (BOOL)loadPrivateFrameworks:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
NSDictionary *classMapping = @{
@"SimDevice" : @"Library/PrivateFrameworks/CoreSimulator.framework",
@"SimDeviceFramebufferService" : @"Library/PrivateFrameworks/SimulatorKit.framework",
@"DVTDevice" : @"../SharedFrameworks/DVTFoundation.framework",
@"DTiPhoneSimulatorApplicationSpecifier" : @"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework"
};
BOOL result = [FBWeakFrameworkLoader loadPrivateFrameworks:classMapping logger:logger error:error];
// Set CoreSimulator Logging since it is now loaded.
[self setCoreSimulatorLoggingEnabled:FBControlCoreGlobalConfiguration.debugLoggingEnabled];
return result;
}
#pragma mark Private Methods
+ (void)setCoreSimulatorLoggingEnabled:(BOOL)enabled
@ -141,52 +97,4 @@
[simulatorDefaults setBool:enabled forKey:@"DebugLogging"];
}
+ (BOOL)loadFrameworkAtPath:(NSString *)path logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
NSBundle *bundle = [NSBundle bundleWithPath:path];
if (!bundle) {
return [[FBSimulatorError
describeFormat:@"Failed to load the bundle for path %@", path]
failBool:error];
}
NSError *innerError = nil;
if (![bundle loadAndReturnError:&innerError]) {
return [[FBSimulatorError
describeFormat:@"Failed to load the the Framework Bundle %@", bundle]
failBool:error];
}
[logger logFormat:@"Successfully loaded %@", path.lastPathComponent];
return YES;
}
/**
Given that it is possible for FBSimulatorControl.framework to be loaded after any of the
Private Frameworks upon which it depends, it's possible that these Frameworks may have
been loaded from a different Developer Directory.
In order to prevent crazy behaviour from arising, FBSimulatorControl will check the
directories of these Frameworks match the one that is currently set.
*/
+ (BOOL)verifyDeveloperDirectoryForPrivateClass:(NSString *)className developerDirectory:(NSString *)developerDirectory logger:(id<FBControlCoreLogger>)logger error:(NSError **)error
{
NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(className)];
if (!bundle) {
return [[FBSimulatorError
describeFormat:@"Could not obtain Framework bundle for class named %@", className]
failBool:error];
}
// Developer Directory is: /Applications/Xcode.app/Contents/Developer
// The common base path is: is: /Applications/Xcode.app
NSString *basePath = [[developerDirectory stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
if (![bundle.bundlePath hasPrefix:basePath]) {
return [[FBSimulatorError
describeFormat:@"Expected Framework %@ to be loaded for Developer Directory at path %@, but was loaded from %@", bundle.bundlePath.lastPathComponent, bundle.bundlePath, developerDirectory]
failBool:error];
}
[logger logFormat:@"%@ has correct path of %@", className, basePath];
return YES;
}
@end