├── BuildSettingExtractor.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── BuildSettingExtractor.xcscheme ├── BuildSettingExtractor ├── AppConstants+Categories.h ├── AppConstants+Categories.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── BuildSettingExtractorIcon-1024.png │ │ ├── BuildSettingExtractorIcon-128.png │ │ ├── BuildSettingExtractorIcon-16.png │ │ ├── BuildSettingExtractorIcon-256.png │ │ ├── BuildSettingExtractorIcon-32.png │ │ ├── BuildSettingExtractorIcon-512.png │ │ ├── BuildSettingExtractorIcon-64.png │ │ └── Contents.json │ ├── Contents.json │ ├── config_file.imageset │ │ ├── Contents.json │ │ ├── config_file.png │ │ └── config_file@2x.png │ ├── contents_icon.imageset │ │ ├── Contents.json │ │ ├── contents_icon.png │ │ └── contents_icon@2x.png │ ├── dragViewBackgroundColor.colorset │ │ └── Contents.json │ ├── dragViewTextColor.colorset │ │ └── Contents.json │ └── folder.imageset │ │ ├── Contents.json │ │ ├── folder.png │ │ └── folder@2x.png ├── Base.lproj │ └── MainMenu.xib ├── BuildSettingCommentGenerator.h ├── BuildSettingCommentGenerator.m ├── BuildSettingExtractor-Info.plist ├── BuildSettingExtractor.entitlements ├── BuildSettingExtractor.h ├── BuildSettingExtractor.m ├── BuildSettingInfoSource.h ├── BuildSettingInfoSource.m ├── BuildSettingInfoSubpaths.plist ├── BuildSettingInfoSubpathsNotes.md ├── Constants+Categories.h ├── Constants+Categories.m ├── DragFileView.h ├── DragFileView.m ├── Preferences │ ├── Base.lproj │ │ └── Preferences.storyboard │ ├── ContentsPreferencesViewController.h │ ├── ContentsPreferencesViewController.m │ ├── FileLayoutPreferencesViewController.h │ ├── FileLayoutPreferencesViewController.m │ ├── PreferencesViewController.h │ ├── PreferencesViewController.m │ ├── SampleFileStructureGenerator.h │ └── SampleFileStructureGenerator.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── BuildSettingExtractorTests ├── BuildSettingExtractor.xctestplan ├── BuildSettingExtractorTests-Info.plist ├── BuildSettingExtractorTests.m ├── TestFiles │ ├── BadProject.xcodeproj.test │ │ └── project.pbxproj │ ├── BadVersionNumber.xcodeproj.test │ │ └── project.pbxproj │ ├── ConflictingName.xcodeproj.test │ │ └── project.pbxproj │ ├── EmptySettings.xcodeproj.test │ │ └── project.pbxproj │ ├── FormattingTestData.plist │ └── ThreeBuildConfigs.plist └── en.lproj │ └── InfoPlist.strings ├── Config ├── AppConfig.xcconfig └── ProjectConfig.xcconfig ├── ReadMe.md └── ci_scripts └── ci_post_clone.sh /BuildSettingExtractor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BuildSettingExtractor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BuildSettingExtractor.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BuildSettingExtractor.xcodeproj/xcshareddata/xcschemes/BuildSettingExtractor.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 39 | 45 | 46 | 47 | 48 | 49 | 59 | 61 | 67 | 68 | 69 | 70 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /BuildSettingExtractor/AppConstants+Categories.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppConstants+Categories.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 9/30/19. 6 | // Copyright © 2019 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #pragma mark User Default Keys 12 | 13 | extern NSString *const TPSOpenDirectoryInFinder; 14 | extern NSString *const TPSIncludeBuildSettingInfoComments; 15 | extern NSString *const TPSOutputFileNameProject; 16 | extern NSString *const TPSOutputFileNameShared; 17 | extern NSString *const TPSOutputFileNameSeparator; 18 | extern NSString *const TPSLinesBetweenBuildSettings; 19 | extern NSString *const TPSLinesBetweenBuildSettingsWithInfo; 20 | extern NSString *const TPSTargetFoldersEnabled; 21 | extern NSString *const TPSProjectFolderEnabled; 22 | extern NSString *const TPSDestinationFolderName; 23 | extern NSString *const TPSAutosaveInProjectFolder; 24 | extern NSString *const TPSAlignBuildSettingValues; 25 | 26 | @interface NSUserDefaults (TPS_DefaultsRegistration) 27 | - (void)tps_registerApplicationDefaults; 28 | @end 29 | 30 | #pragma mark - 31 | 32 | @interface NSPasteboard (TPS_XcodeProjectURLAdditions) 33 | - (BOOL)tps_canReadXcodeProjectFileURL; 34 | - (NSURL *)tps_readXcodeProjectFileURL; 35 | @end 36 | 37 | -------------------------------------------------------------------------------- /BuildSettingExtractor/AppConstants+Categories.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppConstants+Categories.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 9/30/19. 6 | // Copyright © 2019 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "AppConstants+Categories.h" 10 | #import "Constants+Categories.h" 11 | #import "BuildSettingExtractor.h" 12 | 13 | #pragma mark User Default Keys 14 | 15 | NSString *const TPSOpenDirectoryInFinder = @"TPSOpenDirectoryInFinder"; 16 | NSString *const TPSIncludeBuildSettingInfoComments = @"TPSIncludeBuildSettingInfoComments"; 17 | NSString *const TPSOutputFileNameProject = @"TPSOutputFileNameProject"; 18 | NSString *const TPSOutputFileNameShared = @"TPSOutputFileNameShared"; 19 | NSString *const TPSOutputFileNameSeparator = @"TPSOutputFileNameSeparator"; 20 | NSString *const TPSLinesBetweenBuildSettings = @"TPSLinesBetweenBuildSettings"; 21 | NSString *const TPSLinesBetweenBuildSettingsWithInfo = @"TPSLinesBetweenBuildSettingsWithInfo"; 22 | NSString *const TPSTargetFoldersEnabled = @"TPSTargetFoldersEnabled"; 23 | NSString *const TPSProjectFolderEnabled = @"TPSProjectFolderEnabled"; 24 | NSString *const TPSDestinationFolderName = @"TPSDestinationFolderName"; 25 | NSString *const TPSAutosaveInProjectFolder = @"TPSAutosaveInProjectFolder"; 26 | NSString *const TPSAlignBuildSettingValues = @"TPSAlignBuildSettingValues"; 27 | 28 | @implementation NSUserDefaults (TPS_DefaultsRegistration) 29 | - (void)tps_registerApplicationDefaults { 30 | NSDictionary *defaults = @{ 31 | TPSOpenDirectoryInFinder : @(YES), 32 | TPSIncludeBuildSettingInfoComments : @(YES), 33 | TPSOutputFileNameShared : BuildSettingExtractor.defaultSharedConfigName, 34 | TPSOutputFileNameProject : BuildSettingExtractor.defaultProjectConfigName, 35 | TPSOutputFileNameSeparator : BuildSettingExtractor.defaultNameSeparator, 36 | TPSLinesBetweenBuildSettings : @0, 37 | TPSLinesBetweenBuildSettingsWithInfo : @3, 38 | TPSTargetFoldersEnabled : @(NO), 39 | TPSProjectFolderEnabled : @(NO), 40 | TPSDestinationFolderName : BuildSettingExtractor.defaultDestinationFolderName, 41 | TPSAutosaveInProjectFolder: @(NO), 42 | TPSAlignBuildSettingValues: @(NO) 43 | }; 44 | [self registerDefaults:defaults]; 45 | } 46 | @end 47 | 48 | #pragma mark - 49 | 50 | @implementation NSPasteboard (TPS_XcodeProjectURLAdditions) 51 | 52 | - (NSURL *)tps_readXcodeProjectFileURL { 53 | NSArray *readObjects = [self readObjectsForClasses:@[[NSURL class]] options:[self tps_xcodeProjectReadingOptions]]; 54 | return readObjects.firstObject; 55 | } 56 | 57 | - (BOOL)tps_canReadXcodeProjectFileURL { 58 | return [self canReadObjectForClasses:@[[NSURL class]] options:[self tps_xcodeProjectReadingOptions]]; 59 | } 60 | 61 | - (NSDictionary *)tps_xcodeProjectReadingOptions { 62 | return @{NSPasteboardURLReadingFileURLsOnlyKey: @(YES), NSPasteboardURLReadingContentsConformToTypesKey: @[[NSString tps_projectBundleTypeIdentifier]]}; 63 | } 64 | 65 | @end 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /BuildSettingExtractor/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 9/9/14. 6 | // Copyright (c) 2014 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | @interface AppDelegate : NSObject 12 | @end 13 | -------------------------------------------------------------------------------- /BuildSettingExtractor/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 9/9/14. 6 | // Copyright (c) 2014 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "DragFileView.h" 11 | #import "BuildSettingExtractor.h" 12 | #import "AppConstants+Categories.h" 13 | #import "Constants+Categories.h" 14 | 15 | // During development it is useful to turn off the overwrite checking 16 | #define OVERWRITE_CHECKING_DISABLED 0 17 | 18 | @interface AppDelegate () 19 | 20 | @property (weak) IBOutlet NSWindow *window; 21 | @property (weak) IBOutlet DragFileView *dragFileView; 22 | @property NSWindowController *preferencesWindowController; 23 | @property (weak) IBOutlet NSTextField *dragFileLabel; 24 | 25 | @end 26 | 27 | @implementation AppDelegate 28 | 29 | + (void)initialize { 30 | EmptyStringTransformer *transformer = [[EmptyStringTransformer alloc] init]; 31 | [NSValueTransformer setValueTransformer:transformer forName:@"EmptyStringTransformer"]; 32 | } 33 | 34 | - (void)awakeFromNib { 35 | self.dragFileView.target = self; 36 | self.dragFileView.action = @selector(handleDroppedFile:); 37 | if (@available(macOS 10.13, *)) { 38 | self.dragFileLabel.textColor = [NSColor colorNamed:@"dragViewTextColor"]; 39 | } else { 40 | self.dragFileLabel.textColor = [NSColor colorWithCalibratedRed:0.13 green:0.26 blue:0.42 alpha:1.0]; 41 | } 42 | } 43 | 44 | 45 | - (IBAction)chooseXcodeProject:(id)sender { 46 | 47 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 48 | openPanel.canCreateDirectories = NO; 49 | openPanel.allowsMultipleSelection = NO; 50 | openPanel.canChooseDirectories = NO; 51 | openPanel.canChooseFiles = YES; 52 | openPanel.allowedFileTypes = @[[NSString tps_projectBundleTypeIdentifier]]; 53 | openPanel.message = @"Choose an Xcode project to extract its build settings."; 54 | openPanel.prompt = @"Choose"; 55 | 56 | [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { 57 | if (result == NSModalResponseOK) { 58 | NSURL *projectURL = openPanel.URL; 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | [self processXcodeProjectAtURL:projectURL]; 61 | }); 62 | } 63 | }]; 64 | } 65 | 66 | - (IBAction)handleDroppedFile:(DragFileView *)sender { 67 | NSURL *fileURL = sender.fileURL; 68 | [self processXcodeProjectAtURL:fileURL]; 69 | } 70 | 71 | - (void)processXcodeProjectAtURL:(NSURL *)fileURL { 72 | NSString *typeIdentifier = nil; 73 | NSError *error = nil; 74 | [fileURL getResourceValue:&typeIdentifier forKey:NSURLTypeIdentifierKey error:&error]; 75 | 76 | if (fileURL && [typeIdentifier isEqualToString:[NSString tps_projectBundleTypeIdentifier]]) { 77 | if ([[NSUserDefaults standardUserDefaults] boolForKey:TPSAutosaveInProjectFolder]) { 78 | NSURL *baseURL = [fileURL URLByDeletingLastPathComponent]; 79 | NSURL *destinationURL = [self createValidatedDestinationURLForBaseURL:baseURL error:&error]; 80 | if (!destinationURL) { 81 | NSAlert *alert = [NSAlert alertWithError:error]; 82 | [alert beginSheetModalForWindow:self.window completionHandler:nil]; 83 | return; 84 | } 85 | [self performExtractionFromProject:fileURL toDestination:destinationURL]; 86 | } else { 87 | [self selectDestinationURLForSourceProject:fileURL]; 88 | } 89 | } 90 | } 91 | 92 | - (void)selectDestinationURLForSourceProject:(NSURL *)fileURL { 93 | 94 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 95 | openPanel.canCreateDirectories = YES; 96 | openPanel.allowsMultipleSelection = YES; 97 | openPanel.canChooseDirectories = YES; 98 | openPanel.canChooseFiles = NO; 99 | openPanel.allowedFileTypes = @[(NSString *)kUTTypeFolder]; 100 | openPanel.message = [NSString stringWithFormat:@"Choose location to save configuration files.\nConfiguration files for project ‘%@’\nwill be saved in a folder named '%@'.", [fileURL lastPathComponent], [[NSUserDefaults standardUserDefaults] stringForKey:TPSDestinationFolderName]]; 101 | openPanel.prompt = @"Choose"; 102 | 103 | [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { 104 | 105 | if (result == NSModalResponseOK) { 106 | NSError *error = nil; 107 | NSURL *baseURL = openPanel.URL; 108 | NSURL *destinationURL = [self createValidatedDestinationURLForBaseURL:baseURL error: &error]; 109 | if (!destinationURL) { 110 | NSAlert *alert = [NSAlert alertWithError:error]; 111 | [alert beginSheetModalForWindow:self.window completionHandler:nil]; 112 | return; 113 | } 114 | 115 | BOOL validDestination = [BuildSettingExtractor validateDestinationFolder:destinationURL error:&error]; 116 | 117 | if (validDestination || OVERWRITE_CHECKING_DISABLED) { 118 | [self performExtractionFromProject:fileURL toDestination:destinationURL]; 119 | } 120 | else { 121 | NSAlert *alert = [NSAlert alertWithError:error]; 122 | [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { 123 | if (returnCode == NSAlertSecondButtonReturn) { 124 | [self performExtractionFromProject:fileURL toDestination:destinationURL]; 125 | } 126 | }]; 127 | } 128 | } 129 | }]; 130 | } 131 | 132 | - (nullable NSURL *)createValidatedDestinationURLForBaseURL:(NSURL *) baseURL error: (NSError **)error { 133 | NSString *folderName = [[NSUserDefaults standardUserDefaults] stringForKey:TPSDestinationFolderName]; 134 | // First check if there is a folder of that name in the base url 135 | NSArray *fileURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:baseURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:nil]; 136 | NSMutableIndexSet *untakenIndexes = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(1, 9999)]; 137 | BOOL foundNameConflict = NO; 138 | for (NSURL *fileURL in fileURLs) { 139 | NSString *filename = [fileURL lastPathComponent]; 140 | if ([folderName isEqualToString:filename]) { 141 | foundNameConflict = YES; 142 | } 143 | if ([filename hasPrefix:folderName]) { 144 | NSArray *components = [filename componentsSeparatedByString:@"-"]; 145 | if (components.count > 1) { 146 | NSUInteger value = (NSUInteger)[[components lastObject] integerValue]; 147 | if (value != 0) { 148 | [untakenIndexes removeIndex:value]; 149 | } 150 | } 151 | } 152 | } 153 | NSString *validatedFolderName = nil; 154 | if (foundNameConflict) { 155 | validatedFolderName = [NSString stringWithFormat:@"%@-%ld", folderName, untakenIndexes.firstIndex]; 156 | } else { 157 | validatedFolderName = folderName; 158 | } 159 | 160 | // Then create destination folder 161 | NSURL *validatedDestinationURL = [baseURL URLByAppendingPathComponent:validatedFolderName]; 162 | if ([[NSFileManager defaultManager] createDirectoryAtURL:validatedDestinationURL withIntermediateDirectories:NO attributes:nil error:error]) { 163 | return validatedDestinationURL; 164 | } else { 165 | return nil; 166 | } 167 | } 168 | 169 | - (void)performExtractionFromProject:(NSURL *)fileURL toDestination:(NSURL *)destinationURL { 170 | 171 | // Perform the extraction in the background. 172 | // Using DISPATCH_QUEUE_PRIORITY_HIGH which is available on 10.9 173 | // Move to QOS_CLASS_USER_INITIATED when 10.10 is the deployment target 174 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 175 | BuildSettingExtractor *buildSettingExtractor = [[BuildSettingExtractor alloc] init]; 176 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 177 | buildSettingExtractor.sharedConfigName = [defaults stringForKey:TPSOutputFileNameShared]; 178 | buildSettingExtractor.projectConfigName = [defaults stringForKey:TPSOutputFileNameProject]; 179 | buildSettingExtractor.nameSeparator = [defaults stringForKey:TPSOutputFileNameSeparator]; 180 | BOOL includeInfo = [defaults boolForKey:TPSIncludeBuildSettingInfoComments]; 181 | buildSettingExtractor.includeBuildSettingInfoComments = includeInfo; 182 | buildSettingExtractor.linesBetweenSettings = [defaults integerForKey:includeInfo ? TPSLinesBetweenBuildSettingsWithInfo : TPSLinesBetweenBuildSettings]; 183 | buildSettingExtractor.targetFoldersEnabled = [defaults boolForKey:TPSTargetFoldersEnabled]; 184 | buildSettingExtractor.projectFolderEnabled = [defaults boolForKey:TPSProjectFolderEnabled]; 185 | buildSettingExtractor.alignBuildSettingValues = [defaults boolForKey:TPSAlignBuildSettingValues]; 186 | 187 | NSError *fatalError = nil; 188 | 189 | // Extract the build settings 190 | NSArray *nonFatalErrors = [buildSettingExtractor extractBuildSettingsFromProject:fileURL error:&fatalError]; 191 | 192 | // On extraction fatal error, present the error and return. 193 | if (!nonFatalErrors && fatalError) { 194 | // present error on main thread and return 195 | dispatch_async(dispatch_get_main_queue(), ^{ 196 | [NSApp presentError:fatalError]; 197 | }); 198 | return; // Can't continue, fatal error. 199 | 200 | } 201 | // Otherwise, present non-fatal errors, if present. 202 | else if (nonFatalErrors && nonFatalErrors.count > 0) { 203 | // present non-fatal errors on main thread 204 | dispatch_async(dispatch_get_main_queue(), ^{ 205 | for (NSError *anError in nonFatalErrors) { 206 | [NSApp presentError:anError]; // Will present one at a time. 207 | } 208 | }); 209 | } 210 | 211 | // Write the config files 212 | BOOL success = [buildSettingExtractor writeConfigFilesToDestinationFolder: destinationURL error: &fatalError]; 213 | if (!success && fatalError) { 214 | // present error on main thread and return 215 | dispatch_async(dispatch_get_main_queue(), ^{ 216 | [NSApp presentError:fatalError]; 217 | }); 218 | return; // Can't continue, fatal error. 219 | } 220 | 221 | BOOL openInFinder = [[NSUserDefaults standardUserDefaults] boolForKey:TPSOpenDirectoryInFinder]; 222 | if (success && openInFinder) { 223 | [[NSWorkspace sharedWorkspace] openURL:destinationURL]; 224 | } 225 | }); 226 | } 227 | 228 | - (IBAction)presentPreferencesWindow:(id)sender { 229 | if (!self.preferencesWindowController) { 230 | NSStoryboard *storyboard = [NSStoryboard storyboardWithName:@"Preferences" bundle:nil]; 231 | self.preferencesWindowController = (NSWindowController *)[storyboard instantiateInitialController]; 232 | } 233 | [self.preferencesWindowController showWindow:nil]; 234 | } 235 | 236 | 237 | #pragma mark - NSApplicationDelegate 238 | 239 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { 240 | return YES; 241 | } 242 | 243 | - (void)applicationWillFinishLaunching:(NSNotification *)notification { 244 | [[NSUserDefaults standardUserDefaults] tps_registerApplicationDefaults]; 245 | } 246 | 247 | - (void)application:(NSApplication *)application openURLs:(NSArray *)urls { 248 | NSURL *fileURL = urls.firstObject; 249 | if (fileURL) { [self processXcodeProjectAtURL:fileURL]; } 250 | } 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-1024.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-128.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-16.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-256.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-32.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-512.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/BuildSettingExtractorIcon-64.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "BuildSettingExtractorIcon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "BuildSettingExtractorIcon-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "BuildSettingExtractorIcon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "BuildSettingExtractorIcon-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "BuildSettingExtractorIcon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "BuildSettingExtractorIcon-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "BuildSettingExtractorIcon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "BuildSettingExtractorIcon-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "BuildSettingExtractorIcon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "BuildSettingExtractorIcon-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/config_file.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "config_file.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "config_file@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/config_file.imageset/config_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/config_file.imageset/config_file.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/config_file.imageset/config_file@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/config_file.imageset/config_file@2x.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/contents_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "contents_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "contents_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/contents_icon.imageset/contents_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/contents_icon.imageset/contents_icon.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/contents_icon.imageset/contents_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/contents_icon.imageset/contents_icon@2x.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/dragViewBackgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "mac", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.700", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "0.850" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "mac", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.130", 31 | "alpha" : "1.000", 32 | "blue" : "0.420", 33 | "green" : "0.260" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/dragViewTextColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "mac", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.130", 13 | "alpha" : "1.000", 14 | "blue" : "0.420", 15 | "green" : "0.260" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "mac", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.700", 31 | "alpha" : "1.000", 32 | "blue" : "1.000", 33 | "green" : "0.850" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/folder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "folder.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "folder@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/folder.imageset/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/folder.imageset/folder.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Assets.xcassets/folder.imageset/folder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dempseyatgithub/BuildSettingExtractor/bd95f3d147bd5289068ba4048925f18220b85a5d/BuildSettingExtractor/Assets.xcassets/folder.imageset/folder@2x.png -------------------------------------------------------------------------------- /BuildSettingExtractor/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingCommentGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingCommentGenerator.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/3/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | @class BuildSettingInfoSource; 12 | 13 | @interface BuildSettingCommentGenerator : NSObject 14 | 15 | - (instancetype)initWithBuildSettingInfoSource:(BuildSettingInfoSource *)infoSource; 16 | 17 | - (NSString *)commentForBuildSettingWithName:(NSString *)buildSettingName; 18 | 19 | - (BOOL)loadBuildSettingInfo:(NSError **)error; // In public interface for testing 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingCommentGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingCommentGenerator.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/3/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | #define MAX_LINE_LENGTH 87 // For 90 columns, minus three (comment slashes and a space) 10 | 11 | #import "BuildSettingCommentGenerator.h" 12 | #import "BuildSettingInfoSource.h" 13 | #import "Constants+Categories.h" 14 | 15 | @interface BuildSettingCommentGenerator () 16 | 17 | @property (strong, nonatomic) BuildSettingInfoSource *infoSource; 18 | 19 | @property (strong, nonatomic) NSDictionary *buildSettingInfoDictionary; 20 | 21 | // Some build setting info uses the Xcode flavor of Markdown for option lists. 22 | // In Xcode, these show up as bulletted lists with option names emphasized. 23 | // In plain text the emphasis does not look as good. 24 | // This regex matches the common pattern "* *OptionName:*" and replaces it with "* OptionName:" 25 | @property (strong, nonatomic) NSRegularExpression *regex; 26 | 27 | @end 28 | 29 | 30 | @implementation BuildSettingCommentGenerator 31 | 32 | - (instancetype)init 33 | { 34 | [NSException raise:NSInternalInconsistencyException format:@"You must use -initWithBuildSettingInfoSource: to initialize instances of BuildSettingCommentGenerator."]; 35 | return nil; 36 | } 37 | 38 | - (instancetype)initWithBuildSettingInfoSource:(BuildSettingInfoSource *)infoSource 39 | { 40 | self = [super init]; 41 | if (self) { 42 | self.regex = [NSRegularExpression regularExpressionWithPattern:@"^\\*\\h\\*(.+?)\\*" options:NSRegularExpressionAnchorsMatchLines error:nil]; 43 | self.infoSource = infoSource; 44 | } 45 | return self; 46 | } 47 | 48 | - (NSString *)commentForBuildSettingWithName:(NSString *)buildSettingName { 49 | NSMutableString *comment = [[NSMutableString alloc] init]; 50 | 51 | NSString *presentationName = [self presentationNameForKey:buildSettingName]; 52 | NSString *settingDescription = [self descriptionForKey:buildSettingName]; 53 | 54 | if (presentationName || settingDescription) { 55 | 56 | if (settingDescription) { 57 | settingDescription = [self processedDescriptionString:settingDescription forKey:buildSettingName]; 58 | } 59 | if (presentationName) { 60 | [comment appendFormat:@"// %@\n", presentationName]; 61 | } 62 | if (settingDescription) { 63 | [comment appendFormat:@"// %@\n", settingDescription]; 64 | } 65 | [comment appendString:@"\n"]; 66 | } 67 | 68 | return comment; 69 | } 70 | 71 | - (NSString *)presentationNameForKey:(NSString *)key { 72 | if (!self.buildSettingInfoDictionary) { 73 | [self loadBuildSettingInfo:nil]; 74 | } 75 | 76 | NSString *processedKey = [key tps_baseBuildSettingName]; // strip any conditional part of build setting 77 | 78 | processedKey = [self displayNameKeyForSetting:processedKey]; 79 | return self.buildSettingInfoDictionary[processedKey]; 80 | } 81 | 82 | - (NSString *)descriptionForKey:(NSString *)key { 83 | if (!self.buildSettingInfoDictionary) { 84 | [self loadBuildSettingInfo:nil]; 85 | } 86 | 87 | NSString *processedKey = [key tps_baseBuildSettingName]; // strip any conditional part of build setting 88 | 89 | processedKey = [self descriptionKeyForSetting:processedKey]; 90 | return self.buildSettingInfoDictionary[processedKey]; 91 | } 92 | 93 | - (NSString *)processedCurrentLine:(NSString *)currentLine { 94 | 95 | // Trim whitespace 96 | currentLine = [currentLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 97 | 98 | // Process Markdown option list item 99 | if ([currentLine hasPrefix:@"*"]) { 100 | currentLine = [self.regex stringByReplacingMatchesInString:currentLine options:0 range:NSMakeRange(0, [currentLine length]) withTemplate:@"* $1"]; 101 | } 102 | 103 | // Add newline and comment prefix 104 | currentLine = [NSString stringWithFormat:@"\n// %@", currentLine]; 105 | 106 | return currentLine; 107 | } 108 | 109 | - (NSString *)processedDescriptionString:(NSString *)string forKey:(NSString *)key { 110 | 111 | NSString *processedString = @""; 112 | 113 | // Take the repetition of the build setting name out of the description. 114 | NSString *baseBuildSettingName = [key tps_baseBuildSettingName]; 115 | NSString *buildNameString = [NSString stringWithFormat:@"[%@]", baseBuildSettingName]; 116 | string = [string stringByReplacingOccurrencesOfString:buildNameString withString:@""]; 117 | 118 | buildNameString = [NSString stringWithFormat:@"[%@, ", baseBuildSettingName]; 119 | string = [string stringByReplacingOccurrencesOfString:buildNameString withString:@"["]; 120 | string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 121 | 122 | for (NSString *paragraphString in [string componentsSeparatedByString:@"\n"]) { 123 | 124 | 125 | // Yes, this is not using grapheme clusters and assumes one char is one character. 126 | // It also assumes spaces separate words, which is not true in some languages. 127 | NSInteger characterCount = 0; 128 | NSString *currentLine = @""; 129 | 130 | for (NSString *wordish in [paragraphString componentsSeparatedByString:@" "]) { 131 | characterCount += wordish.length; 132 | if (characterCount < MAX_LINE_LENGTH) { 133 | currentLine = [currentLine stringByAppendingFormat:@"%@ ", wordish]; 134 | characterCount++; // Account for the space 135 | } else { 136 | // Process current line 137 | currentLine = [self processedCurrentLine:currentLine]; 138 | // Add to processed string 139 | processedString = [processedString stringByAppendingString:currentLine]; 140 | // reset line count and set current line to token that didn't fit. 141 | characterCount = wordish.length + 1; 142 | currentLine = [NSString stringWithFormat:@"%@ ", wordish]; 143 | } 144 | } 145 | 146 | // Append the most recent line if there is any 147 | if (![currentLine isEqualToString:@""]) { 148 | // Process current line 149 | currentLine = [self processedCurrentLine:currentLine]; 150 | // Add to processed string 151 | processedString = [processedString stringByAppendingString:currentLine]; 152 | } 153 | 154 | } 155 | 156 | return processedString; 157 | } 158 | 159 | // Returns an array of error strings describing unread build setting info files 160 | - (BOOL)loadBuildSettingInfo:(NSError **)error { 161 | 162 | NSMutableArray *subpathReadingErrorStrings = [[NSMutableArray alloc] init]; 163 | 164 | NSString *defaultXcodePath = self.infoSource.resolvedURL.path; 165 | NSInteger xcodeVersion = self.infoSource.resolvedVersion; 166 | 167 | // Load subpaths 168 | NSURL *buildSettingInfoPlistURL = [[NSBundle mainBundle] URLForResource:@"BuildSettingInfoSubpaths" withExtension:@"plist"]; 169 | NSDictionary *buildSettingInfoDict = [NSDictionary dictionaryWithContentsOfURL:buildSettingInfoPlistURL]; 170 | NSArray *buildSettingInfoSubpaths = buildSettingInfoDict[@"subpaths"]; 171 | 172 | NSMutableDictionary *infoStringFile = [NSMutableDictionary new]; 173 | 174 | // A spot to put additional setting info. If a more official version is read in, it replaces the backstop info. 175 | NSDictionary *backstopSettingsInfo = buildSettingInfoDict[@"backstopSettingInfo"]; 176 | [infoStringFile addEntriesFromDictionary:backstopSettingsInfo]; 177 | 178 | // Add deprecated subpaths that are valid in the version of Xcode we are using 179 | NSMutableArray *subpathsToAdd = nil; 180 | NSDictionary *deprecatedSubpaths = buildSettingInfoDict[@"deprecatedSubpathsByVersion"]; 181 | for (NSString *versionKey in [deprecatedSubpaths allKeys]) { 182 | NSInteger maxVersion = [versionKey integerValue]; 183 | if (xcodeVersion < maxVersion) { 184 | if (!subpathsToAdd) { 185 | subpathsToAdd = [NSMutableArray array]; 186 | } 187 | [subpathsToAdd addObjectsFromArray:deprecatedSubpaths[versionKey]]; 188 | } 189 | } 190 | if (subpathsToAdd) { 191 | buildSettingInfoSubpaths = [buildSettingInfoSubpaths arrayByAddingObjectsFromArray:subpathsToAdd]; 192 | } 193 | 194 | // Add introduced subpaths that are valid in the version of Xcode we are using 195 | subpathsToAdd = nil; 196 | NSDictionary *introducedSubpaths = buildSettingInfoDict[@"introducedSubpathsByVersion"]; 197 | for (NSString *versionKey in [introducedSubpaths allKeys]) { 198 | NSInteger minVersion = [versionKey integerValue]; 199 | if (xcodeVersion >= minVersion) { 200 | if (!subpathsToAdd) { 201 | subpathsToAdd = [NSMutableArray array]; 202 | } 203 | [subpathsToAdd addObjectsFromArray:introducedSubpaths[versionKey]]; 204 | } 205 | } 206 | if (subpathsToAdd) { 207 | buildSettingInfoSubpaths = [buildSettingInfoSubpaths arrayByAddingObjectsFromArray:subpathsToAdd]; 208 | } 209 | 210 | 211 | // Rather than track exactly what Xcode versions contain which files, group versions of an expected file in an array. 212 | // Log if no file in the group can be read in. 213 | for (NSArray *buildSettingInfoSubpathList in buildSettingInfoSubpaths) { 214 | BOOL foundOne = NO; 215 | for (NSString *subpath in buildSettingInfoSubpathList) { 216 | NSString *pathExtension = [subpath pathExtension]; 217 | NSString *fullpath = [defaultXcodePath stringByAppendingPathComponent:subpath]; 218 | NSDictionary *dictionary = nil; 219 | if ([pathExtension isEqualToString:@"strings"]) { 220 | dictionary = [NSDictionary dictionaryWithContentsOfFile:fullpath]; 221 | } else if ([pathExtension isEqualToString:@"xcspec"]) { 222 | dictionary = [self xcspecFileBuildSettingInfoForPath:fullpath]; 223 | } 224 | if (dictionary) { 225 | // Remove empty string values that may be masking backstop values 226 | NSDictionary *processedDictionary = [dictionary tps_dictionaryByRemovingEmptyStringValues]; 227 | [infoStringFile addEntriesFromDictionary:processedDictionary]; 228 | } 229 | if (dictionary || foundOne) { 230 | foundOne = YES; 231 | } 232 | } 233 | if (!foundOne) { 234 | NSString *errorString = nil; 235 | if (buildSettingInfoSubpathList.count == 0) { 236 | errorString = [NSString stringWithFormat: @"Empty array of subpaths at index %lu", [buildSettingInfoSubpaths indexOfObject:buildSettingInfoSubpathList]]; 237 | } else if (buildSettingInfoSubpathList.count == 1) { 238 | errorString = [NSString stringWithFormat:@"Could not read settings strings at path: %@", buildSettingInfoSubpathList[0]]; 239 | } else { 240 | errorString = [NSString stringWithFormat:@"Could not read settings strings at these paths: %@", buildSettingInfoSubpathList]; 241 | } 242 | [subpathReadingErrorStrings addObject:errorString]; 243 | } 244 | } 245 | 246 | _buildSettingInfoDictionary = infoStringFile; 247 | 248 | BOOL success = subpathReadingErrorStrings.count == 0; 249 | 250 | if (!success && error) { 251 | *error = [NSError errorForSettingInfoFilesNotFound:subpathReadingErrorStrings]; 252 | } 253 | 254 | return success; 255 | } 256 | 257 | - (NSDictionary *)xcspecFileBuildSettingInfoForPath:(NSString *)path { 258 | NSURL *fileURL = [NSURL fileURLWithPath:path]; 259 | NSMutableDictionary *buildSettingInfo = [NSMutableDictionary dictionary]; 260 | NSError *error = nil; 261 | NSData *plistData = [[NSData alloc] initWithContentsOfURL:fileURL]; 262 | // If we can't read the file, behave like +dictionaryWithContentsOfFile: and return nil 263 | if (!plistData) { 264 | return nil; 265 | } 266 | id plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:nil error:&error]; 267 | if ([plist isKindOfClass:[NSArray class]]) { 268 | NSArray *specs = (NSArray *)plist; 269 | for (id spec in specs) { 270 | if ([spec isKindOfClass:[NSDictionary class]]) { 271 | NSDictionary *specDict = (NSDictionary *)spec; 272 | NSString *specType = specDict[@"Type"]; 273 | if ([specType isEqualToString:@"BuildSystem"] || ([specType isEqualToString:@"Compiler"])) { 274 | 275 | id options = specDict[@"Options"]; 276 | if (!options) options = specDict[@"Properties"]; 277 | 278 | if ([options isKindOfClass:[NSArray class]]) { 279 | for (id option in options) { 280 | if ([option isKindOfClass:[NSDictionary class]]) { 281 | NSString *name = option[@"Name"]; 282 | NSString *desc = option[@"Description"]; 283 | NSString *displayName = option[@"DisplayName"]; 284 | 285 | if (name && desc && displayName) { 286 | [buildSettingInfo setValue:displayName forKey:[self displayNameKeyForSetting:name]]; 287 | [buildSettingInfo setValue:desc forKey:[self descriptionKeyForSetting:name]]; 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | } 295 | } 296 | } 297 | 298 | return buildSettingInfo; 299 | } 300 | 301 | - (NSString *)displayNameKeyForSetting:(NSString *)settingName { 302 | return [NSString stringWithFormat:@"[%@]-name", settingName]; 303 | } 304 | 305 | - (NSString *)descriptionKeyForSetting:(NSString *)settingName { 306 | return [NSString stringWithFormat:@"[%@]-description", settingName]; 307 | } 308 | 309 | @end 310 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingExtractor-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | xcodeproj 13 | 14 | CFBundleTypeRole 15 | Editor 16 | LSItemContentTypes 17 | 18 | com.apple.xcode.project 19 | 20 | 21 | 22 | CFBundleExecutable 23 | ${EXECUTABLE_NAME} 24 | CFBundleIconFile 25 | 26 | CFBundleIdentifier 27 | $(PRODUCT_BUNDLE_IDENTIFIER) 28 | CFBundleInfoDictionaryVersion 29 | 6.0 30 | CFBundleName 31 | ${PRODUCT_NAME} 32 | CFBundlePackageType 33 | APPL 34 | CFBundleShortVersionString 35 | $(MARKETING_VERSION) 36 | CFBundleSignature 37 | ???? 38 | CFBundleVersion 39 | $(CURRENT_PROJECT_VERSION) 40 | LSApplicationCategoryType 41 | public.app-category.developer-tools 42 | LSMinimumSystemVersion 43 | ${MACOSX_DEPLOYMENT_TARGET} 44 | NSHumanReadableCopyright 45 | Copyright © 2014-2025 Tapas Software. All rights reserved. 46 | NSMainNibFile 47 | MainMenu 48 | NSPrincipalClass 49 | NSApplication 50 | 51 | 52 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingExtractor.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingExtractor.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingExtractor.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface BuildSettingExtractor : NSObject 14 | 15 | /* The generated file names take the form '.xcconfig' 16 | 17 | For the piece: 18 | Target build setting files use the name of the target. 19 | Project build setting files use the value of -projectConfigName. 20 | 21 | For the piece: 22 | Files containing the settings unique to a Build Configuration use the name of the build configuration. 23 | Files containing settings common to all build configurations for a target use the value of -sharedConfigName. 24 | 25 | For the piece: 26 | The separator used is the value of -nameSeparator. 27 | */ 28 | 29 | // The default values for naming of generated files. Potentially useful to register as defaults in an app. 30 | + (NSString *)defaultSharedConfigName; // "Shared" is the default. 31 | + (NSString *)defaultProjectConfigName; // "Project" is the default. 32 | + (NSString *)defaultNameSeparator; // "-" (hyphen) is the default. 33 | + (NSString *)defaultDestinationFolderName; // "xcconfig" is the default. 34 | 35 | 36 | // The name that will be used to name common / shared config files. 37 | @property (copy) NSString *sharedConfigName; 38 | 39 | // The name that will be used to name the project configuration. 40 | @property (copy) NSString *projectConfigName; 41 | 42 | // The string that will separate filename components. 43 | @property (copy) NSString *nameSeparator; 44 | 45 | // Number of lines between build settings. Default is 0. 46 | @property NSInteger linesBetweenSettings; 47 | 48 | 49 | // Should each build setting be commented with title and description, if available. Default is NO. 50 | @property (assign) BOOL includeBuildSettingInfoComments; 51 | 52 | // If set to YES the generated config files for each target will be put in a separate folder. 53 | // Use with the projectFolderEnabled property for project-level config files. Default is NO. 54 | @property BOOL targetFoldersEnabled; 55 | 56 | // If set to YES project-level config files will be put in a separate folder. 57 | // Only takes effect when targetFoldersEnabled is also set to YES. Default is NO. 58 | @property BOOL projectFolderEnabled; 59 | 60 | // If set to YES aligns the build settings in generated files. Default is NO. 61 | @property BOOL alignBuildSettingValues; 62 | 63 | // Validates destination folder, checking for existing build config files 64 | // Returns an error suitable for presentation in an alert with options to cancel or replace existing files. 65 | + (BOOL)validateDestinationFolder:(NSURL *)destinationURL error:(NSError **)error; 66 | 67 | // Extracts the build settings from the project. 68 | // Returns an array of zero or more non-fatal validation errors or nil if a fatal error is encountered 69 | - (nullable NSArray *)extractBuildSettingsFromProject:(NSURL *)projectWrapperURL error:(NSError **)error; 70 | 71 | // After successful extraction, writes config files to the destination folder 72 | // Returns whether the method was successful and an error if unsuccessful 73 | // 74 | // NOTE: This method will throw an exception if it is called before successful extraction of build settings 75 | // by successful completion of -extractBuildSettingsFromProject:error:. Callers must call -extractBuildSettingsFromProject:error: first and check for a non-nil return value indicating success. 76 | - (BOOL)writeConfigFilesToDestinationFolder:(NSURL *)destinationURL error:(NSError **)error; 77 | 78 | // Generates an example build setting string using the provided settings dictionary and options. The settings dictionary keys are expected 79 | // to be string. The values are expected to be strings or arrays of strings. 80 | + (NSString *)exampleBuildFormattingStringForSettings:(NSDictionary *)settings includeBuildSettingInfoComments:(BOOL)includeBuildSettingInfoComments alignBuildSettingValues:(BOOL)alignBuildSettingValues linesBetweenSettings:(NSInteger)linesBetweenSettings; 81 | 82 | @end 83 | 84 | NS_ASSUME_NONNULL_END 85 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingExtractor.m: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingExtractor.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "BuildSettingExtractor.h" 10 | #import "BuildSettingCommentGenerator.h" 11 | #import "BuildSettingInfoSource.h" 12 | #import "Constants+Categories.h" 13 | 14 | static NSSet *XcodeCompatibilityVersionStringSet(void) { 15 | static NSSet *_compatibilityVersionStringSet; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | _compatibilityVersionStringSet = [NSSet setWithObjects:@"Xcode 3.2", @"Xcode 6.3", @"Xcode 8.0", @"Xcode 9.3", @"Xcode 10.0", @"Xcode 11.0", @"Xcode 11.4", @"Xcode 12.0", @"Xcode 13.0", @"Xcode 14.0", @"Xcode 15.0", @"Xcode 15.3", @"Xcode 16.0", nil]; 19 | }); 20 | return _compatibilityVersionStringSet; 21 | } 22 | 23 | static NSSet *XcodeObjectVersionStringSet(void) { 24 | static NSSet *_objectVersionStringSet; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | _objectVersionStringSet = [NSSet setWithObjects:@"77", @"70", nil]; 28 | }); 29 | return _objectVersionStringSet; 30 | } 31 | 32 | @interface BuildSettingExtractor () 33 | @property (strong) NSMutableDictionary *buildSettingsByTarget; 34 | @property (strong) NSDictionary *objects; 35 | @property (nullable) NSString *validatedProjectConfigName; 36 | @property BOOL extractionSuccessful; 37 | 38 | @property (strong) BuildSettingCommentGenerator *buildSettingCommentGenerator; 39 | @end 40 | 41 | @implementation BuildSettingExtractor 42 | 43 | + (NSString *)defaultSharedConfigName { 44 | return @"Shared"; 45 | } 46 | 47 | + (NSString *)defaultProjectConfigName { 48 | return @"Project"; 49 | } 50 | 51 | + (NSString *)defaultNameSeparator { 52 | return @"-"; 53 | } 54 | 55 | + (NSString *)defaultDestinationFolderName { 56 | return @"xcconfig"; 57 | } 58 | 59 | - (instancetype)init { 60 | self = [super init]; 61 | if (self) { 62 | _sharedConfigName = [[self class] defaultSharedConfigName]; 63 | _projectConfigName = [[self class] defaultProjectConfigName]; 64 | _nameSeparator = [[self class] defaultNameSeparator]; 65 | _buildSettingsByTarget = [[NSMutableDictionary alloc] init]; 66 | _buildSettingCommentGenerator = nil; 67 | _validatedProjectConfigName = nil; 68 | _extractionSuccessful = NO; 69 | } 70 | return self; 71 | } 72 | 73 | /* Given a dictionary and key whose value is an array of object identifiers, return the identified objects in an array */ 74 | - (NSArray *)objectArrayForDictionary:(NSDictionary *)dict key:(NSString *)key { 75 | NSArray *identifiers = dict[key]; 76 | NSMutableArray *objectArray = [[NSMutableArray alloc] init]; 77 | for (NSString *identifier in identifiers) { 78 | id obj = self.objects[identifier]; 79 | [objectArray addObject:obj]; 80 | } 81 | return objectArray; 82 | } 83 | 84 | 85 | + (BOOL)validateDestinationFolder:(NSURL *)destinationURL error:(NSError **)error { 86 | 87 | NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:destinationURL includingPropertiesForKeys:@[NSURLTypeIdentifierKey] options:NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants errorHandler:nil]; 88 | 89 | BOOL foundBuildConfigFile = NO; 90 | 91 | NSURL *currentURL = nil; 92 | while (currentURL = [enumerator nextObject]) { 93 | // Only check three levels deep, in case user chooses something like the home folder 94 | if (enumerator.level > 3) { 95 | [enumerator skipDescendants]; 96 | continue; 97 | } 98 | NSString *typeIdentifier = nil; 99 | NSError *resourceError = nil; 100 | [currentURL getResourceValue:&typeIdentifier forKey:NSURLTypeIdentifierKey error:&resourceError]; 101 | if ([typeIdentifier isEqualToString:[NSString tps_buildConfigurationFileTypeIdentifier]]) { 102 | foundBuildConfigFile = YES; 103 | break; 104 | } 105 | } 106 | 107 | BOOL isValid = !foundBuildConfigFile; 108 | 109 | if (foundBuildConfigFile) { 110 | if (error) { 111 | *error = [NSError errorForDestinationContainsBuildConfigFiles]; 112 | } 113 | } 114 | 115 | return isValid; 116 | } 117 | 118 | 119 | - (NSArray *)extractBuildSettingsFromProject:(NSURL *)projectWrapperURL error:(NSError **)error { 120 | 121 | NSMutableArray *nonFatalErrors = [[NSMutableArray alloc] init]; 122 | 123 | [self.buildSettingsByTarget removeAllObjects]; 124 | 125 | if (self.includeBuildSettingInfoComments) { 126 | 127 | NSError *infoSourceError = nil; 128 | BuildSettingInfoSource *infoSource = [BuildSettingInfoSource resolvedBuildSettingInfoSourceWithStyle:BuildSettingInfoSourceStyleStandard customURL:nil error:&infoSourceError]; 129 | 130 | if (infoSource) { 131 | self.buildSettingCommentGenerator = [[BuildSettingCommentGenerator alloc] initWithBuildSettingInfoSource:infoSource]; 132 | } else { 133 | // If no info source, fallback to basic formatting 134 | self.includeBuildSettingInfoComments = NO; 135 | self.linesBetweenSettings = 0; 136 | } 137 | 138 | if (infoSourceError) { 139 | [nonFatalErrors addObject:infoSourceError]; 140 | } 141 | } 142 | 143 | NSURL *projectFileURL = [projectWrapperURL URLByAppendingPathComponent:@"project.pbxproj"]; 144 | 145 | NSData *fileData = [NSData dataWithContentsOfURL:projectFileURL options:0 error:error]; 146 | if (!fileData) { 147 | return nil; 148 | } 149 | 150 | NSDictionary *projectPlist = [NSPropertyListSerialization propertyListWithData:fileData options:NSPropertyListImmutable format:NULL error:error]; 151 | if (!projectPlist) { 152 | return nil; 153 | } 154 | 155 | // Get object version 156 | NSString *objectVersion = projectPlist[@"objectVersion"]; 157 | 158 | // Get root object (project) 159 | self.objects = projectPlist[@"objects"]; 160 | NSDictionary *rootObject = self.objects[projectPlist[@"rootObject"]]; 161 | 162 | // Check compatibility version 163 | NSString *compatibilityVersion = rootObject[@"compatibilityVersion"]; 164 | 165 | if (![XcodeCompatibilityVersionStringSet() containsObject:compatibilityVersion] && ![XcodeObjectVersionStringSet() containsObject:objectVersion]){ 166 | if (error) { 167 | *error = [NSError errorForUnsupportedProjectURL:projectWrapperURL fileVersion:compatibilityVersion]; 168 | } 169 | return nil; 170 | } 171 | 172 | // Get project targets 173 | NSArray *targets = [self objectArrayForDictionary:rootObject key:@"targets"]; 174 | 175 | // Validate project config name to guard against name conflicts with target names 176 | NSError *nameValdationError = nil; 177 | NSArray *targetNames = [targets valueForKeyPath:@"name"]; 178 | self.validatedProjectConfigName = [self validatedProjectConfigNameWithTargetNames:targetNames error: &nameValdationError]; 179 | 180 | if (nameValdationError) { 181 | [nonFatalErrors addObject:nameValdationError]; 182 | } 183 | 184 | // Get project settings 185 | NSString *buildConfigurationListID = rootObject[@"buildConfigurationList"]; 186 | NSDictionary *projectSettings = [self buildSettingStringsByConfigurationForBuildConfigurationListID:buildConfigurationListID]; 187 | 188 | self.buildSettingsByTarget[self.validatedProjectConfigName] = projectSettings; 189 | 190 | // Begin check that the project file has some settings 191 | BOOL projectFileHasSettings = projectSettings.tps_containsBuildSettings; 192 | 193 | // Add project targets 194 | for (NSDictionary *target in targets) { 195 | NSString *targetName = target[@"name"]; 196 | buildConfigurationListID = target[@"buildConfigurationList"]; 197 | NSDictionary *targetSettings = [self buildSettingStringsByConfigurationForBuildConfigurationListID:buildConfigurationListID]; 198 | if (!projectFileHasSettings) { projectFileHasSettings = targetSettings.tps_containsBuildSettings; } 199 | 200 | self.buildSettingsByTarget[targetName] = targetSettings; 201 | 202 | } 203 | 204 | if (!projectFileHasSettings) { 205 | if (error) { 206 | *error = [NSError errorForNoSettingsFoundInProject: [projectWrapperURL lastPathComponent]]; 207 | } 208 | return nil; 209 | } 210 | 211 | 212 | self.extractionSuccessful = YES; 213 | return nonFatalErrors; 214 | } 215 | 216 | // This will return a validated project config name to guard against naming conflicts with targets 217 | // If a conflict is found, the project config files will have "-Project-Settings" appended to the 218 | // provided project config name, using the user-specified name separator between words. 219 | - (NSString *)validatedProjectConfigNameWithTargetNames:(NSArray *)targetNames error:(NSError **)error { 220 | NSString *validatedProjectConfigName = self.projectConfigName; 221 | if ([targetNames containsObject:self.projectConfigName]) { 222 | validatedProjectConfigName = [validatedProjectConfigName stringByAppendingFormat:@"%@Project%@Settings", self.nameSeparator, self.nameSeparator]; 223 | if (error) { 224 | *error = [NSError errorForNameConflictWithName:self.projectConfigName validatedName:validatedProjectConfigName]; 225 | } 226 | } 227 | return validatedProjectConfigName; 228 | } 229 | 230 | /* Writes an xcconfig file for each target / configuration combination to the specified directory. 231 | */ 232 | - (BOOL)writeConfigFilesToDestinationFolder:(NSURL *)destinationURL error:(NSError **)error { 233 | 234 | // It is a programming error to try to write before extracting or to call this method after extracting build settings has failed. 235 | if (!self.extractionSuccessful) { 236 | [NSException raise:NSInternalInconsistencyException format:@"The method -writeConfigFilesToDestinationFolder:error: was called before successful completion of -extractBuildSettingsFromProject:error:. Callers of this method must call -extractBuildSettingsFromProject:error: first and check for a non-nil return value indicating success."]; 237 | } 238 | 239 | __block BOOL success = YES; 240 | __block NSError *blockError = nil; 241 | 242 | [self.buildSettingsByTarget enumerateKeysAndObjectsUsingBlock:^(id targetName, id obj, BOOL *stop) { 243 | [obj enumerateKeysAndObjectsUsingBlock:^(id configName, id settings, BOOL *stop) { 244 | 245 | NSString *filename = [self configFilenameWithTargetName:targetName configName:configName]; 246 | 247 | NSString *configFileString = @""; 248 | 249 | // Add header comment 250 | NSString *headerComment = [self headerCommentForFilename:filename]; 251 | if (headerComment) { 252 | configFileString = [configFileString stringByAppendingString:headerComment]; 253 | } 254 | 255 | // If the config name is not the shared config, we need to import the shared config 256 | if (![configName isEqualToString:self.sharedConfigName]) { 257 | NSString *configFilename = [self configFilenameWithTargetName:targetName configName:self.sharedConfigName]; 258 | NSString *includeDirective = [NSString stringWithFormat:@"\n\n#include \"%@\"", configFilename]; 259 | configFileString = [configFileString stringByAppendingString:includeDirective]; 260 | } 261 | 262 | // If there are no settings at all, add a comment that the lack of settings is on purpose 263 | if ([settings isEqualToString:@""]) { 264 | settings = [settings stringByAppendingString:@"\n\n"]; 265 | settings = [settings stringByAppendingString:@"//********************************************//\n"]; 266 | settings = [settings stringByAppendingString:@"//* Currently no build settings in this file *//\n"]; 267 | settings = [settings stringByAppendingString:@"//********************************************//"]; 268 | 269 | ; 270 | } else { 271 | // Add a few lines before first setting 272 | configFileString = [configFileString stringByAppendingString:@"\n\n"]; 273 | } 274 | 275 | configFileString = [configFileString stringByAppendingString:settings]; 276 | 277 | // Trim whitespace and newlines 278 | configFileString = [configFileString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 279 | 280 | // Add a newline at end of file 281 | configFileString = [configFileString stringByAppendingString:@"\n"]; 282 | 283 | BOOL isProcessingProjectConfigFiles = [targetName isEqualToString:self.validatedProjectConfigName]; 284 | BOOL currentTargetCanUseFolder = isProcessingProjectConfigFiles ? self.projectFolderEnabled : YES; 285 | 286 | NSURL *fileURL = nil; 287 | if (self.targetFoldersEnabled && currentTargetCanUseFolder) { 288 | fileURL = [destinationURL URLByAppendingPathComponent:targetName]; 289 | success = [[NSFileManager defaultManager] createDirectoryAtURL:fileURL withIntermediateDirectories:YES attributes:nil error:&blockError]; 290 | fileURL = [fileURL URLByAppendingPathComponent:filename]; 291 | } else { 292 | fileURL = [destinationURL URLByAppendingPathComponent:filename]; 293 | } 294 | 295 | // Don't write to file if directory creation failed 296 | if (success) { 297 | success = [configFileString writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:&blockError]; 298 | } 299 | // If we failed to write a configuration, stop iterating through this target's configurations 300 | if (!success) { *stop = YES; } 301 | }]; 302 | 303 | // If we failed writing a target's configurations, stop iterating through targets 304 | if (!success) { *stop = YES; } 305 | }]; 306 | 307 | if (!success && error) { 308 | *error = blockError; 309 | } 310 | 311 | return success; 312 | } 313 | 314 | // Given the target name and config name returns the xcconfig filename to be used. 315 | - (NSString *)configFilenameWithTargetName:(NSString *)targetName configName:(NSString *)configName { 316 | 317 | // Use empty separator if there is no config name 318 | NSString *separator = configName.length ? self.nameSeparator: @""; 319 | 320 | NSString *filename = [NSString stringWithFormat:@"%@%@%@.xcconfig", targetName, separator, configName]; 321 | 322 | // Replace all spaces in filename with separator. 323 | filename = [filename stringByReplacingOccurrencesOfString:@" " withString:self.nameSeparator]; 324 | 325 | return filename; 326 | } 327 | 328 | // Given the filename generate the header comment 329 | - (NSString *)headerCommentForFilename:(NSString *)filename { 330 | NSString *headerComment = @""; 331 | 332 | NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterNoStyle]; 333 | 334 | headerComment = [headerComment stringByAppendingString:@"//\n"]; 335 | headerComment = [headerComment stringByAppendingFormat:@"// %@\n", filename]; 336 | headerComment = [headerComment stringByAppendingString:@"//\n"]; 337 | headerComment = [headerComment stringByAppendingFormat:@"// Generated by BuildSettingExtractor on %@\n", dateString]; 338 | headerComment = [headerComment stringByAppendingString:@"// https://buildsettingextractor.com\n"]; 339 | headerComment = [headerComment stringByAppendingString:@"//"]; 340 | 341 | return headerComment; 342 | } 343 | 344 | 345 | /* Given a build setting dictionary, returns a string representation of the build settings, suitable for an xcconfig file. */ 346 | - (NSString *)stringRepresentationOfBuildSettings:(NSDictionary *)buildSettings { 347 | return [BuildSettingExtractor stringRepresentationOfBuildSettings:buildSettings includeBuildSettingInfoComments:self.includeBuildSettingInfoComments alignBuildSettingValues:self.alignBuildSettingValues linesBetweenSettings:self.linesBetweenSettings commentGenerator:self.buildSettingCommentGenerator]; 348 | } 349 | 350 | /* Given a build setting dictionary and a set of options, returns a string representation of the build settings, suitable for an xcconfig file. Logic is shared between generating xcconfig files and providing a formatting example. */ 351 | + (NSString *)stringRepresentationOfBuildSettings:(NSDictionary *)buildSettings includeBuildSettingInfoComments:(BOOL)includeBuildSettingInfoComments alignBuildSettingValues:(BOOL)alignBuildSettingValues linesBetweenSettings:(NSInteger)linesBetweenSettings commentGenerator:(BuildSettingCommentGenerator *) buildSettingCommentGenerator { 352 | NSMutableString *string = [[NSMutableString alloc] init]; 353 | 354 | // Sort build settings by name for easier reading and testing. Case insensitive compare should stay stable regardess of locale. 355 | NSArray *sortedKeys = [[buildSettings allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; 356 | 357 | NSUInteger maxKeyLength = 0; 358 | if (alignBuildSettingValues) { 359 | for (NSString *key in sortedKeys) { 360 | maxKeyLength = MAX(maxKeyLength, key.length); 361 | } 362 | } 363 | 364 | BOOL firstKey = YES; 365 | NSString *previousKey = nil; 366 | for (NSString *key in sortedKeys) { 367 | id value = buildSettings[key]; 368 | 369 | // If same base setting name as previous key, this is a conditional build setting. 370 | // Don't put newlines between them and don't repeat the build setting info comment. 371 | BOOL sameBaseSettingName = [previousKey tps_baseBuildSettingNameIsEqualTo:key]; // nil previousKey returns nil aka NO 372 | 373 | if (!firstKey && !sameBaseSettingName){ 374 | for (NSInteger i = 0; i < linesBetweenSettings; i++) { 375 | [string appendString:@"\n"]; 376 | } 377 | } 378 | 379 | if (includeBuildSettingInfoComments && !sameBaseSettingName) { 380 | NSString *comment = [buildSettingCommentGenerator commentForBuildSettingWithName:key]; 381 | [string appendString:comment]; 382 | } 383 | 384 | [string appendString:key]; 385 | if (alignBuildSettingValues) { 386 | for (NSUInteger currentLength = key.length; currentLength < maxKeyLength; currentLength++) { 387 | [string appendString:@" "]; 388 | } 389 | } 390 | 391 | if ([value isKindOfClass:[NSString class]]) { 392 | [string appendFormat:@" = %@\n", value]; 393 | 394 | } else if ([value isKindOfClass:[NSArray class]]) { 395 | [string appendFormat:@" = %@\n", [value componentsJoinedByString:@" "]]; 396 | } else { 397 | [NSException raise:@"Should not get here!" format:@"Unexpected class: %@ in %s", [value class], __PRETTY_FUNCTION__]; 398 | } 399 | 400 | previousKey = key; 401 | firstKey = NO; 402 | } 403 | 404 | return string; 405 | } 406 | 407 | /* Given a build configuration list ID, retrieves the list of build configurations, consolidates shared build settings into a shared configuration and returns a dictionary of build settings configurations as strings, keyed by configuration name. */ 408 | - (NSDictionary *)buildSettingStringsByConfigurationForBuildConfigurationListID:(NSString *)buildConfigurationListID { 409 | 410 | // Get the array of build configuration objects for the build configuration list ID 411 | NSDictionary *buildConfigurationList = self.objects[buildConfigurationListID]; 412 | NSArray *projectBuildConfigurations = [self objectArrayForDictionary:buildConfigurationList key:@"buildConfigurations"]; 413 | 414 | 415 | NSDictionary *buildSettingsByConfiguration = [self buildSettingsByConfigurationForConfigurations:projectBuildConfigurations]; 416 | 417 | // Turn each build setting into a build setting string. Store by configuration name 418 | NSMutableDictionary *buildSettingStringsByConfiguration = [[NSMutableDictionary alloc] init]; 419 | [buildSettingsByConfiguration enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 420 | NSString *buildSettingsString = [self stringRepresentationOfBuildSettings:obj]; 421 | [buildSettingStringsByConfiguration setValue:buildSettingsString forKey:key]; 422 | 423 | }]; 424 | return buildSettingStringsByConfiguration; 425 | 426 | } 427 | 428 | 429 | /* Given an array of build configuration dictionaries, removes common build settings into a shared build configuration and returns a dictionary of build settings dictionaries, keyed by configuration name. 430 | */ 431 | - (NSDictionary *)buildSettingsByConfigurationForConfigurations:(NSArray *)buildConfigurations { 432 | 433 | NSMutableDictionary *buildSettingsByConfiguration = [[NSMutableDictionary alloc] init]; 434 | 435 | NSMutableDictionary *sharedBuildSettings = [[NSMutableDictionary alloc] init]; 436 | NSDictionary *firstBuildSettings = nil; 437 | NSInteger index = 0; 438 | 439 | for (NSDictionary *buildConfiguration in buildConfigurations) { 440 | 441 | NSDictionary *buildSettings = buildConfiguration[@"buildSettings"]; 442 | 443 | // Use first build settings as a starting point, represents all settings after first iteration 444 | if (index == 0) { 445 | firstBuildSettings = buildSettings; 446 | 447 | } 448 | 449 | // Second iteration, compare second against first build settings to come up with common items 450 | else if (index == 1){ 451 | [firstBuildSettings enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 452 | id otherObj = buildSettings[key]; 453 | if ([obj isEqualTo:otherObj]) { 454 | sharedBuildSettings[key] = obj; 455 | } 456 | }]; 457 | } 458 | 459 | // Subsequent iteratons, remove common items that don't match current config settings 460 | else { 461 | [[sharedBuildSettings copy] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 462 | id otherObj = buildSettings[key]; 463 | if (![obj isEqualTo:otherObj]) { 464 | [sharedBuildSettings removeObjectForKey:key]; 465 | } 466 | }]; 467 | } 468 | 469 | index++; 470 | } 471 | 472 | [buildSettingsByConfiguration setValue:sharedBuildSettings forKey:self.sharedConfigName]; 473 | 474 | NSArray *sharedKeys = [sharedBuildSettings allKeys]; 475 | for (NSDictionary *projectBuildConfiguration in buildConfigurations) { 476 | NSString *configName = projectBuildConfiguration[@"name"]; 477 | NSMutableDictionary *buildSettings = projectBuildConfiguration[@"buildSettings"]; 478 | [buildSettings removeObjectsForKeys:sharedKeys]; 479 | [buildSettingsByConfiguration setValue:buildSettings forKey:configName]; 480 | 481 | } 482 | 483 | return buildSettingsByConfiguration; 484 | } 485 | 486 | + (NSString *)exampleBuildFormattingStringForSettings:(NSDictionary *)settings includeBuildSettingInfoComments:(BOOL)includeBuildSettingInfoComments alignBuildSettingValues:(BOOL)alignBuildSettingValues linesBetweenSettings:(NSInteger)linesBetweenSettings { 487 | 488 | BuildSettingCommentGenerator *buildSettingCommentGenerator = nil; 489 | 490 | if (includeBuildSettingInfoComments) { 491 | 492 | NSError *infoSourceError = nil; 493 | BuildSettingInfoSource *infoSource = [BuildSettingInfoSource resolvedBuildSettingInfoSourceWithStyle:BuildSettingInfoSourceStyleStandard customURL:nil error:&infoSourceError]; 494 | 495 | if (infoSource) { 496 | buildSettingCommentGenerator = [[BuildSettingCommentGenerator alloc] initWithBuildSettingInfoSource:infoSource]; 497 | } 498 | } 499 | 500 | return [BuildSettingExtractor stringRepresentationOfBuildSettings:settings includeBuildSettingInfoComments:includeBuildSettingInfoComments alignBuildSettingValues:alignBuildSettingValues linesBetweenSettings:linesBetweenSettings commentGenerator:buildSettingCommentGenerator]; 501 | } 502 | 503 | @end 504 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingInfoSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingInfoSource.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 10/9/19. 6 | // Copyright © 2019 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, BuildSettingInfoSourceStyle) { 12 | BuildSettingInfoSourceStyleStandard = 100, 13 | }; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface BuildSettingInfoSource : NSObject 18 | 19 | @property (nullable) NSURL *resolvedURL; // nil if unresolved 20 | @property NSInteger resolvedVersion; // -1 if unresolved 21 | 22 | // On success, will return a BuildSettingInfoSource instance with valid values for resolvedURL and resolvedVersion. 23 | // 24 | // Even on success, error value may be present if resolved value was not from the expected source. 25 | // For example, if a custom URL was provided but not found the resolved info source may be the version of Xcode 26 | // in its standard location. 27 | // 28 | // On failure will return nil and an error if unable to resolve a build setting info source. 29 | 30 | + (nullable BuildSettingInfoSource *)resolvedBuildSettingInfoSourceWithStyle:(BuildSettingInfoSourceStyle)style customURL:(nullable NSURL *)customURL error:(NSError **)error; 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingInfoSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingInfoSource.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 10/9/19. 6 | // Copyright © 2019 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "BuildSettingInfoSource.h" 10 | #import "Constants+Categories.h" 11 | 12 | @interface BuildSettingInfoSource () 13 | @property BuildSettingInfoSourceStyle style; 14 | @property (nullable) NSURL *customURL; 15 | @property NSString *standardAppPath; 16 | @property NSString *standardBetaPath; 17 | @end 18 | 19 | @implementation BuildSettingInfoSource 20 | 21 | + (BuildSettingInfoSource *)resolvedBuildSettingInfoSourceWithStyle:(BuildSettingInfoSourceStyle)style customURL:(NSURL *)customURL error:(NSError **)error { 22 | 23 | BuildSettingInfoSource *infoSource = [[BuildSettingInfoSource alloc] initWithStyle:style customURL:customURL]; 24 | BOOL success = [infoSource resolveBuildSettingInfoSourceWithError:error]; 25 | 26 | if (success) { 27 | return infoSource; 28 | } else { 29 | return nil; 30 | } 31 | } 32 | 33 | - (instancetype)init { 34 | return [self initWithStyle:BuildSettingInfoSourceStyleStandard customURL:nil]; 35 | } 36 | 37 | - (instancetype)initWithStyle:(BuildSettingInfoSourceStyle)style customURL:(nullable NSURL *)customURL { 38 | self = [super init]; 39 | if (self) { 40 | self.style = style; 41 | self.customURL = customURL; 42 | self.resolvedURL = nil; 43 | self.resolvedVersion = -1; 44 | self.standardAppPath = @"/Applications/Xcode.app"; 45 | self.standardBetaPath = @"/Applications/Xcode-beta.app"; 46 | 47 | // If using the special style for testing failures, set incorrect paths 48 | if (style == -1) { 49 | self.standardAppPath = @"/Supplications/EXcode.app"; 50 | self.standardBetaPath = @"/Supplications/EXcode-beta.app"; 51 | } 52 | } 53 | return self; 54 | } 55 | 56 | - (BOOL)resolveBuildSettingInfoSourceWithError: (NSError **)error { 57 | BOOL successfullyResolved = NO; 58 | 59 | BOOL standardAppExists = [[NSFileManager defaultManager] fileExistsAtPath:self.standardAppPath]; 60 | NSInteger standardAppVersion = -1; 61 | NSURL *standardAppURL = nil; 62 | 63 | BOOL standardBetaExists = [[NSFileManager defaultManager] fileExistsAtPath:self.standardBetaPath]; 64 | NSInteger standardBetaVersion = -1; 65 | NSURL *standardBetaURL = nil; 66 | 67 | if (standardAppExists) { 68 | standardAppURL = [NSURL fileURLWithPath:self.standardAppPath]; 69 | standardAppVersion = [self versionForXcodeAtURL:standardAppURL]; 70 | } 71 | 72 | if (standardBetaExists) { 73 | standardBetaURL = [NSURL fileURLWithPath:self.standardBetaPath]; 74 | standardBetaVersion = [self versionForXcodeAtURL:standardBetaURL]; 75 | } 76 | 77 | // If both are found use the beta if it is a later version 78 | if (standardAppExists && standardBetaExists) { 79 | if (standardBetaVersion > standardAppVersion) { 80 | self.resolvedURL = standardBetaURL; 81 | self.resolvedVersion = standardBetaVersion; 82 | } else { 83 | self.resolvedURL = standardAppURL; 84 | self.resolvedVersion = standardAppVersion; 85 | } 86 | } 87 | 88 | else if (standardAppExists) { 89 | self.resolvedURL = standardAppURL; 90 | self.resolvedVersion = standardAppVersion; 91 | } 92 | 93 | else if (standardBetaExists) { 94 | self.resolvedURL = standardBetaURL; 95 | self.resolvedVersion = standardBetaVersion; 96 | } 97 | 98 | if (self.resolvedURL != nil && self.resolvedVersion != -1) { 99 | successfullyResolved = YES; 100 | } else { 101 | if (error) { 102 | *error = [NSError errorForUnresolvedBuildSettingInfoSource]; 103 | } 104 | } 105 | 106 | return successfullyResolved; 107 | } 108 | 109 | - (NSInteger)versionForXcodeAtURL:(NSURL *)url { 110 | NSString *pathToXcodeInfoPlist = [[url path] stringByAppendingPathComponent:@"Contents/Info.plist"]; 111 | NSDictionary *xcodeInfoDictionary = [NSDictionary dictionaryWithContentsOfFile:pathToXcodeInfoPlist]; 112 | NSString *versionString = xcodeInfoDictionary[@"DTXcode"]; 113 | NSInteger xcodeVersion = [versionString integerValue]; 114 | if (!versionString || xcodeVersion == 0) { 115 | NSLog(@"Could not read Xcode version. Version string: %@, Version Number: %ld", versionString, (long)xcodeVersion); 116 | } 117 | return xcodeVersion; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingInfoSubpaths.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | backstopSettingInfo 6 | 7 | [COMBINE_HIDPI_IMAGES]-description 8 | Combines image files at different resolutions into one multi-page TIFF file that is HiDPI compliant for macOS 10.7 and later. Only image files in the same directory and with the same base name and extension are combined. The file names must conform to the naming convention used in HiDPI. 9 | [COMBINE_HIDPI_IMAGES]-name 10 | Combine HiDPI Images 11 | [IPHONEOS_DEPLOYMENT_TARGET]-description 12 | Code will load on this and later versions of iOS. Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs. 13 | [IPHONEOS_DEPLOYMENT_TARGET]-name 14 | iOS Deployment Target 15 | [MARKETING_VERSION]-description 16 | This setting defines the user-visible version of the project. The value corresponds to the `CFBundleShortVersionString` key in your app's Info.plist. 17 | [MARKETING_VERSION]-name 18 | Marketing Version 19 | [SWIFT_COMPILATION_MODE]-description 20 | This setting controls the way the Swift files in a module are rebuilt.\n- Incremental: Only rebuild the Swift source files in the module that are out of date, running multiple compiler processes as needed.\n- Whole Module: Always rebuild all Swift source files in the module, in a single compiler process. 21 | [SWIFT_COMPILATION_MODE]-name 22 | Swift Compilation Mode 23 | [SWIFT_OPTIMIZATION_LEVEL]-description 24 | None [-Onone], Fastest [-O], Fastest, Unchecked [-Ounchecked] 25 | [SWIFT_OPTIMIZATION_LEVEL]-name 26 | Swift Optimization Level 27 | [TVOS_DEPLOYMENT_TARGET]-description 28 | Code will load on this and later versions of tvOS. Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs. 29 | [TVOS_DEPLOYMENT_TARGET]-name 30 | tvOS Deployment Target 31 | [WATCHOS_DEPLOYMENT_TARGET]-description 32 | Code will load on this and later versions of watchOS. Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs. 33 | [WATCHOS_DEPLOYMENT_TARGET]-name 34 | watchOS Deployment Target 35 | [SWIFT_VERSION]-name 36 | Swift Language Version 37 | [SWIFT_VERSION]-description 38 | Interpret input according to a specific Swift language version number. 39 | 40 | deprecatedSubpathsByVersion 41 | 42 | 1200 43 | 44 | 45 | Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/Native Build System.strings 46 | Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/Native Build System.xcspec 47 | 48 | 49 | 0900 50 | 51 | 52 | Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Specifications/English.lproj/Native Build System.strings 53 | 54 | 55 | 56 | introducedSubpathsByVersion 57 | 58 | 1200 59 | 60 | 61 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Metal.xcplugin/Contents/Resources/MetalLinker.strings 62 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Metal.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.metal-linker.strings 63 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.metal-linker.strings 64 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.metal-linker.strings 65 | 66 | 67 | 68 | subpaths 69 | 70 | 71 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/Frameworks/DevToolsCore.framework/Versions/Current/Resources/en.lproj/CoreBuildSystem.strings 72 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/Frameworks/DevToolsCore.framework/Versions/Current/Resources/English.lproj/CoreBuildSystem.strings 73 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.strings 74 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.strings 75 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.strings 76 | 77 | 78 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 6.0.strings 79 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 6.1.strings 80 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 7.0.strings 81 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 7.1.strings 82 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 8.0.strings 83 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 8.1.strings 84 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple LLVM 9.0.strings 85 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/English.lproj/Apple Clang.strings 86 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/en.lproj/Apple Clang.strings 87 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Apple Clang.strings 88 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Apple Clang.strings 89 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Apple Clang.strings 90 | 91 | 92 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/XCLanguageSupport.xcplugin/Contents/Resources/en.lproj/Swift Compiler.strings 93 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Swift Compiler.strings 94 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Swift Compiler.strings 95 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Swift Compiler.strings 96 | 97 | 98 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/XCLanguageSupport.xcplugin/Contents/Resources/SwiftBuildSettings.strings 99 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/SwiftBuildSettings.strings 100 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/SwiftBuildSettings.strings 101 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/SwiftBuildSettings.strings 102 | 103 | 104 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Metal.xcplugin/Contents/Resources/Metal Compiler.strings 105 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Metal.xcplugin/Contents/Resources/MetalCompiler.strings 106 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Metal.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.metal.strings 107 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.metal.strings 108 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.metal.strings 109 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.metal.strings 110 | 111 | 112 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.model.coredata.strings 113 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/English.lproj/com.apple.compilers.model.coredata.strings 114 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredata.strings 115 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredata.strings 116 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredata.strings 117 | 118 | 119 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.model.coredatamapping.strings 120 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/English.lproj/com.apple.compilers.model.coredatamapping.strings 121 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredatamapping.strings 122 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredatamapping.strings 123 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.coredatamapping.strings 124 | 125 | 126 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.model.persistence.strings 127 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Core Data.xcplugin/Contents/Resources/English.lproj/com.apple.compilers.model.persistence.strings 128 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.persistence.strings 129 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.persistence.strings 130 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.model.persistence.strings 131 | 132 | 133 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/AssetCatalogCompiler.xcspec 134 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/AssetCatalogCompiler.xcspec 135 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/AssetCatalogCompiler.xcspec 136 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/AssetCatalogCompiler.xcspec 137 | 138 | 139 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/en.lproj/com.apple.compilers.assetcatalog.strings 140 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/English.lproj/com.apple.compilers.assetcatalog.strings 141 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.assetcatalog.strings 142 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.assetcatalog.strings 143 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.compilers.assetcatalog.strings 144 | 145 | 146 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/en.lproj/com.apple.xcode.tools.ibtool.compiler.strings 147 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/English.lproj/com.apple.xcode.tools.ibtool.compiler.strings 148 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.compiler.strings 149 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.compiler.strings 150 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.compiler.strings 151 | 152 | 153 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/en.lproj/com.apple.xcode.tools.ibtool.postprocessor.strings 154 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/English.lproj/com.apple.xcode.tools.ibtool.postprocessor.strings 155 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.postprocessor.strings 156 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.postprocessor.strings 157 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/com.apple.xcode.tools.ibtool.postprocessor.strings 158 | 159 | 160 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/en.lproj/Interface Builder Storyboard Compiler.strings 161 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/English.lproj/Interface Builder Storyboard Compiler.strings 162 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Compiler.strings 163 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Compiler.strings 164 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Compiler.strings 165 | 166 | 167 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/en.lproj/Interface Builder Storyboard Postprocessor.strings 168 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/IBCompilerPlugin.xcplugin/Contents/Resources/English.lproj/Interface Builder Storyboard Postprocessor.strings 169 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Postprocessor.strings 170 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Postprocessor.strings 171 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Interface Builder Storyboard Postprocessor.strings 172 | 173 | 174 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/CoreBuildTasks.xcplugin/Contents/Resources/en.lproj/OpenCL.strings 175 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/CoreBuildTasks.xcplugin/Contents/Resources/English.lproj/OpenCL.strings 176 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/OpenCL.strings 177 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/OpenCL.strings 178 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/OpenCL.strings 179 | 180 | 181 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/Frameworks/DevToolsCore.framework/Versions/A/Resources/CoreBuildSystem.xcspec 182 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.xcspec 183 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.xcspec 184 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/CoreBuildSystem.xcspec 185 | 186 | 187 | Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/PrivatePlugIns/IDEiOSPlatformSupportCore.ideplugin/Contents/Resources/Device.xcspec 188 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Embedded-Shared.xcspec 189 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Embedded-Shared.xcspec 190 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Embedded-Shared.xcspec 191 | 192 | 193 | Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/XCLanguageSupport.xcplugin/Contents/Resources/Swift.xcspec 194 | Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Swift.xcspec 195 | Contents/Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Swift.xcspec 196 | Contents/SharedFrameworks/XCBuild.framework/Versions/A/PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Swift.xcspec 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /BuildSettingExtractor/BuildSettingInfoSubpathsNotes.md: -------------------------------------------------------------------------------- 1 | # BuildSettingInfoSubpaths 2 | 3 | An overview of the structure and keys in the BuildSettingInfoSubpaths.plist file. 4 | 5 | ## Overview 6 | BuildSettingExtractor gleans information about build settings from files within the Xcode app bundle. The `BuildSettingInfoSubpaths.plist` file is a listing of the paths to those files. All paths in the plist file are relative to the `Xcode.app` app wrapper. 7 | 8 | The build setting info is sometimes contained in `.strings` files and sometimes in `.xcspec` files. 9 | 10 | ## Arrays of subpaths 11 | From release to release of Xcode, sometimes the same logical file will have a new path or file name. For instance, many had _English.lproj_ as part of the path, which was changed to _en.lproj_ in subsequent releases. More recently, file names have been gradually being renamed to use a reverse-DNS naming style. 12 | 13 | Rather than keep detailed versioning bookkeeping, each array contains a list of paths, at least one of which should be found. If what is conceptually the same file has a different path or file name in a new release, the new path should be added to the array. Ideally it should be added to the beginning of the array, since it will be found sooner when searching more recent Xcode versions. In practice, these arrays are so short the difference is negligible. 14 | 15 | ## Keys 16 | ### subpaths 17 | #### An array of arrays of subpath strings 18 | Each item is an array of paths representing one logical file containing build setting info. The assumption is that at least one of these paths is valid across all versions of Xcode. 19 | 20 | Add an entry to a subpath array in this key if the path or filename of an existing entry has changed in a new version of Xcode. Theoretically, any new file containing build setting info would be added to the _ introducedSubpathsByVersion_ key since the file does not exist in prior versions of Xcode. 21 | 22 | ### deprecatedSubpathsByVersion 23 | #### A dictionary with Xcode version as the key and an array of arrays of subpath strings as the value. 24 | Each key is a version string matching the version string found for the key `DTXcode` in the Xcode `Info.plist` file. The version number matches the version the file was first removed. 25 | 26 | The value for each key is an array that works in the same manner as an entry in the subpaths array. 27 | 28 | Add to this key when a build setting info file is removed completely. This prevents a warning / error / test failure when using newer Xcode versions as the build info source, but still allows the file to be successfully included when using older Xcode versions as the build info source. 29 | 30 | ### introducedSubpathsByVersion 31 | #### A dictionary with Xcode version as the key and an array of arrays of subpath strings as the value. 32 | Each key is a version string matching the version string found for the key `DTXcode` the Xcode `Info.plist` file. The version number matches the version the file first appeared. 33 | 34 | The value for each key is an array that works in the same manner as an entry in the subpaths array. 35 | 36 | Add to this key when a completely new build setting info file is added. This prevents a warning / error / test failure when using older Xcode versions as the build info source, but allows the file to be successfully included when using newer Xcode versions as the build info source. 37 | 38 | Note that, just as with the subpaths values, any subpath introduced here is represented as an array. If the path or filename changes for the same logical file, the new subpath should be added to the array. 39 | 40 | ### backstopSettingInfo 41 | #### A dictionary with string keys and values 42 | Sometimes Xcode does not have a title or description defined for a build setting, or alternately, the file that provides that information has been elusive. 43 | 44 | This dictionary provides that missing information as a backstop in case the information is not found by searching the build info files specified by the other keys. 45 | 46 | The keys take the form `\[_BuildSettingName_\]-name` for the human-readable title of the build setting and `\[_BuildSettingName_\]-description` for the description of the setting. 47 | 48 | The keys match the format of keys found in the various `.strings` files containing build setting info. 49 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Constants+Categories.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants+Categories.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | typedef NS_ENUM(NSUInteger, BuildSettingExtractorErrorCodes) { 12 | UnsupportedXcodeVersion = 100, 13 | DirectoryContainsBuildConfigFiles = 101, 14 | ProjectSettingsNamingConflict = 102, 15 | NoSettingsFoundInProjectFile = 103, 16 | BuildSettingInfoSourceNotFound = 104, 17 | BuildSettingInfoFilesNotFound = 105, 18 | BuildSettingInfoSubpathNotFound = 106, 19 | }; 20 | 21 | extern NSErrorDomain const TPSBuildSettingExtractorErrorDomain; 22 | extern NSString * TPSMultipleUnderlyingErrorsKey(void); 23 | 24 | #pragma mark - 25 | 26 | @interface NSString (TPS_TypeIdentifierAdditions) 27 | + (NSString *)tps_projectBundleTypeIdentifier; 28 | + (NSString *)tps_buildConfigurationFileTypeIdentifier; 29 | + (NSString *)tps_preferredTypeIdentifierForFileExtension:(NSString *)string; 30 | @end 31 | 32 | @interface NSString (TPS_BuildSettingAdditions) 33 | - (NSString *)tps_baseBuildSettingName; // Removes any conditional section of a build setting 34 | - (BOOL)tps_baseBuildSettingNameIsEqualTo:(NSString *)buildSettingName; // returns YES if provided build setting name has the same base as the receiver 35 | @end 36 | 37 | #pragma mark - 38 | 39 | @interface NSDictionary (TPS_BuildSettingAdditions) 40 | // Assumes that a dictionary of build settings always has NSString values 41 | // Returns NO if all the values in a dictionary are an empty string 42 | // Raises an exception if used on a dictionary with any non-NSString value 43 | - (BOOL)tps_containsBuildSettings; 44 | 45 | // Returns a new dictionary containing all entries in the receiver except for 46 | // entries with an empty string value. 47 | - (NSDictionary *)tps_dictionaryByRemovingEmptyStringValues; 48 | @end 49 | 50 | #pragma mark - 51 | 52 | @interface NSError (TPS_BuildSettingExtractorAdditions) 53 | 54 | // Notify the user we are not using the exact name for the project settings provided in Preferences 55 | + (NSError *)errorForNameConflictWithName:(NSString *)conflictedName validatedName:(NSString *)validatedName; 56 | 57 | // Notify the user we did not find any settings in the project. 58 | + (NSError *)errorForNoSettingsFoundInProject:(NSString *)projectName; 59 | 60 | // Notify the user the project version is unsupported 61 | + (NSError *)errorForUnsupportedProjectURL:(NSURL *)projectWrapperURL fileVersion:(NSString *)compatibilityVersion; 62 | 63 | // Notify the user no build setting info source could was found 64 | + (NSError *)errorForUnresolvedBuildSettingInfoSource; 65 | 66 | // Notify the user the destination folder already contains build config files 67 | + (NSError *)errorForDestinationContainsBuildConfigFiles; 68 | 69 | // Error that one or more expected build setting info files were not found 70 | // User info includes NSMultipleUnderlyingErrorsKey to report underlying errors 71 | + (NSError *)errorForSettingInfoFilesNotFound:(NSArray *)subpathErrorStrings; 72 | 73 | @end 74 | 75 | #pragma mark - 76 | 77 | @interface EmptyStringTransformer: NSValueTransformer {} 78 | @end 79 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Constants+Categories.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSPasteboard+TPS_XcodeProjectReadingExtensions.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "Constants+Categories.h" 10 | 11 | NSErrorDomain const TPSBuildSettingExtractorErrorDomain = @"TPSBuildSettingExtractorErrorDomain"; 12 | 13 | @implementation NSString (TPS_TypeIdentifierAdditions) 14 | 15 | + (NSString *)tps_projectBundleTypeIdentifier { 16 | static NSString *projectBundleTypeIdentifier; 17 | if (!projectBundleTypeIdentifier) { 18 | projectBundleTypeIdentifier = [self tps_preferredTypeIdentifierForFileExtension:@"xcodeproj"]; 19 | // NSLog(@"Xcode UTI: %@", projectBundleTypeIdentifier); 20 | } 21 | return projectBundleTypeIdentifier; 22 | } 23 | 24 | + (NSString *)tps_buildConfigurationFileTypeIdentifier { 25 | static NSString *buildConfigurationFileTypeIdentifier; 26 | if (!buildConfigurationFileTypeIdentifier) { 27 | buildConfigurationFileTypeIdentifier = [self tps_preferredTypeIdentifierForFileExtension:@"xcconfig"]; 28 | // NSLog(@"xcconfig UTI: %@", buildConfigurationFileTypeIdentifier); 29 | } 30 | return buildConfigurationFileTypeIdentifier; 31 | } 32 | 33 | + (NSString *)tps_preferredTypeIdentifierForFileExtension:(NSString *)string { 34 | NSString *identifier = CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)string, NULL)); 35 | return identifier; 36 | } 37 | 38 | @end 39 | 40 | #pragma mark - 41 | 42 | @implementation NSString (TPS_BuildSettingAdditions) 43 | 44 | - (NSString *)tps_baseBuildSettingName { 45 | NSString *baseBuildSettingName = [self copy]; 46 | NSRange range = [baseBuildSettingName rangeOfString:@"["]; // delimeter for a conditional build setting 47 | if (range.location != NSNotFound) { 48 | baseBuildSettingName = [baseBuildSettingName substringToIndex:range.location]; 49 | } 50 | return baseBuildSettingName; 51 | } 52 | 53 | - (BOOL)tps_baseBuildSettingNameIsEqualTo:(NSString *)buildSettingName { 54 | return [[self tps_baseBuildSettingName] isEqualToString:[buildSettingName tps_baseBuildSettingName]]; 55 | } 56 | 57 | @end 58 | 59 | #pragma mark - 60 | 61 | @implementation NSDictionary (TPS_BuildSettingAdditions) 62 | 63 | - (BOOL)tps_containsBuildSettings { 64 | BOOL foundNonEmptyString = NO; 65 | for (id value in self.allValues) { 66 | if ([value isKindOfClass:[NSString class]]) { 67 | if (![(NSString *)value isEqualToString:@""]) { 68 | foundNonEmptyString = YES; 69 | break; 70 | } 71 | } else { 72 | [NSException raise:(NSInternalInconsistencyException) format:@"-containsBuildSetting is expected to be called on a dictionary with NSString values."]; 73 | } 74 | } 75 | return foundNonEmptyString; 76 | } 77 | 78 | - (NSDictionary *)tps_dictionaryByRemovingEmptyStringValues { 79 | NSMutableDictionary *temp = [[NSMutableDictionary alloc] init]; 80 | [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) { 81 | if (![value isEqualTo:@""]) { 82 | [temp setValue:value forKey:key]; 83 | } 84 | }]; 85 | return temp; 86 | } 87 | 88 | @end 89 | 90 | #pragma mark - 91 | 92 | @implementation NSError (TPS_BuildSettingExtractorAdditions) 93 | 94 | // Notify the user we are not using the exact name for the project settings provided in Preferences 95 | + (NSError *)errorForNameConflictWithName:(NSString *)conflictedName validatedName:(NSString *)validatedName { 96 | NSString *errorDescription = [NSString stringWithFormat:@"Project settings filename conflict."]; 97 | NSString *errorRecoverySuggestion = [NSString stringWithFormat:@"The target \'%@\' has the same name as the project name set in Preferences.\n\nThe generated project settings files will use the name \'%@\' to avoid a conflict.", conflictedName, validatedName]; 98 | NSDictionary *errorUserInfo = @{NSLocalizedDescriptionKey:errorDescription, NSLocalizedRecoverySuggestionErrorKey: errorRecoverySuggestion}; 99 | 100 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:ProjectSettingsNamingConflict userInfo:errorUserInfo]; 101 | 102 | return error; 103 | } 104 | 105 | // Notify the user we did not find any settings in the project. 106 | + (NSError *)errorForNoSettingsFoundInProject:(NSString *)projectName { 107 | NSString *errorDescription = [NSString stringWithFormat:@"No settings found."]; 108 | NSString *errorRecoverySuggestion = [NSString stringWithFormat:@"No settings were found in the project \'%@\'.\n\nThe project may already be using .xcconfig files for its build settings.\n\nNo xcconfig files will be written. ", projectName]; 109 | NSDictionary *errorUserInfo = @{NSLocalizedDescriptionKey:errorDescription, NSLocalizedRecoverySuggestionErrorKey: errorRecoverySuggestion}; 110 | 111 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:NoSettingsFoundInProjectFile userInfo:errorUserInfo]; 112 | 113 | return error; 114 | } 115 | 116 | 117 | + (NSError *)errorForUnsupportedProjectURL:(NSURL *)projectWrapperURL fileVersion:(NSString *)compatibilityVersion { 118 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Unable to extract build settings from project ‘%@’.", [[projectWrapperURL lastPathComponent] stringByDeletingPathExtension]], NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:@"Project file format version ‘%@’ is not supported.", compatibilityVersion]}; 119 | 120 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:UnsupportedXcodeVersion userInfo:userInfo]; 121 | 122 | return error; 123 | } 124 | 125 | + (NSError *)errorForUnresolvedBuildSettingInfoSource { 126 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"No source of build setting info was found.", NSLocalizedRecoverySuggestionErrorKey:@"Xcode or Xcode-beta must be installed in the Applications folder to generate descriptive build setting comments.\n\nConfiguration files will be generated without comments."}; 127 | 128 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:BuildSettingInfoSourceNotFound userInfo:userInfo]; 129 | 130 | return error; 131 | } 132 | 133 | + (NSError *)errorForDestinationContainsBuildConfigFiles { 134 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Build config files already exist in this folder. Do you want to replace them?", NSLocalizedRecoveryOptionsErrorKey:@[@"Cancel", @"Replace"], NSLocalizedRecoverySuggestionErrorKey:@"Build configuration files already exist in this folder. Replacing will overwrite any files with the same file names."}; 135 | 136 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:DirectoryContainsBuildConfigFiles userInfo:userInfo]; 137 | 138 | return error; 139 | } 140 | 141 | + (NSError *)errorForSettingInfoFilesNotFound:(NSArray *)subpathErrorStrings { 142 | if (subpathErrorStrings.count == 0) { 143 | [NSException raise:NSInternalInconsistencyException format:@"Attempt to create BuildSettingInfoSubpathNotFound error with no underlying errors"]; 144 | } 145 | NSMutableArray *underlyingErrors = [[NSMutableArray alloc] init]; 146 | for (NSString *errorString in subpathErrorStrings) { 147 | NSDictionary *subpathErrorUserInfo = @{NSLocalizedDescriptionKey:errorString}; 148 | NSError *subpathError = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:BuildSettingInfoSubpathNotFound userInfo:subpathErrorUserInfo]; 149 | [underlyingErrors addObject:subpathError]; 150 | } 151 | 152 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Some build info files not found.", TPSMultipleUnderlyingErrorsKey():underlyingErrors}; 153 | 154 | NSError *error = [NSError errorWithDomain:TPSBuildSettingExtractorErrorDomain code:BuildSettingInfoFilesNotFound userInfo:userInfo]; 155 | 156 | return error; 157 | } 158 | 159 | @end 160 | 161 | // Can remove once deployment target is macOS 11.3 or later 162 | NSString * TPSMultipleUnderlyingErrorsKey(void) { 163 | if (@available(macOS 11.3, *)) { 164 | return NSMultipleUnderlyingErrorsKey; 165 | } else { 166 | return @"NSMultipleUnderlyingErrorsKey"; 167 | } 168 | } 169 | 170 | #pragma mark - 171 | 172 | @implementation EmptyStringTransformer 173 | + (Class)transformedValueClass { return [NSString class]; } 174 | + (BOOL)allowsReverseTransformation { return YES; } 175 | 176 | - (id)transformedValue:(id)value { 177 | if ([value isKindOfClass:[NSString class]] && [value isEqualToString:@""]) { 178 | return nil; 179 | } else if (value == nil) { 180 | return @""; 181 | } else { 182 | return value; 183 | } 184 | } 185 | 186 | - (id)reversedTransformedValue:(id)value { 187 | if (value == nil) { return @""; } 188 | else { return value; } 189 | } 190 | @end 191 | 192 | -------------------------------------------------------------------------------- /BuildSettingExtractor/DragFileView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DragFileView.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | IB_DESIGNABLE 12 | @interface DragFileView : NSBox 13 | 14 | @property (readonly) NSURL *fileURL; 15 | 16 | @property (weak) id target; 17 | @property SEL action; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /BuildSettingExtractor/DragFileView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DragFileView.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 1/30/15. 6 | // Copyright (c) 2015 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "DragFileView.h" 10 | #import "AppConstants+Categories.h" 11 | #import "Constants+Categories.h" 12 | 13 | 14 | @interface DragFileView () 15 | @property NSURL *fileURL; 16 | @property (weak) IBOutlet NSTextField *labelView; 17 | @end 18 | 19 | @implementation DragFileView 20 | 21 | - (void)commonInit { 22 | self.boxType = NSBoxCustom; 23 | self.cornerRadius = 20.0; 24 | self.borderWidth = 0.0; 25 | [self setHighlight:NO]; 26 | [self registerForDraggedTypes:@[(NSString *)kUTTypeFileURL]]; 27 | } 28 | 29 | - (instancetype)initWithCoder:(NSCoder *)coder { 30 | if (self = [super initWithCoder:coder]) { [self commonInit]; } return self; 31 | } 32 | 33 | - (instancetype)initWithFrame:(NSRect)frameRect { 34 | if (self = [super initWithFrame:frameRect]) { [self commonInit]; } return self; 35 | } 36 | 37 | - (void)setHighlight:(BOOL)flag { 38 | if (flag) { 39 | if (@available(macOS 10.14, *)) { 40 | self.fillColor = [[NSColor colorNamed: @"dragViewBackgroundColor"] colorWithSystemEffect:NSColorSystemEffectPressed]; 41 | } else { 42 | self.fillColor = [NSColor colorWithCalibratedRed:0.56 green:0.7 blue:0.81 alpha:1.0]; 43 | } 44 | } else { 45 | if (@available(macOS 10.13, *)) { 46 | self.fillColor = [NSColor colorNamed: @"dragViewBackgroundColor"]; 47 | } else { 48 | self.fillColor = [NSColor colorWithCalibratedRed:0.7 green:0.85 blue:1.0 alpha:1.0]; 49 | } 50 | } 51 | } 52 | 53 | - (NSDragOperation)draggingEntered:(id )sender { 54 | 55 | BOOL canRead = [[sender draggingPasteboard] tps_canReadXcodeProjectFileURL]; 56 | 57 | if (canRead) { 58 | [self setHighlight:YES]; 59 | return NSDragOperationGeneric; 60 | } else { 61 | return NSDragOperationNone; 62 | } 63 | } 64 | 65 | - (void)draggingExited:(id )sender { 66 | [self setHighlight:NO]; 67 | } 68 | 69 | - (BOOL)prepareForDragOperation:(id )sender { 70 | return [[sender draggingPasteboard] tps_canReadXcodeProjectFileURL]; 71 | } 72 | 73 | - (BOOL)performDragOperation:(id )sender { 74 | self.fileURL = [[sender draggingPasteboard] tps_readXcodeProjectFileURL]; 75 | return self.fileURL != nil; 76 | } 77 | 78 | - (void)concludeDragOperation:(id )sender { 79 | [self setHighlight:NO]; 80 | [NSApp sendAction:self.action to:self.target from:self]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/ContentsPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentsPreferencesViewController.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/14/20. 6 | // Copyright © 2020 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ContentsPreferencesViewController : NSViewController 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/ContentsPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContentsPreferencesViewController.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/14/20. 6 | // Copyright © 2020 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "ContentsPreferencesViewController.h" 10 | #import "AppConstants+Categories.h" 11 | #import "BuildSettingExtractor.h" 12 | 13 | @interface ContentsPreferencesViewController () 14 | @property (unsafe_unretained) IBOutlet NSTextView *exampleOutputTextView; 15 | @property (weak) IBOutlet NSTextField *linesBetweenSettingsTextField; 16 | @property (weak) IBOutlet NSStepper *linesBetweenSettingsStepper; 17 | @property (weak) IBOutlet NSTextField *linesBetweenSettingsLabel; 18 | @end 19 | 20 | @implementation ContentsPreferencesViewController 21 | 22 | - (NSSize)preferredContentSize { 23 | return NSMakeSize(744.0, 352.0); 24 | } 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | NSArray *defaultKeys = @[TPSIncludeBuildSettingInfoComments, TPSLinesBetweenBuildSettingsWithInfo, TPSLinesBetweenBuildSettings, TPSAlignBuildSettingValues]; 29 | for (NSString *key in defaultKeys) { 30 | [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:key options:0 context:nil]; 31 | } 32 | } 33 | 34 | - (void)viewWillAppear { 35 | [super viewWillAppear]; 36 | [self updateUserInterface]; 37 | } 38 | 39 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 40 | [self updateUserInterface]; 41 | } 42 | 43 | - (void)controlTextDidChange:(NSNotification *)obj { 44 | NSInteger newValue = self.linesBetweenSettingsTextField.integerValue; 45 | [self setLineSpacing:newValue]; 46 | } 47 | 48 | - (IBAction)lineSpacingStepperChanged:(id)sender { 49 | NSInteger newValue = self.linesBetweenSettingsStepper.integerValue; 50 | [self setLineSpacing:newValue]; 51 | } 52 | - (IBAction)lineSpacingTextFieldChanged:(id)sender { 53 | NSInteger newValue = self.linesBetweenSettingsTextField.integerValue; 54 | [self setLineSpacing:newValue]; 55 | } 56 | 57 | - (void)setLineSpacing:(NSInteger)value { 58 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 59 | BOOL includeBuildSettingsInfo = [defaults boolForKey:TPSIncludeBuildSettingInfoComments]; 60 | NSString *lineSpacingKey = includeBuildSettingsInfo ? TPSLinesBetweenBuildSettingsWithInfo : TPSLinesBetweenBuildSettings; 61 | [defaults setInteger:value forKey:lineSpacingKey]; 62 | } 63 | 64 | - (void)updateUserInterface { 65 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 66 | BOOL includeBuildSettingsInfo = [defaults boolForKey:TPSIncludeBuildSettingInfoComments]; 67 | NSString *lineSpacingKey = includeBuildSettingsInfo ? TPSLinesBetweenBuildSettingsWithInfo : TPSLinesBetweenBuildSettings; 68 | NSInteger linesBetweenSettings = [defaults integerForKey:lineSpacingKey]; 69 | [self updateLineSpacingControlsWithLineSpacing:linesBetweenSettings]; 70 | [self updateSampleOutputWithLineSpacing:linesBetweenSettings]; 71 | } 72 | 73 | - (void)updateLineSpacingControlsWithLineSpacing:(NSInteger)linesBetweenSettings { 74 | self.linesBetweenSettingsTextField.integerValue = linesBetweenSettings; 75 | self.linesBetweenSettingsStepper.integerValue = linesBetweenSettings; 76 | self.linesBetweenSettingsLabel.stringValue = linesBetweenSettings == 1 ? @"line between settings" : @"lines between settings"; 77 | } 78 | 79 | - (void)updateSampleOutputWithLineSpacing:(NSInteger)linesBetweenSettings { 80 | NSDictionary *sampleSettings = @{@"CLANG_WARN__DUPLICATE_METHOD_MATCH":@"YES", 81 | @"COPY_PHASE_STRIP":@"YES", 82 | @"DEBUG_INFORMATION_FORMAT":@"dwarf-with-dsym", 83 | @"ENABLE_NS_ASSERTIONS":@"NO"}; 84 | 85 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 86 | BOOL includeBuildSettingsInfo = [defaults boolForKey:TPSIncludeBuildSettingInfoComments]; 87 | BOOL alignBuildSettings = [defaults boolForKey:TPSAlignBuildSettingValues]; 88 | 89 | NSString *testString = [BuildSettingExtractor exampleBuildFormattingStringForSettings:sampleSettings includeBuildSettingInfoComments:includeBuildSettingsInfo alignBuildSettingValues:alignBuildSettings linesBetweenSettings:linesBetweenSettings]; 90 | self.exampleOutputTextView.string = testString; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/FileLayoutPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileLayoutPreferencesViewController.h 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/14/20. 6 | // Copyright © 2020 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FileLayoutPreferencesViewController : NSViewController 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/FileLayoutPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileLayoutPreferencesViewController.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 2/14/20. 6 | // Copyright © 2020 Tapas Software. All rights reserved. 7 | // 8 | 9 | #import "FileLayoutPreferencesViewController.h" 10 | #import "AppConstants+Categories.h" 11 | #import "SampleFileStructureGenerator.h" 12 | #import "BuildSettingExtractor.h" 13 | 14 | typedef NS_ENUM(NSUInteger, PreferencesInterfaceElementTags) { 15 | ProjectNameTextField = 101, 16 | SharedNameTextField = 102, 17 | SeparatorTextField = 103, 18 | DestinationFolderNameTextField = 104 19 | }; 20 | 21 | #pragma mark - 22 | 23 | @interface FileLayoutPreferencesViewController () 24 | @property (weak) IBOutlet NSOutlineView *outlineView; 25 | @property Item *rootItem; 26 | @end 27 | 28 | @implementation FileLayoutPreferencesViewController 29 | 30 | - (NSSize)preferredContentSize { 31 | return NSMakeSize(744.0, 352.0); 32 | } 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | NSArray *defaultKeys = @[TPSOutputFileNameShared, TPSOutputFileNameProject, TPSOutputFileNameSeparator, TPSTargetFoldersEnabled, TPSProjectFolderEnabled, TPSDestinationFolderName]; 37 | for (NSString *key in defaultKeys) { 38 | [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:key options:0 context:nil]; 39 | } 40 | } 41 | 42 | - (void)viewWillAppear { 43 | [super viewWillAppear]; 44 | [self updateOutlineView]; 45 | } 46 | 47 | - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { 48 | if ([fieldEditor.string isEqualToString:@""]) { 49 | if (control.tag == ProjectNameTextField) { 50 | fieldEditor.string = BuildSettingExtractor.defaultProjectConfigName; 51 | } 52 | else if (control.tag == SharedNameTextField) { 53 | fieldEditor.string = BuildSettingExtractor.defaultSharedConfigName; 54 | } 55 | else if (control.tag == DestinationFolderNameTextField) { 56 | fieldEditor.string = BuildSettingExtractor.defaultDestinationFolderName; 57 | } 58 | } 59 | return YES; 60 | } 61 | 62 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 63 | [self updateOutlineView]; 64 | } 65 | 66 | - (void)updateOutlineView { 67 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 68 | NSString *sharedName = [defaults stringForKey:TPSOutputFileNameShared]; 69 | NSString *projectName = [defaults stringForKey:TPSOutputFileNameProject]; 70 | NSString *separator = [defaults stringForKey:TPSOutputFileNameSeparator]; 71 | BOOL targetFoldersEnabled = [defaults boolForKey:TPSTargetFoldersEnabled]; 72 | BOOL projectFolderEnabled = [defaults boolForKey:TPSProjectFolderEnabled]; 73 | NSString *destinationFolderName = [defaults stringForKey:TPSDestinationFolderName]; 74 | 75 | self.rootItem = [[[SampleFileStructureGenerator alloc] init] exampleDataForProjectName:projectName sharedName:sharedName wordSeparator:separator useSubfolders:targetFoldersEnabled useProjectFolder:projectFolderEnabled destinationFolderName:destinationFolderName]; 76 | [self.outlineView reloadData]; 77 | [self.outlineView expandItem:nil expandChildren:YES]; 78 | } 79 | 80 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(nullable id)item { 81 | if (!item) { return self.rootItem.children.count; } 82 | else { return ((Item *)item).children.count; } 83 | } 84 | 85 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(nullable id)item{ 86 | if (!item) { return self.rootItem.children[index]; } 87 | else { return ((Item *)item).children[index]; } 88 | } 89 | 90 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { 91 | if (!item) { return self.rootItem.isFolder; } 92 | else { return ((Item *)item).isFolder; } 93 | } 94 | 95 | - (nullable NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(nullable NSTableColumn *)tableColumn item:(id)item { 96 | Item *outlineItem = item; 97 | NSTableCellView *cell = [outlineView makeViewWithIdentifier:@"OutlineCell" owner:nil]; 98 | cell.textField.stringValue = outlineItem.name; 99 | cell.imageView.image = outlineItem.icon; 100 | return cell; 101 | } 102 | 103 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item { 104 | return NO; 105 | } 106 | 107 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item { 108 | return NO; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/PreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.h 3 | // PreferencesPrototype 4 | // 5 | // Created by James Dempsey on 1/31/20. 6 | // Copyright © 2020 James Dempsey. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface PreferencesViewController : NSTabViewController 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/PreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.m 3 | // PreferencesPrototype 4 | // 5 | // Created by James Dempsey on 1/31/20. 6 | // Copyright © 2020 James Dempsey. All rights reserved. 7 | // 8 | 9 | #import "PreferencesViewController.h" 10 | 11 | @interface PreferencesViewController () 12 | @end 13 | 14 | @implementation PreferencesViewController 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | // Use SF Symbols for preference pane icons on macOS 11 and later 19 | if (@available(macOS 11.0, *)) { 20 | for (NSTabViewItem *item in self.tabViewItems) { 21 | if ([item.identifier isEqualToString:@"FileLayout"]) { 22 | item.image = [NSImage imageWithSystemSymbolName:@"doc.on.doc" accessibilityDescription:item.label]; 23 | } 24 | else if ([item.identifier isEqualToString:@"Contents"]) { 25 | item.image = [NSImage imageWithSystemSymbolName:@"hammer" accessibilityDescription:item.label]; 26 | } else { 27 | [NSException raise: NSInternalInconsistencyException format:@"Unexpected preference pane tab item"]; 28 | } 29 | } 30 | } 31 | } 32 | 33 | - (void)viewWillAppear { 34 | [super viewWillAppear]; 35 | [self updateWindowTitle]; 36 | } 37 | 38 | - (void)viewDidAppear { 39 | [super viewDidAppear]; 40 | [self.view.window makeFirstResponder:nil]; 41 | } 42 | 43 | - (void)updateWindowTitle { 44 | NSTabViewItem *selectedItem = self.tabViewItems[self.selectedTabViewItemIndex]; 45 | self.view.window.title = selectedItem.label; 46 | } 47 | 48 | - (NSRect)windowFrameForViewController:(NSViewController *)viewController { 49 | NSRect contentRect = { NSZeroPoint, viewController.preferredContentSize }; 50 | NSSize newSize = [self.view.window frameRectForContentRect:contentRect].size; 51 | NSRect windowFrame = self.view.window.frame; 52 | windowFrame.origin.y += windowFrame.size.height - newSize.height; 53 | windowFrame.size = newSize; 54 | return windowFrame; 55 | } 56 | 57 | - (void)transitionFromViewController:(NSViewController *)fromViewController toViewController:(NSViewController *)toViewController options:(NSViewControllerTransitionOptions)options completionHandler:(void (^)(void))completion { 58 | [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { 59 | [self updateWindowTitle]; 60 | NSRect windowFrame = [self windowFrameForViewController:toViewController]; 61 | [self.view.window.animator setFrame:windowFrame display:false]; 62 | NSViewControllerTransitionOptions options = NSViewControllerTransitionAllowUserInteraction | NSViewControllerTransitionCrossfade; 63 | [super transitionFromViewController:fromViewController toViewController:toViewController options: options completionHandler:completion]; 64 | }]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/SampleFileStructureGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // SampleFileStructureGenerator.h 3 | // SampleOutlineView 4 | // 5 | // Created by James Dempsey on 2/5/20. 6 | // Copyright © 2020 James Dempsey. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Item: NSObject 15 | @property(copy) NSString *name; 16 | @property NSMutableArray *children; 17 | @property (readonly) BOOL isFolder; 18 | @property (readonly) NSImage *icon; 19 | - (instancetype)initWithName:(NSString *)name; 20 | @end 21 | 22 | @interface SampleFileStructureGenerator : NSObject 23 | - (Item *)exampleDataForProjectName:(NSString *)projectName sharedName:(NSString *)sharedName wordSeparator:(NSString *)wordSeparator useSubfolders:(BOOL)useSubfolders useProjectFolder:(BOOL)useProjectFolder destinationFolderName:(NSString *)destinationFolderName; 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /BuildSettingExtractor/Preferences/SampleFileStructureGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // SampleFileStructureGenerator.m 3 | // SampleOutlineView 4 | // 5 | // Created by James Dempsey on 2/5/20. 6 | // Copyright © 2020 James Dempsey. All rights reserved. 7 | // 8 | 9 | #import "SampleFileStructureGenerator.h" 10 | 11 | @implementation Item 12 | - (instancetype)initWithName:(NSString *)name { 13 | self = [super init]; 14 | if (self) { 15 | self.name = name; 16 | self.children = [[NSMutableArray alloc] init]; 17 | } 18 | return self; 19 | } 20 | - (BOOL)isFolder { 21 | return self.children.count > 0; 22 | } 23 | - (NSImage *)icon { 24 | NSString *iconName = self.isFolder ? @"folder" : @"config_file"; 25 | return [NSImage imageNamed:iconName]; 26 | } 27 | @end 28 | 29 | @interface NSString (TPS_SpaceReplacement) 30 | -(NSString *)tps_stringReplacingSpacesWithString:(NSString *)separator; 31 | @end 32 | @implementation NSString (TPS_SpaceReplacement) 33 | -(NSString *)tps_stringReplacingSpacesWithString:(NSString *)separator { 34 | return [self stringByReplacingOccurrencesOfString:@" " withString:separator]; 35 | } 36 | @end 37 | 38 | @implementation SampleFileStructureGenerator 39 | - (Item *)exampleDataForProjectName:(NSString *)projectName sharedName:(NSString *)sharedName wordSeparator:(NSString *)wordSeparator useSubfolders:(BOOL)useSubfolders useProjectFolder:(BOOL)useProjectFolder destinationFolderName:(NSString *)destinationFolderName { 40 | 41 | NSArray *sortedConfigs = [@[[sharedName tps_stringReplacingSpacesWithString:wordSeparator], @"Debug", @"Release"] sortedArrayUsingSelector:@selector(localizedCompare:)]; 42 | NSArray *sortedTargets = [@[[projectName tps_stringReplacingSpacesWithString:wordSeparator], @"MyApp", @"MyAppTests"] sortedArrayUsingSelector:@selector(localizedCompare:)]; 43 | 44 | Item *rootItem = [[Item alloc] initWithName:@"Root"]; 45 | 46 | Item *destinationItem = [[Item alloc] initWithName:destinationFolderName]; 47 | [rootItem.children addObject:destinationItem]; 48 | 49 | for (NSString *targetName in sortedTargets) { 50 | BOOL skipProjectFolder = [targetName isEqualToString:projectName] && !useProjectFolder; 51 | if (useSubfolders && !skipProjectFolder) { 52 | Item *targetFolderItem = [[Item alloc] initWithName:targetName]; 53 | [self appendChildrenToItem:targetFolderItem withRootName:targetName configNames:sortedConfigs wordSeparator:wordSeparator]; 54 | [destinationItem.children addObject:targetFolderItem]; 55 | 56 | } else { 57 | [self appendChildrenToItem:destinationItem withRootName:targetName configNames:sortedConfigs wordSeparator:wordSeparator]; 58 | } 59 | } 60 | 61 | return rootItem; 62 | } 63 | 64 | - (NSString *)nameForRootName:(NSString *)rootName separator:(NSString *)separator configName:(NSString *)configName { 65 | return [NSString stringWithFormat:@"%@%@%@.xcconfig", rootName, separator, configName]; 66 | } 67 | 68 | - (void)appendChildrenToItem:(Item *)item withRootName:(NSString *)rootName configNames:(NSArray *)configNames wordSeparator:(NSString *)wordSeparator { 69 | for (NSString * configName in configNames) { 70 | NSString *fileName = [self nameForRootName:rootName separator:wordSeparator configName:configName]; 71 | Item *newItem = [[Item alloc] initWithName:fileName]; 72 | [item.children addObject:newItem]; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /BuildSettingExtractor/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BuildSettingExtractor/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 9/9/14. 6 | // Copyright (c) 2014 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/BuildSettingExtractor.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "A59CFD73-15EC-4B32-9336-A775AA56A72E", 5 | "name" : "Standard", 6 | "options" : { 7 | 8 | } 9 | }, 10 | { 11 | "id" : "4BF654CB-B6F5-489E-B5A8-99F06E583C46", 12 | "name" : "Thread Sanitizer", 13 | "options" : { 14 | "threadSanitizerEnabled" : true, 15 | "undefinedBehaviorSanitizerEnabled" : true 16 | } 17 | }, 18 | { 19 | "id" : "373DC580-59EC-476B-A670-9ADF153957DF", 20 | "name" : "Address Sanitizer", 21 | "options" : { 22 | "addressSanitizer" : { 23 | "enabled" : true 24 | }, 25 | "undefinedBehaviorSanitizerEnabled" : true 26 | } 27 | } 28 | ], 29 | "defaultOptions" : { 30 | "codeCoverage" : false, 31 | "targetForVariableExpansion" : { 32 | "containerPath" : "container:BuildSettingExtractor.xcodeproj", 33 | "identifier" : "53719E8E19BF4C6D005D3DE0", 34 | "name" : "BuildSettingExtractor" 35 | } 36 | }, 37 | "testTargets" : [ 38 | { 39 | "target" : { 40 | "containerPath" : "container:BuildSettingExtractor.xcodeproj", 41 | "identifier" : "53719EAF19BF4C6D005D3DE0", 42 | "name" : "BuildSettingExtractorTests" 43 | } 44 | } 45 | ], 46 | "version" : 1 47 | } 48 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/BuildSettingExtractorTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/BuildSettingExtractorTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BuildSettingExtractorTests.m 3 | // BuildSettingExtractorTests 4 | // 5 | // Created by James Dempsey on 9/9/14. 6 | // Copyright (c) 2014 Tapas Software. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | #import "BuildSettingExtractor.h" 11 | #import "BuildSettingCommentGenerator.h" 12 | #import "BuildSettingInfoSource.h" 13 | #import "Constants+Categories.h" 14 | 15 | @interface NSObject (BuildSettingExtractorMethods) 16 | - (NSDictionary *)buildSettingsByConfigurationForConfigurations:(NSArray *)buildConfigurations; 17 | @end 18 | 19 | @interface BuildSettingExtractorTests : XCTestCase 20 | @end 21 | 22 | @implementation BuildSettingExtractorTests 23 | 24 | - (void)testThreeBuildConfigurations 25 | { 26 | NSURL *testFileURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"ThreeBuildConfigs" withExtension:@"plist"]; 27 | NSDictionary *testPlist = [NSDictionary dictionaryWithContentsOfURL:testFileURL]; 28 | 29 | NSArray *buildConfigurations = testPlist[@"buildConfigurations"]; 30 | NSDictionary *expectedBuildSettings = testPlist[@"expectedBuildSettings"]; 31 | 32 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 33 | 34 | NSDictionary *buildSettings = [extractor buildSettingsByConfigurationForConfigurations:buildConfigurations]; 35 | 36 | NSDictionary *sharedBuildSettings = buildSettings[extractor.sharedConfigName]; 37 | 38 | XCTAssert([sharedBuildSettings isEqualToDictionary:expectedBuildSettings], @"Build settings should match"); 39 | } 40 | 41 | - (void)testDictionaryBuildSettingsCategory 42 | { 43 | NSDictionary *dictionaryWithBuildSettings = @{ @"Shared": @"COPY_PHASE_STRIP = NO", @"Release": @"COPY_PHASE_STRIP = NO", @"Debug": @"COPY_PHASE_STRIP = NO" }; 44 | XCTAssertTrue(dictionaryWithBuildSettings.tps_containsBuildSettings); 45 | 46 | NSDictionary *dictionaryWithMinimalBuildSettings = @{ @"Shared": @"", @"Release": @"", @"Debug": @"COPY_PHASE_STRIP = NO" }; 47 | XCTAssertTrue(dictionaryWithMinimalBuildSettings.tps_containsBuildSettings); 48 | 49 | NSDictionary *dictionaryWithoutBuildSettings = @{ @"Shared": @"", @"Release": @"", @"Debug": @"" }; 50 | XCTAssertFalse(dictionaryWithoutBuildSettings.tps_containsBuildSettings); 51 | 52 | NSDictionary *badDictionary = @{@"Shared":@"", @"Release":@"", @"Debug":[NSDate date] }; 53 | BOOL result = NO; 54 | XCTAssertThrows(result = badDictionary.tps_containsBuildSettings); 55 | } 56 | 57 | - (void)testLoadingBuildSettingInfo 58 | { 59 | NSError *error = nil; 60 | 61 | // This test assumes Xcode is installed at /Applications/Xcode.app or /Applications/Xcode-beta.app 62 | BuildSettingInfoSource *infoSource = [BuildSettingInfoSource resolvedBuildSettingInfoSourceWithStyle:BuildSettingInfoSourceStyleStandard customURL:nil error:&error]; 63 | XCTAssertNotNil(infoSource); 64 | XCTAssertNil(error); 65 | 66 | NSString *activityName = [NSString stringWithFormat:@"Load build setting info from info source '%@' version '%ld'", infoSource.resolvedURL, (long)infoSource.resolvedVersion]; 67 | [XCTContext runActivityNamed:activityName block:^(id _Nonnull activity) { 68 | NSError *activityError = nil; 69 | BuildSettingCommentGenerator *commentGenerator = [[BuildSettingCommentGenerator alloc] initWithBuildSettingInfoSource:infoSource]; 70 | BOOL success = [commentGenerator loadBuildSettingInfo:&activityError]; 71 | XCTAssertTrue(success); 72 | XCTAssertNil(activityError); 73 | }]; 74 | } 75 | 76 | - (void)testBadProjectURL 77 | { 78 | NSError *fatalError = nil; 79 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 80 | 81 | NSURL *badURL = [NSURL fileURLWithPath:[@"~/Documents/BadProjectURL.xcodeproj" stringByExpandingTildeInPath]]; 82 | 83 | NSArray *nonFatalErrors = [extractor extractBuildSettingsFromProject:badURL error:&fatalError]; 84 | 85 | XCTAssertNil(nonFatalErrors); 86 | XCTAssertNotNil(fatalError); 87 | XCTAssertEqual(fatalError.code, 260); 88 | 89 | } 90 | 91 | // Reads the project.pbxproj file inside of BadProject.xcodeproj.test. 92 | // A stripped down xcodeproj bundle with a malformed project.pbxproj plist. 93 | - (void)testMalformedProjectFile 94 | { 95 | NSError *fatalError = nil; 96 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 97 | 98 | NSURL *badProjectURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"BadProject.xcodeproj" withExtension:@"test"]; 99 | 100 | NSArray *nonFatalErrors = [extractor extractBuildSettingsFromProject:badProjectURL error:&fatalError]; 101 | 102 | XCTAssertNil(nonFatalErrors); 103 | XCTAssertNotNil(fatalError); 104 | XCTAssertEqual(fatalError.code, 3840); // "Junk after plist at line 545" 105 | 106 | } 107 | 108 | // Reads the project.pbxproj file inside of BadVersionNumber.xcodeproj.test. 109 | // A stripped down xcodeproj bundle with its project version set to "Xcode 9999.9" 110 | - (void)testUnsupportedProjectVersion 111 | { 112 | NSError *fatalError = nil; 113 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 114 | 115 | NSURL *badProjectURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"BadVersionNumber.xcodeproj" withExtension:@"test"]; 116 | 117 | NSArray *nonFatalErrors = [extractor extractBuildSettingsFromProject:badProjectURL error:&fatalError]; 118 | 119 | XCTAssertNil(nonFatalErrors); 120 | XCTAssertNotNil(fatalError); 121 | XCTAssertEqual(fatalError.domain, TPSBuildSettingExtractorErrorDomain); 122 | XCTAssertEqual(fatalError.code, UnsupportedXcodeVersion); 123 | // "Unable to extract build settings from project ‘BadVersionNumber.xcodeproj" 124 | // "Project file format version ‘Xcode 9999.9’ is not supported." 125 | 126 | } 127 | 128 | // Reads the project.pbxproj file inside of ConflictingName.xcodeproj.test. 129 | // A stripped down xcodeproj bundle with a conflicting target name "MyTarget". 130 | - (void)testConflictingProjectName 131 | { 132 | NSError *fatalError = nil; 133 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 134 | extractor.projectConfigName = @"MyTarget"; 135 | 136 | NSURL *badProjectURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"ConflictingName.xcodeproj" withExtension:@"test"]; 137 | 138 | NSArray *nonFatalErrors = [extractor extractBuildSettingsFromProject:badProjectURL error:&fatalError]; 139 | 140 | XCTAssertNotNil(nonFatalErrors); 141 | XCTAssertEqual(nonFatalErrors.count, 1); 142 | 143 | NSError *firstError = nonFatalErrors.firstObject; 144 | XCTAssertNotNil(firstError); 145 | XCTAssertEqual(firstError.domain, TPSBuildSettingExtractorErrorDomain); 146 | XCTAssertEqual(firstError.code, ProjectSettingsNamingConflict); 147 | // "Project settings filename conflict." 148 | // "The target 'MyTarget' has the same name as the project name set in Preferences." 149 | 150 | XCTAssertNil(fatalError); 151 | 152 | } 153 | 154 | // Reads the project.pbxproj file inside of EmptySettings.xcodeproj.test. 155 | // A stripped down xcodeproj bundle with no build settings. 156 | - (void)testEmptySettings 157 | { 158 | NSError *fatalError = nil; 159 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 160 | 161 | NSURL *emptySettingsProjectURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"EmptySettings.xcodeproj" withExtension:@"test"]; 162 | 163 | NSArray *nonFatalErrors = [extractor extractBuildSettingsFromProject:emptySettingsProjectURL error:&fatalError]; 164 | 165 | XCTAssertNil(nonFatalErrors); 166 | XCTAssertNotNil(fatalError); 167 | XCTAssertEqual(fatalError.domain, TPSBuildSettingExtractorErrorDomain); 168 | XCTAssertEqual(fatalError.code, NoSettingsFoundInProjectFile); 169 | // "No settings were found in the project 'EmptySettings.xcodeproj.test'." 170 | // "The project may already be using .xcconfig files for its build settings." 171 | // "No xcconfig files will be written." 172 | 173 | } 174 | 175 | - (void)testWritingWithoutExtracting 176 | { 177 | NSError *fatalError = nil; 178 | BuildSettingExtractor *extractor = [[BuildSettingExtractor alloc] init]; 179 | 180 | NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()]; 181 | 182 | XCTAssertThrows([extractor writeConfigFilesToDestinationFolder:tempFolder error:&fatalError]); 183 | } 184 | 185 | - (void)testUnresolvedBuildSettingInfoSource { 186 | 187 | // NOTE: -1 is special undeclared style for testing failure to resolve an info source. 188 | // It causes BuildSettingInfoSource to use invalid paths for Xcode and Xcode-beta. 189 | BuildSettingInfoSourceStyle failureStyle = -1; 190 | 191 | NSError *error = nil; 192 | BuildSettingInfoSource *source = [BuildSettingInfoSource resolvedBuildSettingInfoSourceWithStyle:failureStyle customURL:nil error:&error]; 193 | XCTAssertNil(source); 194 | XCTAssertNotNil(error); 195 | XCTAssertEqual(error.domain, TPSBuildSettingExtractorErrorDomain); 196 | XCTAssertEqual(error.code, BuildSettingInfoSourceNotFound); 197 | } 198 | 199 | - (void)testBuildSettingFormatting { 200 | NSURL *testFileURL = [[NSBundle bundleForClass:[BuildSettingExtractorTests class]] URLForResource:@"FormattingTestData" withExtension:@"plist"]; 201 | NSDictionary *testPlist = [NSDictionary dictionaryWithContentsOfURL:testFileURL]; 202 | 203 | NSArray *formattingTests = testPlist[@"formattingTests"]; 204 | 205 | NSDictionary *testSettings = @{@"CLANG_WARN__DUPLICATE_METHOD_MATCH":@"YES", 206 | @"COPY_PHASE_STRIP":@"YES", 207 | @"DEBUG_INFORMATION_FORMAT":@"dwarf-with-dsym", 208 | @"ENABLE_NS_ASSERTIONS":@"NO"}; 209 | 210 | for (NSDictionary *test in formattingTests) { 211 | NSNumber *includeBuildSettingInfoValue = [test valueForKey:@"includeBuildSettingInfo"]; 212 | XCTAssertNotNil(includeBuildSettingInfoValue); 213 | BOOL includeBuildSettingsInfo = [includeBuildSettingInfoValue boolValue]; 214 | 215 | NSNumber *alignBuildSettingsValue = [test valueForKey:@"alignSettings"]; 216 | XCTAssertNotNil(alignBuildSettingsValue); 217 | BOOL alignBuildSettings = [alignBuildSettingsValue boolValue]; 218 | 219 | NSNumber *linesBetweenSettingsValue = [test valueForKey:@"linesBetweenSettings"]; 220 | XCTAssertNotNil(linesBetweenSettingsValue); 221 | NSInteger linesBetweenSettings = [linesBetweenSettingsValue integerValue]; 222 | 223 | NSString *expectedResult = [test valueForKey:@"expectedResult"]; 224 | XCTAssertNotNil(expectedResult); 225 | 226 | NSString *testString = [BuildSettingExtractor exampleBuildFormattingStringForSettings:testSettings includeBuildSettingInfoComments:includeBuildSettingsInfo alignBuildSettingValues:alignBuildSettings linesBetweenSettings:linesBetweenSettings]; 227 | 228 | XCTAssertEqualObjects(testString, expectedResult); 229 | 230 | } 231 | 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/TestFiles/ConflictingName.xcodeproj.test/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 53FA8808234103D500F51EC3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA8807234103D500F51EC3 /* AppDelegate.m */; }; 11 | 53FA880B234103D500F51EC3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA880A234103D500F51EC3 /* ViewController.m */; }; 12 | 53FA880D234103D700F51EC3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53FA880C234103D700F51EC3 /* Assets.xcassets */; }; 13 | 53FA8810234103D700F51EC3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53FA880E234103D700F51EC3 /* Main.storyboard */; }; 14 | 53FA8813234103D700F51EC3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA8812234103D700F51EC3 /* main.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 53FA8803234103D500F51EC3 /* MyTarget.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyTarget.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 53FA8806234103D500F51EC3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 20 | 53FA8807234103D500F51EC3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 21 | 53FA8809234103D500F51EC3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 22 | 53FA880A234103D500F51EC3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 23 | 53FA880C234103D700F51EC3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 53FA880F234103D700F51EC3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 53FA8811234103D700F51EC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 53FA8812234103D700F51EC3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27 | 53FA8814234103D700F51EC3 /* MyTarget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MyTarget.entitlements; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 53FA8800234103D500F51EC3 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 53FA87FA234103D500F51EC3 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 53FA8805234103D500F51EC3 /* MyTarget */, 45 | 53FA8804234103D500F51EC3 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 53FA8804234103D500F51EC3 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 53FA8803234103D500F51EC3 /* MyTarget.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 53FA8805234103D500F51EC3 /* MyTarget */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 53FA8806234103D500F51EC3 /* AppDelegate.h */, 61 | 53FA8807234103D500F51EC3 /* AppDelegate.m */, 62 | 53FA8809234103D500F51EC3 /* ViewController.h */, 63 | 53FA880A234103D500F51EC3 /* ViewController.m */, 64 | 53FA880C234103D700F51EC3 /* Assets.xcassets */, 65 | 53FA880E234103D700F51EC3 /* Main.storyboard */, 66 | 53FA8811234103D700F51EC3 /* Info.plist */, 67 | 53FA8812234103D700F51EC3 /* main.m */, 68 | 53FA8814234103D700F51EC3 /* MyTarget.entitlements */, 69 | ); 70 | path = MyTarget; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 53FA8802234103D500F51EC3 /* MyTarget */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 53FA8817234103D700F51EC3 /* Build configuration list for PBXNativeTarget "MyTarget" */; 79 | buildPhases = ( 80 | 53FA87FF234103D500F51EC3 /* Sources */, 81 | 53FA8800234103D500F51EC3 /* Frameworks */, 82 | 53FA8801234103D500F51EC3 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = MyTarget; 89 | productName = MyTarget; 90 | productReference = 53FA8803234103D500F51EC3 /* MyTarget.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 53FA87FB234103D500F51EC3 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastUpgradeCheck = 1100; 100 | ORGANIZATIONNAME = Test; 101 | TargetAttributes = { 102 | 53FA8802234103D500F51EC3 = { 103 | CreatedOnToolsVersion = 11.0; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 53FA87FE234103D500F51EC3 /* Build configuration list for PBXProject "MyTarget" */; 108 | compatibilityVersion = "Xcode 9.3"; 109 | developmentRegion = en; 110 | hasScannedForEncodings = 0; 111 | knownRegions = ( 112 | en, 113 | Base, 114 | ); 115 | mainGroup = 53FA87FA234103D500F51EC3; 116 | productRefGroup = 53FA8804234103D500F51EC3 /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 53FA8802234103D500F51EC3 /* MyTarget */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 53FA8801234103D500F51EC3 /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 53FA880D234103D700F51EC3 /* Assets.xcassets in Resources */, 131 | 53FA8810234103D700F51EC3 /* Main.storyboard in Resources */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXResourcesBuildPhase section */ 136 | 137 | /* Begin PBXSourcesBuildPhase section */ 138 | 53FA87FF234103D500F51EC3 /* Sources */ = { 139 | isa = PBXSourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 53FA880B234103D500F51EC3 /* ViewController.m in Sources */, 143 | 53FA8813234103D700F51EC3 /* main.m in Sources */, 144 | 53FA8808234103D500F51EC3 /* AppDelegate.m in Sources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin PBXVariantGroup section */ 151 | 53FA880E234103D700F51EC3 /* Main.storyboard */ = { 152 | isa = PBXVariantGroup; 153 | children = ( 154 | 53FA880F234103D700F51EC3 /* Base */, 155 | ); 156 | name = Main.storyboard; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXVariantGroup section */ 160 | 161 | /* Begin XCBuildConfiguration section */ 162 | 53FA8815234103D700F51EC3 /* Debug */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | CLANG_ANALYZER_NONNULL = YES; 167 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 168 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 169 | CLANG_CXX_LIBRARY = "libc++"; 170 | CLANG_ENABLE_MODULES = YES; 171 | CLANG_ENABLE_OBJC_ARC = YES; 172 | CLANG_ENABLE_OBJC_WEAK = YES; 173 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 174 | CLANG_WARN_BOOL_CONVERSION = YES; 175 | CLANG_WARN_COMMA = YES; 176 | CLANG_WARN_CONSTANT_CONVERSION = YES; 177 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 178 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 179 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 180 | CLANG_WARN_EMPTY_BODY = YES; 181 | CLANG_WARN_ENUM_CONVERSION = YES; 182 | CLANG_WARN_INFINITE_RECURSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 186 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 188 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 189 | CLANG_WARN_STRICT_PROTOTYPES = YES; 190 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 191 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 192 | CLANG_WARN_UNREACHABLE_CODE = YES; 193 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 194 | COPY_PHASE_STRIP = NO; 195 | DEBUG_INFORMATION_FORMAT = dwarf; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | ENABLE_TESTABILITY = YES; 198 | GCC_C_LANGUAGE_STANDARD = gnu11; 199 | GCC_DYNAMIC_NO_PIC = NO; 200 | GCC_NO_COMMON_BLOCKS = YES; 201 | GCC_OPTIMIZATION_LEVEL = 0; 202 | GCC_PREPROCESSOR_DEFINITIONS = ( 203 | "DEBUG=1", 204 | "$(inherited)", 205 | ); 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | MACOSX_DEPLOYMENT_TARGET = 10.14; 213 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 214 | MTL_FAST_MATH = YES; 215 | ONLY_ACTIVE_ARCH = YES; 216 | SDKROOT = macosx; 217 | }; 218 | name = Debug; 219 | }; 220 | 53FA8816234103D700F51EC3 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_ENABLE_OBJC_WEAK = YES; 231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 232 | CLANG_WARN_BOOL_CONVERSION = YES; 233 | CLANG_WARN_COMMA = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INFINITE_RECURSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | MACOSX_DEPLOYMENT_TARGET = 10.14; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = macosx; 268 | }; 269 | name = Release; 270 | }; 271 | 53FA8818234103D700F51EC3 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 275 | CODE_SIGN_ENTITLEMENTS = MyTarget/MyTarget.entitlements; 276 | CODE_SIGN_STYLE = Automatic; 277 | COMBINE_HIDPI_IMAGES = YES; 278 | INFOPLIST_FILE = MyTarget/Info.plist; 279 | LD_RUNPATH_SEARCH_PATHS = ( 280 | "$(inherited)", 281 | "@executable_path/../Frameworks", 282 | ); 283 | PRODUCT_BUNDLE_IDENTIFIER = com.example.test.MyTarget; 284 | PRODUCT_NAME = "$(TARGET_NAME)"; 285 | }; 286 | name = Debug; 287 | }; 288 | 53FA8819234103D700F51EC3 /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | CODE_SIGN_ENTITLEMENTS = MyTarget/MyTarget.entitlements; 293 | CODE_SIGN_STYLE = Automatic; 294 | COMBINE_HIDPI_IMAGES = YES; 295 | INFOPLIST_FILE = MyTarget/Info.plist; 296 | LD_RUNPATH_SEARCH_PATHS = ( 297 | "$(inherited)", 298 | "@executable_path/../Frameworks", 299 | ); 300 | PRODUCT_BUNDLE_IDENTIFIER = com.example.test.MyTarget; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | }; 303 | name = Release; 304 | }; 305 | /* End XCBuildConfiguration section */ 306 | 307 | /* Begin XCConfigurationList section */ 308 | 53FA87FE234103D500F51EC3 /* Build configuration list for PBXProject "MyTarget" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | 53FA8815234103D700F51EC3 /* Debug */, 312 | 53FA8816234103D700F51EC3 /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | 53FA8817234103D700F51EC3 /* Build configuration list for PBXNativeTarget "MyTarget" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 53FA8818234103D700F51EC3 /* Debug */, 321 | 53FA8819234103D700F51EC3 /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | /* End XCConfigurationList section */ 327 | }; 328 | rootObject = 53FA87FB234103D500F51EC3 /* Project object */; 329 | } 330 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/TestFiles/EmptySettings.xcodeproj.test/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 53FA8808234103D500F51EC3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA8807234103D500F51EC3 /* AppDelegate.m */; }; 11 | 53FA880B234103D500F51EC3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA880A234103D500F51EC3 /* ViewController.m */; }; 12 | 53FA880D234103D700F51EC3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53FA880C234103D700F51EC3 /* Assets.xcassets */; }; 13 | 53FA8810234103D700F51EC3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53FA880E234103D700F51EC3 /* Main.storyboard */; }; 14 | 53FA8813234103D700F51EC3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 53FA8812234103D700F51EC3 /* main.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 53FA8803234103D500F51EC3 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 53FA8806234103D500F51EC3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 20 | 53FA8807234103D500F51EC3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 21 | 53FA8809234103D500F51EC3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 22 | 53FA880A234103D500F51EC3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 23 | 53FA880C234103D700F51EC3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 53FA880F234103D700F51EC3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 53FA8811234103D700F51EC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 53FA8812234103D700F51EC3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27 | 53FA8814234103D700F51EC3 /* MyTarget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MyTarget.entitlements; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 53FA8800234103D500F51EC3 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 53FA87FA234103D500F51EC3 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 53FA8805234103D500F51EC3 /* MyTarget */, 45 | 53FA8804234103D500F51EC3 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 53FA8804234103D500F51EC3 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 53FA8803234103D500F51EC3 /* .app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 53FA8805234103D500F51EC3 /* MyTarget */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 53FA8806234103D500F51EC3 /* AppDelegate.h */, 61 | 53FA8807234103D500F51EC3 /* AppDelegate.m */, 62 | 53FA8809234103D500F51EC3 /* ViewController.h */, 63 | 53FA880A234103D500F51EC3 /* ViewController.m */, 64 | 53FA880C234103D700F51EC3 /* Assets.xcassets */, 65 | 53FA880E234103D700F51EC3 /* Main.storyboard */, 66 | 53FA8811234103D700F51EC3 /* Info.plist */, 67 | 53FA8812234103D700F51EC3 /* main.m */, 68 | 53FA8814234103D700F51EC3 /* MyTarget.entitlements */, 69 | ); 70 | path = MyTarget; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 53FA8802234103D500F51EC3 /* MyTarget */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 53FA8817234103D700F51EC3 /* Build configuration list for PBXNativeTarget "MyTarget" */; 79 | buildPhases = ( 80 | 53FA87FF234103D500F51EC3 /* Sources */, 81 | 53FA8800234103D500F51EC3 /* Frameworks */, 82 | 53FA8801234103D500F51EC3 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = MyTarget; 89 | productName = MyTarget; 90 | productReference = 53FA8803234103D500F51EC3 /* .app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 53FA87FB234103D500F51EC3 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastUpgradeCheck = 1100; 100 | ORGANIZATIONNAME = Test; 101 | TargetAttributes = { 102 | 53FA8802234103D500F51EC3 = { 103 | CreatedOnToolsVersion = 11.0; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 53FA87FE234103D500F51EC3 /* Build configuration list for PBXProject "MyTarget" */; 108 | compatibilityVersion = "Xcode 9.3"; 109 | developmentRegion = en; 110 | hasScannedForEncodings = 0; 111 | knownRegions = ( 112 | en, 113 | Base, 114 | ); 115 | mainGroup = 53FA87FA234103D500F51EC3; 116 | productRefGroup = 53FA8804234103D500F51EC3 /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 53FA8802234103D500F51EC3 /* MyTarget */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 53FA8801234103D500F51EC3 /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 53FA880D234103D700F51EC3 /* Assets.xcassets in Resources */, 131 | 53FA8810234103D700F51EC3 /* Main.storyboard in Resources */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXResourcesBuildPhase section */ 136 | 137 | /* Begin PBXSourcesBuildPhase section */ 138 | 53FA87FF234103D500F51EC3 /* Sources */ = { 139 | isa = PBXSourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 53FA880B234103D500F51EC3 /* ViewController.m in Sources */, 143 | 53FA8813234103D700F51EC3 /* main.m in Sources */, 144 | 53FA8808234103D500F51EC3 /* AppDelegate.m in Sources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin PBXVariantGroup section */ 151 | 53FA880E234103D700F51EC3 /* Main.storyboard */ = { 152 | isa = PBXVariantGroup; 153 | children = ( 154 | 53FA880F234103D700F51EC3 /* Base */, 155 | ); 156 | name = Main.storyboard; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXVariantGroup section */ 160 | 161 | /* Begin XCBuildConfiguration section */ 162 | 53FA8815234103D700F51EC3 /* Debug */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | }; 166 | name = Debug; 167 | }; 168 | 53FA8816234103D700F51EC3 /* Release */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | }; 172 | name = Release; 173 | }; 174 | 53FA8818234103D700F51EC3 /* Debug */ = { 175 | isa = XCBuildConfiguration; 176 | buildSettings = { 177 | }; 178 | name = Debug; 179 | }; 180 | 53FA8819234103D700F51EC3 /* Release */ = { 181 | isa = XCBuildConfiguration; 182 | buildSettings = { 183 | }; 184 | name = Release; 185 | }; 186 | /* End XCBuildConfiguration section */ 187 | 188 | /* Begin XCConfigurationList section */ 189 | 53FA87FE234103D500F51EC3 /* Build configuration list for PBXProject "MyTarget" */ = { 190 | isa = XCConfigurationList; 191 | buildConfigurations = ( 192 | 53FA8815234103D700F51EC3 /* Debug */, 193 | 53FA8816234103D700F51EC3 /* Release */, 194 | ); 195 | defaultConfigurationIsVisible = 0; 196 | defaultConfigurationName = Release; 197 | }; 198 | 53FA8817234103D700F51EC3 /* Build configuration list for PBXNativeTarget "MyTarget" */ = { 199 | isa = XCConfigurationList; 200 | buildConfigurations = ( 201 | 53FA8818234103D700F51EC3 /* Debug */, 202 | 53FA8819234103D700F51EC3 /* Release */, 203 | ); 204 | defaultConfigurationIsVisible = 0; 205 | defaultConfigurationName = Release; 206 | }; 207 | /* End XCConfigurationList section */ 208 | }; 209 | rootObject = 53FA87FB234103D500F51EC3 /* Project object */; 210 | } 211 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/TestFiles/FormattingTestData.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | formattingTests 6 | 7 | 8 | alignSettings 9 | 10 | linesBetweenSettings 11 | 3 12 | includeBuildSettingInfo 13 | 14 | expectedResult 15 | // Duplicate Method Definitions 16 | // 17 | // Warn about declaring the same method more than once within the same `@interface`. 18 | 19 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 20 | 21 | 22 | 23 | // Strip Debug Symbols During Copy 24 | // 25 | // Specifies whether binary files that are copied during the build, such as in a Copy 26 | // Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. 27 | // It does not cause the linked product of a target to be stripped—use 28 | // `STRIP_INSTALLED_PRODUCT` for that. 29 | 30 | COPY_PHASE_STRIP = YES 31 | 32 | 33 | 34 | // Debug Information Format 35 | // 36 | // The type of debug information to produce. 37 | // 38 | // * DWARF: Object files and linked products will use DWARF as the debug information 39 | // format. [dwarf] 40 | // * DWARF with dSYM File: Object files and linked products will use DWARF as the debug 41 | // information format, and Xcode will also produce a dSYM file containing the debug 42 | // information from the individual object files (except that a dSYM file is not needed 43 | // and will not be created for static library or object file products). [dwarf-with-dsym] 44 | 45 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 46 | 47 | 48 | 49 | // Enable Foundation Assertions 50 | // 51 | // Controls whether assertion logic provided by `NSAssert` is included in the 52 | // preprocessed source code or is elided during preprocessing. Disabling assertions can 53 | // improve code performance. 54 | 55 | ENABLE_NS_ASSERTIONS = NO 56 | 57 | 58 | 59 | alignSettings 60 | 61 | linesBetweenSettings 62 | 0 63 | includeBuildSettingInfo 64 | 65 | expectedResult 66 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 67 | COPY_PHASE_STRIP = YES 68 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 69 | ENABLE_NS_ASSERTIONS = NO 70 | 71 | 72 | 73 | alignSettings 74 | 75 | linesBetweenSettings 76 | 0 77 | includeBuildSettingInfo 78 | 79 | expectedResult 80 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 81 | COPY_PHASE_STRIP = YES 82 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 83 | ENABLE_NS_ASSERTIONS = NO 84 | 85 | 86 | 87 | alignSettings 88 | 89 | linesBetweenSettings 90 | 1 91 | includeBuildSettingInfo 92 | 93 | expectedResult 94 | // Duplicate Method Definitions 95 | // 96 | // Warn about declaring the same method more than once within the same `@interface`. 97 | 98 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 99 | 100 | // Strip Debug Symbols During Copy 101 | // 102 | // Specifies whether binary files that are copied during the build, such as in a Copy 103 | // Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. 104 | // It does not cause the linked product of a target to be stripped—use 105 | // `STRIP_INSTALLED_PRODUCT` for that. 106 | 107 | COPY_PHASE_STRIP = YES 108 | 109 | // Debug Information Format 110 | // 111 | // The type of debug information to produce. 112 | // 113 | // * DWARF: Object files and linked products will use DWARF as the debug information 114 | // format. [dwarf] 115 | // * DWARF with dSYM File: Object files and linked products will use DWARF as the debug 116 | // information format, and Xcode will also produce a dSYM file containing the debug 117 | // information from the individual object files (except that a dSYM file is not needed 118 | // and will not be created for static library or object file products). [dwarf-with-dsym] 119 | 120 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 121 | 122 | // Enable Foundation Assertions 123 | // 124 | // Controls whether assertion logic provided by `NSAssert` is included in the 125 | // preprocessed source code or is elided during preprocessing. Disabling assertions can 126 | // improve code performance. 127 | 128 | ENABLE_NS_ASSERTIONS = NO 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /BuildSettingExtractorTests/TestFiles/ThreeBuildConfigs.plist: -------------------------------------------------------------------------------- 1 | { 2 | buildConfigurations = ( 3 | { 4 | isa = XCBuildConfiguration; 5 | buildSettings = { 6 | ALWAYS_SEARCH_USER_PATHS = NO; 7 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 8 | CLANG_ENABLE_OBJC_ARC = YES; 9 | COPY_PHASE_STRIP = NO; 10 | GCC_C_LANGUAGE_STANDARD = gnu99; 11 | GCC_DYNAMIC_NO_PIC = NO; 12 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 13 | GCC_OPTIMIZATION_LEVEL = 0; 14 | GCC_PREPROCESSOR_DEFINITIONS = ( 15 | "DEBUG=1", 16 | "$(inherited)", 17 | ); 18 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 19 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 20 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 21 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 22 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 23 | GCC_WARN_UNUSED_VARIABLE = YES; 24 | MACOSX_DEPLOYMENT_TARGET = 10.7; 25 | ONLY_ACTIVE_ARCH = YES; 26 | SDKROOT = macosx; 27 | }; 28 | name = Debug; 29 | }, 30 | { 31 | isa = XCBuildConfiguration; 32 | buildSettings = { 33 | ALWAYS_SEARCH_USER_PATHS = NO; 34 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 35 | CLANG_ENABLE_OBJC_ARC = YES; 36 | COPY_PHASE_STRIP = YES; 37 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 38 | GCC_C_LANGUAGE_STANDARD = gnu99; 39 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 40 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 41 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 42 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 43 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 44 | GCC_WARN_UNUSED_VARIABLE = YES; 45 | MACOSX_DEPLOYMENT_TARGET = 10.7; 46 | SDKROOT = macosx; 47 | }; 48 | name = Release; 49 | }, 50 | { 51 | isa = XCBuildConfiguration; 52 | buildSettings = { 53 | ALWAYS_SEARCH_USER_PATHS = NO; 54 | SDKROOT = macosx; 55 | MACOSX_DEPLOYMENT_TARGET = 10.7; 56 | GCC_WARN_UNUSED_VARIABLE = NO; 57 | /* ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 58 | CLANG_ENABLE_OBJC_ARC = YES; 59 | COPY_PHASE_STRIP = YES; 60 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 61 | GCC_C_LANGUAGE_STANDARD = gnu99; 62 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 63 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 64 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 65 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 66 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 67 | */ 68 | }; 69 | name = Other; 70 | } 71 | ); 72 | 73 | expectedBuildSettings = { 74 | ALWAYS_SEARCH_USER_PATHS = NO; 75 | SDKROOT = macosx; 76 | MACOSX_DEPLOYMENT_TARGET = 10.7; 77 | /* ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 78 | CLANG_ENABLE_OBJC_ARC = YES; 79 | COPY_PHASE_STRIP = YES; 80 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 81 | GCC_C_LANGUAGE_STANDARD = gnu99; 82 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 83 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 84 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 85 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 86 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 87 | GCC_WARN_UNUSED_VARIABLE = YES; 88 | MACOSX_DEPLOYMENT_TARGET = 10.7; 89 | SDKROOT = macosx; 90 | */ 91 | }; 92 | } -------------------------------------------------------------------------------- /BuildSettingExtractorTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Config/AppConfig.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfig.xcconfig 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 11/11/22. 6 | // Copyright © 2022 Tapas Software. All rights reserved. 7 | // 8 | 9 | // Configuration settings file format documentation can be found at: 10 | // https://help.apple.com/xcode/#/dev745c5c974 11 | 12 | // Product Bundle Identifier 13 | 14 | // A string that uniquely identifies the bundle. The string should be in 15 | // reverse DNS format using only alphanumeric characters (A-Z, a-z, 0-9), 16 | // the dot (.), and the hyphen (-). This value is used as the 17 | // CFBundleIdentifier in the Info.plist of the built bundle. 18 | 19 | PRODUCT_BUNDLE_IDENTIFIER = net.tapas-software.${PRODUCT_NAME:rfc1034identifier} 20 | 21 | // Allows private app config info to be set and overridden for local dev builds 22 | #include? "/Users/Shared/git/xcconfig-private/CodeSigning.xcconfig" 23 | 24 | // Allows private app config information to be set and overridden for CI builds 25 | #include? "PrivateAppConfig.xcconfig" 26 | 27 | -------------------------------------------------------------------------------- /Config/ProjectConfig.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // BSE-CodeSigning.xcconfig 3 | // BuildSettingExtractor 4 | // 5 | // Created by James Dempsey on 10/2/19. 6 | // Copyright © 2019 Tapas Software. All rights reserved. 7 | // 8 | 9 | // Configuration settings file format documentation can be found at: 10 | // https://help.apple.com/xcode/#/dev745c5c974 11 | 12 | 13 | // This file optionally includes an xcconfig file outside of the main 14 | // github repository. This allows for codesigning the app without needing 15 | // to include private values, such as team identifiers, in the public 16 | // repository. 17 | 18 | // The optional #include? directive allows the open source project to compile 19 | // when the private xcconfig file is not found. 20 | 21 | #include? "/usr/local/etc/xcconfig-private/CodeSigning.xcconfig" 22 | #include? "/Users/Shared/git/xcconfig-private/CodeSigning.xcconfig" 23 | 24 | 25 | // Code Sign Style 26 | 27 | // This setting specifies the method used to acquire and locate signing assets. 28 | // Choose `Automatic` to let Xcode automatically create and update profiles, 29 | // app IDs, and certificates. Choose `Manual` to create and update these yourself 30 | // on the developer website. 31 | 32 | // Set to Automatic mode. 33 | 34 | CODE_SIGN_STYLE = Automatic 35 | 36 | 37 | // Code Signing Identity 38 | 39 | // The name, also known as the common name, of a valid code-signing certificate 40 | // in a keychain within your keychain path. A missing or invalid certificate 41 | // will cause a build error. 42 | 43 | CODE_SIGN_IDENTITY = Apple Development 44 | 45 | 46 | // Allows private project config information to be set and overridden for production builds 47 | #include? "PrivateProjectConfig.xcconfig" 48 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | BuildSettingExtractor Read Me 2 | ================================= 3 | 4 | This is a utility to extract build configuration settings from an Xcode project into a set of xcconfig files. 5 | 6 | The current release, pre-built, code-signed, and notarized is available for download at [https://buildsettingextractor.com](https://buildsettingextractor.com). 7 | 8 | If you decide to move Xcode build settings out of your project file and into xcconfig files, this utility can make that initial move easier. It’s also an easy way for the curious to take a look at the build settings in a project without fear of accidentally changing them. 9 | 10 | For each target and the project itself, BuildSettingExtractor will generate one xcconfig file per build configuration plus a shared xcconfig file with all shared build settings for that target. 11 | 12 | Using the app: 13 | 14 | 1. Launch BuildSettingExtractor 15 | 2. Drag an Xcode Project file (xcodeproj) to the app window 16 | 3. Choose a destination folder 17 | 18 | Choose Preferences… (Command-,) from the BuildSettingExtractor menu to set generation options. 19 | 20 | ### Notes ### 21 | 22 | - BuildSettingExtractor does not alter the original Xcode project file. 23 | - BuildSettingExtractor does not update existing xcconfig files, it does a one-time extraction. 24 | - BuildSettingExtractor does not hoist shared target build settings to the project level. 25 | - Do not taunt BuildSettingExtractor. 26 | 27 | ### Generated Files ### 28 | 29 | The generated xcconfig files include build setting explanations gleaned from Xcode: 30 | 31 | // Framework Search Paths 32 | // 33 | // This is a list of paths to folders containing frameworks to be searched by the 34 | // compiler for both included or imported header files when compiling C, Objective-C, 35 | // C++, or Objective-C++, and by the linker for frameworks used by the product. Paths are 36 | // delimited by whitespace, so any paths with spaces in them need to be properly quoted. 37 | // [-F] 38 | 39 | FRAMEWORK_SEARCH_PATHS = $(DEVELOPER_FRAMEWORKS_DIR) $(inherited) 40 | 41 | 42 | // Info.plist File 43 | // 44 | // This is the project-relative path to the plist file that contains the Info.plist 45 | // information used by bundles. 46 | 47 | INFOPLIST_FILE = BuildSettingExtractorTests/BuildSettingExtractorTests-Info.plist 48 | 49 | These comments can be turned off in the Preferences window for a more compact file: 50 | 51 | FRAMEWORK_SEARCH_PATHS = $(DEVELOPER_FRAMEWORKS_DIR) $(inherited) 52 | INFOPLIST_FILE = BuildSettingExtractorTests/BuildSettingExtractorTests-Info.plist 53 | 54 | ### Version History ### 55 | 56 | *Version 1.4.8* 57 | *Dec 23, 2024* 58 | 59 | – Updated to extract build settings from Xcode 15.3-compatible project files. 60 | – Updated to extract build settings from Xcode 16.0-compatible project files. 61 | – Added new line to end of generated files. 62 | – Tested on macOS 15.1.1 Sequoia, Apple Silicon. 63 | – Built with Xcode 16.2 on macOS 15.1.1 Sequoia. 64 | 65 | 66 | *Version 1.4.7* 67 | *Mar 2, 2024* 68 | 69 | – Updated list of build setting info files for Xcode 15.3. 70 | – Tested on macOS 14.3.1 Sonoma, Apple Silicon. 71 | – Built with Xcode 15.2 on macOS 14.3.1 Sonoma. 72 | 73 | 74 | *Version 1.4.6* 75 | *Jun 12, 2023* 76 | 77 | – Updated to extract build settings from Xcode 15.0-compatible project files. 78 | – Updated list of build setting info files. 79 | – Tested on macOS 13.4 Ventura, Apple Silicon. 80 | – Built with Xcode 14.3 on macOS 12.4 Ventura. 81 | 82 | 83 | *Version 1.4.5* 84 | *Sep 13, 2022* 85 | 86 | – Updated to extract build settings from Xcode 14.0-compatible project files. 87 | – Added grouping to conditional build setting generation. 88 | – Updated URL in generated files. 89 | – Tested on macOS 12.5.1 Monterey, Apple Silicon. 90 | – Built with Xcode 14.0 on macOS 12.5.1 Monterey. 91 | 92 | 93 | *Version 1.4.4* 94 | *May 7, 2022* 95 | 96 | – Updated list of build setting info files for Xcode 13.3 and later. 97 | – Added backstop entry for SWIFT_VERSION build setting. 98 | – Tested on macOS 12.3.1 Monterey, Apple Silicon. 99 | – Built with Xcode 13.3.1 on macOS 12.3.1 Monterey. 100 | 101 | 102 | *Version 1.4.3* 103 | *Oct 21, 2021* 104 | 105 | – Updated to extract build settings from Xcode 13.0-compatible project files. 106 | – Tested on macOS macOS 11.6 Big Sur. 107 | – Tested on macOS 12.0.1 Big Sur, Apple Silicon. 108 | – Built with Xcode 12.5.1 on macOS 11.6 Big Sur. 109 | 110 | 111 | *Version 1.4.2* 112 | *Nov 14, 2020* 113 | 114 | – Added native Apple Silicon support. 115 | – Updated app icon to follow macOS 11 Big Sur guidelines. 116 | – Updated icons in preferences window for macOS 11 Big Sur. 117 | – Fixed row spacing issue when building with Xcode 12 and later. 118 | – Updated list of build setting description files for Metal compiler and linker. 119 | – Updated to extract build settings from Xcode 11.4-compatible project files. 120 | – Added documentation for the BuildSettingInfoSubpaths.plist file. 121 | – Updated copyright notice. 122 | – Tested on macOS 10.14.6 Mojave. 123 | – Tested on macOS 10.15.7 Catalina. 124 | – Tested on macOS 11.0.1 Big Sur, Apple Silicon. 125 | – Built with Xcode 12.2 on macOS 10.15.7 Catalina. 126 | 127 | *Version 1.4.1* 128 | *Aug 3, 2020* 129 | 130 | – Added ability to drag Xcode project to app icon to extract settings. 131 | – Added support for no word separator in generated file names. 132 | – Added validation for text fields that should never be empty. 133 | – Added mechanism for handling introduced build setting info files. 134 | – Updated list of build setting info files for Xcode 12.0 beta. 135 | – Updated to extract build settings from Xcode 12.0-compatible project files. 136 | – Convert app scheme to use a test plan. 137 | – Tested on macOS 10.15.5 Catalina. 138 | – Tested on macOS 11.0 Big Sur beta 3. 139 | – Built with Xcode 11.6 on macOS 10.15.5 Catalina. 140 | 141 | *Version 1.4* 142 | *May 17, 2020* 143 | 144 | – Moved preferences from a sheet to a separate window. 145 | – Added preference to generate xcconfig files in folders grouped by target. 146 | – Added preference to group project xcconfig files in a folder. 147 | – Added preference to set name of folder enclosing all generated xcconfig files. 148 | – Folder enclosing generated xcconfig files is named according to preference value. 149 | – Added preference to automatically save generated files in same folder as source project. 150 | – Added preference for line spacing between settings. 151 | – Added preference for aligning build setting values. 152 | – Added a preview to each preference pane. 153 | – Added Close command to the File menu. 154 | – Added shared xcscheme file to allow Xcode Server usage. 155 | – Updated list of build setting info files for Xcode 11.4. 156 | – Updated minimum macOS deployment target to 10.14. 157 | – Tested on macOS 10.15.4 Catalina. 158 | – Built with Xcode 11.4.1 on macOS 10.15.4 Catalina. 159 | 160 | *Version 1.3.2* 161 | *Oct 13, 2019* 162 | 163 | – Spaces are now replaced with separator in generated xcconfig file names. 164 | – Added BSE-CodeSigning.xcconfig file to store easily accessible code signing settings. 165 | – Enabled hardened runtime to allow app notarization. 166 | – Enabled sandboxing with entitlement for read-write access to user-selected files. 167 | – Added support for Xcode beta as build setting info souce. 168 | – Added error reporting if no build setting info source is found. 169 | – Refactored error creation, handling and presentation. 170 | – Refactored AppKit-dependent category methods into separate file. 171 | – Removed precompiled headers. 172 | – Moved to explicit @import statements. 173 | – Tested on macOS 10.15 Catalina. 174 | – Built with Xcode 11.1 on macOS 10.14.6 Mojave. 175 | 176 | *Version 1.3.1* 177 | *Sep 15, 2019* 178 | 179 | – Updated to extract build settings from Xcode 11.0-compatible project files. 180 | – Add mechanism for handling deprecated build setting info files. 181 | – Update list of build setting info files for Xcode 11. 182 | – Add test to ensure build setting info files load without error. 183 | – Process Markdown in build setting options to plain text. 184 | – Tested on macOS 10.14.6 with Xcode 11. 185 | – Tested on macOS 10.15 Catalina beta 8. 186 | – Built with Xcode 11.0 on macOS 10.14.6 Mojave. 187 | - NOTE: The Xcode project file refuses to open in Xcode 11 GMc on macOS 10.15 Catalina beta 8. 188 | 189 | *Version 1.3* 190 | *Sep 25, 2018* 191 | 192 | – Added Dark Mode support. 193 | – Built with Xcode 10.0 on macOS 10.14 Mojave. 194 | 195 | *Version 1.2.8* 196 | *Sep 24, 2018* 197 | 198 | – Updated list of build setting description files to include Apple Clang file. 199 | – Fixed crash when xcspec file was not found when reading build setting descriptions. 200 | – Tested Xcode 10.0-compatible project files with Xcode 10. 201 | – Tested build setting descriptions with Xcode 10. 202 | – This the last version that will build cleanly on macOS 10.13 and earlier. 203 | – Built with Xcode 9.4.1 on macOS 10.13.6 High Sierra. 204 | 205 | *Version 1.2.7* 206 | *Aug 19, 2018* 207 | 208 | – Added alert to notify users if the selected project contains no build settings. 209 | – Added names and descriptions for some common settings without info. 210 | – Updated to extract build settings from Xcode 9.3-compatible project files. 211 | – Updated to extract build settings from Xcode 10.0-compatible project files. (Tested with beta 6.) 212 | – Made changes to prepare for Mojave Dark Mode. 213 | 214 | *Version 1.2.6* 215 | *Nov 3, 2017* 216 | 217 | – Added menu item to choose Xcode project. 218 | – Removed unused menu items. 219 | – Added window minimum size. 220 | – Updated window with options more befitting a single-window app. 221 | – Improved support for reading build setting descriptions from xcspec files. 222 | – Updated list of build setting description files to include Swift xcspec file. 223 | 224 | *Version 1.2.5* 225 | *Jun 13, 2017* 226 | 227 | – Resolve possible naming conflict between generated project and target files. 228 | – Updated list of build setting description files to include LLDB 8.1 file. 229 | – Updated list of build setting description files to include LLDB 9.0 file. 230 | – Fixed incorrect Markdown in the ReadMe file. 231 | 232 | *Version 1.2.4* 233 | *Nov 14, 2016* 234 | 235 | – Updated list of build setting description files to include LLDB 7.1 file. 236 | – Updated list of build setting description files to include LLDB 8.0 file. 237 | – Added support for reading build setting descriptions from xcspec files. 238 | – Updated to extract build settings from Xcode 6.3-compatible project files. 239 | – Updated to extract build settings from Xcode 8.0-compatible project files. 240 | 241 | *Version 1.2.3* 242 | *Nov 9, 2015* 243 | 244 | – Updated list of build setting description files to include new LLDB 7.0 file. 245 | – No longer inexplicably using return instead of newline character in two spots. 246 | 247 | *Version 1.2.2* 248 | *May 25, 2015* 249 | 250 | – Added brand new app icon. 251 | – Removed default Credits.rtf file. 252 | 253 | *Version 1.2.1* 254 | *May 16, 2015* 255 | 256 | – Updated list of build setting description files to include new LLDB 6.1 file. 257 | 258 | *Version 1.2* 259 | *May 16, 2015* 260 | 261 | – Added options for generated file names in Preferences. (Thank you [Alex Curylo](https://github.com/alexcurylo)!) 262 | 263 | *Version 1.1.1* 264 | *May 12, 2015* 265 | 266 | – Fixed crash on Mavericks. 267 | 268 | *Version 1.1* 269 | *February 7, 2015* 270 | 271 | – Added build settings explaination comments gleaned from Xcode. 272 | – Files are shown in Finder after they are generated. 273 | – Both options can be turned off in new Preferences pane. 274 | – Extraction and file generation now occurs in the background. 275 | 276 | *Version 1.0* 277 | *January 31, 2015* 278 | 279 | – Initial version of BuildSettingExtractor. 280 | – Generates xcconfig files from the build settings in an Xcode project. 281 | 282 | ***** 283 | 284 | *This code is provide as-is with no warranties express or implied. 285 | Please put projects in source control to guard against things going horribly awry.* 286 | -------------------------------------------------------------------------------- /ci_scripts/ci_post_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ci_post_clone.sh 4 | # BuildSettingExtractor 5 | # 6 | # Created by James Dempsey on 6/10/23. 7 | # 8 | 9 | if [ $CI_XCODE_CLOUD = "TRUE" ]; then 10 | 11 | # Get path to xcconfig files in project 12 | configFolderPath = "$CI_PRIMARY_REPOSITORY_PATH/Config" 13 | 14 | # Write private project configuration file 15 | privateProjectConfigPath="$configFolderPath/PrivateProjectConfig.xcconfig" 16 | 17 | cat > $privateProjectConfigPath <<- EOF 18 | DEVELOPMENT_TEAM = $CI_TEAM_ID 19 | EOF 20 | 21 | # Write private app configuration file 22 | privateAppConfigPath="$configFolderPath/PrivateAppConfig.xcconfig" 23 | 24 | cat > $privateAppConfigPath <<- EOF 25 | PRODUCT_BUNDLE_IDENTIFIER = $CI_BUNDLE_ID 26 | EOF 27 | 28 | else 29 | 30 | echo "CI_XCODE_CLOUD env variable was not TRUE" 31 | 32 | fi 33 | --------------------------------------------------------------------------------