Skip to content

Commit

Permalink
Break out xcode-locator's main function into smaller chunks.
Browse files Browse the repository at this point in the history
This is intended to be a refactoring without functional changes for my
own sanity in understanding what this code does.  Except I couldn't
avoid applying some visual improvements to the usage message.

RELNOTES: None.
PiperOrigin-RevId: 206771539
  • Loading branch information
jmmv authored and Copybara-Service committed Jul 31, 2018
1 parent 04d8a92 commit a0f77be
Showing 1 changed file with 99 additions and 71 deletions.
170 changes: 99 additions & 71 deletions tools/osx/xcode_locator.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,100 @@ static void AddEntryToDictionary(XcodeVersionEntry *entry,
return version;
}

// Searches for all available Xcodes in the system and returns a dictionary that
// maps version identifiers of any form (X, X.Y, and X.Y.Z) to the directory
// where the Xcode bundle lives.
//
// If there is a problem locating the Xcodes, prints an error message and
// returns nil.
static NSMutableDictionary *FindXcodes() __attribute((ns_returns_retained)) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
CFErrorRef cfError;
NSArray *array = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier(
CFSTR("com.apple.dt.Xcode"), &cfError));
if (array == nil) {
NSError *nsError = (__bridge NSError *)cfError;
fprintf(stderr, "error: %s\n", nsError.description.UTF8String);
return nil;
}
for (NSURL *url in array) {
NSBundle *bundle = [NSBundle bundleWithURL:url];
if (!bundle) {
fprintf(stderr, "error: Unable to open bundle at URL: %s\n",
url.description.UTF8String);
return nil;
}
NSString *version = bundle.infoDictionary[@"CFBundleShortVersionString"];
if (!version) {
fprintf(stderr, "error: Unable to extract CFBundleShortVersionString "
"from URL: %s\n", url.description.UTF8String);
return nil;
}
version = ExpandVersion(version);
NSURL *developerDir =
[url URLByAppendingPathComponent:@"Contents/Developer"];
XcodeVersionEntry *entry =
[[XcodeVersionEntry alloc] initWithVersion:version url:developerDir];
AddEntryToDictionary(entry, dict);
}
return dict;
}

// Prints out the located Xcodes as a set of lines where each line contains the
// list of versions for a given Xcode and its location on disk.
static void DumpAsVersionsOnly(FILE *output, NSMutableDictionary *dict) {
NSSet *distinctValues = [[NSSet alloc] initWithArray:[dict allValues]];
NSMutableDictionary *aliasDict = [[NSMutableDictionary alloc] init];
for (XcodeVersionEntry *value in distinctValues) {
NSString *versionString = value.version;
if (aliasDict[versionString] == nil) {
aliasDict[versionString] = [[NSMutableSet alloc] init];
}
[aliasDict[versionString]
addObjectsFromArray:[dict allKeysForObject:value]];
}
for (NSString *version in aliasDict) {
XcodeVersionEntry *entry = dict[version];
fprintf(output, "%s:%s:%s\n",
version.UTF8String,
[[aliasDict[version] allObjects]
componentsJoinedByString: @","].UTF8String,
entry.url.fileSystemRepresentation);
}
}

// Prints out the located Xcodes in JSON format.
static void DumpAsJson(FILE *output, NSMutableDictionary *dict) {
fprintf(output, "{\n");
for (NSString *version in dict) {
XcodeVersionEntry *entry = dict[version];
fprintf(output, "\t\"%s\": \"%s\",\n",
version.UTF8String, entry.url.fileSystemRepresentation);
}
fprintf(output, "}\n");
}

// Dumps usage information.
static void usage(FILE *output) {
fprintf(
output,
"xcode-locator [-v|<version_number>]"
"\n\n"
"Given a version number or partial version number in x.y.z format, "
"will attempt to return the path to the appropriate developer "
"directory."
"\n\n"
"Omitting a version number will list all available versions in JSON "
"format, alongside their paths."
"\n\n"
"Passing -v will list all available fully-specified version numbers "
"along with their possible aliases and their developer directory, "
"each on a new line. For example:"
"\n\n"
"7.3.1:7,7.3,7.3.1:/Applications/Xcode.app/Contents/Developer"
"\n");
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *versionArg = nil;
Expand All @@ -129,56 +223,14 @@ int main(int argc, const char * argv[]) {
}
}
if (versionArg == nil) {
fprintf(
stderr,
"xcode-locator [-v|<version_number>]"
"\n\n"
"Given a version number, or partial version number in x.y.z format, "
"will attempt to return the path to the appropriate developer "
"directory."
"\n"
"Omitting a version number will list all available versions in JSON "
"format, alongside their paths."
"\n"
"Passing -v will list all available fully-specified version numbers "
"along with their possible aliases and their developer directory, "
"each on a new line."
"\n"
"For example: "
"'7.3.1:7,7.3,7.3.1:/Applications/Xcode.app/Contents/Developer'."
"\n");
usage(stderr);
return 1;
}

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
CFErrorRef cfError;
NSArray *array = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier(
CFSTR("com.apple.dt.Xcode"), &cfError));
if (array == nil) {
NSError *nsError = (__bridge NSError *)cfError;
fprintf(stderr, "error: %s\n", nsError.description.UTF8String);
NSMutableDictionary *dict = FindXcodes();
if (dict == nil) {
return 1;
}
for (NSURL *url in array) {
NSBundle *bundle = [NSBundle bundleWithURL:url];
if (!bundle) {
fprintf(stderr, "error: Unable to open bundle at URL: %s\n",
url.description.UTF8String);
return 1;
}
NSString *version = bundle.infoDictionary[@"CFBundleShortVersionString"];
if (!version) {
fprintf(stderr, "error: Unable to extract CFBundleShortVersionString "
"from URL: %s\n", url.description.UTF8String);
return 1;
}
version = ExpandVersion(version);
NSURL *developerDir =
[url URLByAppendingPathComponent:@"Contents/Developer"];
XcodeVersionEntry *entry =
[[XcodeVersionEntry alloc] initWithVersion:version url:developerDir];
AddEntryToDictionary(entry, dict);
}

XcodeVersionEntry *entry = [dict objectForKey:versionArg];
if (entry) {
Expand All @@ -187,33 +239,9 @@ int main(int argc, const char * argv[]) {
}

if (versionsOnly) {
NSSet *distinctValues = [[NSSet alloc] initWithArray:[dict allValues]];
NSMutableDictionary *aliasDict = [[NSMutableDictionary alloc] init];
for (XcodeVersionEntry *value in distinctValues) {
NSString *versionString = value.version;
if (aliasDict[versionString] == nil) {
aliasDict[versionString] = [[NSMutableSet alloc] init];
}
[aliasDict[versionString]
addObjectsFromArray:[dict allKeysForObject:value]];
}
for (NSString *version in aliasDict) {
XcodeVersionEntry *entry = dict[version];
printf("%s:%s:%s\n",
version.UTF8String,
[[aliasDict[version] allObjects]
componentsJoinedByString: @","].UTF8String,
entry.url.fileSystemRepresentation);
}
DumpAsVersionsOnly(stdout, dict);
} else {
// Print out list in json format.
printf("{\n");
for (NSString *version in dict) {
XcodeVersionEntry *entry = dict[version];
printf("\t\"%s\": \"%s\",\n",
version.UTF8String, entry.url.fileSystemRepresentation);
}
printf("}\n");
DumpAsJson(stdout, dict);
}
return ([@"" isEqualToString:versionArg] ? 0 : 1);
}
Expand Down

0 comments on commit a0f77be

Please sign in to comment.