From a7b40a5cddbe300cf941d3ad251c42e2ddd70c0d Mon Sep 17 00:00:00 2001 From: Antoine Mercadal Date: Thu, 13 Sep 2012 19:07:46 -0700 Subject: [PATCH] XcodeCapp now uses both .m and .h shadow files in addition of the general include .h. Seems to fix most of the problems + refactoring --- Tools/XcodeCapp/TNXCodeCapp.h | 4 +- Tools/XcodeCapp/TNXCodeCapp.m | 544 ++++++++++-------- .../XcodeCapp.xcodeproj/project.pbxproj | 19 + 3 files changed, 311 insertions(+), 256 deletions(-) diff --git a/Tools/XcodeCapp/TNXCodeCapp.h b/Tools/XcodeCapp/TNXCodeCapp.h index b43cfa130..2097b6dc8 100644 --- a/Tools/XcodeCapp/TNXCodeCapp.h +++ b/Tools/XcodeCapp/TNXCodeCapp.h @@ -73,7 +73,9 @@ extern NSString * const XCCListeningStartNotification; - (BOOL)isXIBFile:(NSString *)path; - (BOOL)isXCCIgnoreFile:(NSString *)path; - (BOOL)prepareXCodeSupportProject; -- (NSURL*)shadowURLForSourceURL:(NSURL*)aSourceURL; +- (NSURL*)shadowHeaderURLForSourceURL:(NSURL*)aSourceURL; +- (void)cleanUpShadowsRelatedToSourceURL:(NSURL*)aSourceURL; +- (NSURL*)shadowImplementationURLForSourceURL:(NSURL*)aSourceURL; - (NSURL*)sourceURLForShadowName:(NSString *)aString; - (void)handleFileModification:(NSString*)fullPath notify:(BOOL)shouldNotify; - (void)handleFileRemoval:(NSString*)fullPath; diff --git a/Tools/XcodeCapp/TNXCodeCapp.m b/Tools/XcodeCapp/TNXCodeCapp.m index a77736932..422ad9a42 100644 --- a/Tools/XcodeCapp/TNXCodeCapp.m +++ b/Tools/XcodeCapp/TNXCodeCapp.m @@ -39,8 +39,8 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification @synthesize supportFileLevelAPI; @synthesize isUsingFileLevelAPI; -#pragma mark - -#pragma mark Initialization + +#pragma mark - Initialization /*! Initialize the AppController @@ -48,7 +48,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification - (id)init { self = [super init]; - + if (self) { errorList = [NSMutableArray arrayWithCapacity:10]; @@ -57,7 +57,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification parserPath = [[NSBundle mainBundle] pathForResource:@"parser" ofType:@"j"]; lastEventId = [[NSUserDefaults standardUserDefaults] objectForKey:@"lastEventId"]; appStartedTimestamp = [NSDate date]; - + [self setIsListening:NO]; [self setIsUsingFileLevelAPI:NO]; @@ -65,11 +65,11 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification SInt32 versionMinor = 0; Gestalt(gestaltSystemVersionMajor, &versionMajor); Gestalt(gestaltSystemVersionMinor, &versionMinor); - + [self setSupportFileLevelAPI:versionMajor >= 10 && versionMinor >= 7]; // Uncomment to simulate 10.6 mode // [self setSupportFileLevelAPI:NO]; - + [self configure]; if([fm fileExistsAtPath:[@"~/.bash_profile" stringByExpandingTildeInPath]]) @@ -91,21 +91,21 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification profilePath = @""; } } - + return self; } - (void)start -{ +{ if (![[NSUserDefaults standardUserDefaults] boolForKey:@"XCCReopenLastProject"]) return; NSString *lastOpenedPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastOpenedPath"]; - + if (lastOpenedPath) { if ([fm fileExistsAtPath:lastOpenedPath]) - { + { [self listenProjectAtPath:[NSString stringWithFormat:@"%@/", lastOpenedPath]]; } else @@ -115,6 +115,135 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification } } + +#pragma mark - Project Management + +/*! + Check if .XcodeSupport needs to be initialized. + If not needed, check that all J files are mirrored. If no, + then launch conversion for missing mirrored h files + @return YES or NO + */ +- (BOOL)prepareXCodeSupportProject +{ + XCodeSupportProjectName = [NSString stringWithFormat:@"%@.xcodeproj/", currentProjectName]; + XCodeTemplatePBXPath = [[NSBundle mainBundle] pathForResource:@"project.pbxproj" ofType:@"sample"]; + XCodeSupportProject = [NSURL URLWithString:XCodeSupportProjectName relativeToURL:currentProjectURL]; + XCodeSupportProjectSources = [NSURL URLWithString:@".XcodeSupport/" relativeToURL:currentProjectURL]; + XCodeSupportPBXPath = [NSString stringWithFormat:@"%@/project.pbxproj", [XCodeSupportProject path]]; + PBXModifierScriptPath = [[NSBundle mainBundle] pathForResource:@"pbxprojModifier" ofType:@"py"]; + + + //[fm removeItemAtURL:XCodeSupportProjectSources error:nil]; + //[fm removeItemAtURL:XCodeSupportProject error:nil]; + + // create the template project if it doesn't exist + if (![fm fileExistsAtPath:[XCodeSupportProjectSources path]]) + { + NSLog(@"prepareXCodeSupportProject: Xcode support folder created at: %@", [XCodeSupportProject path]); + [fm createDirectoryAtPath:[XCodeSupportProject path] withIntermediateDirectories:YES attributes:nil error:nil]; + + DLog(@"prepareXCodeSupportProject: Copying project.pbxproj from %@ to %@", XCodeTemplatePBXPath, [XCodeSupportProject path]); + [fm copyItemAtPath:XCodeTemplatePBXPath toPath:XCodeSupportPBXPath error:nil]; + + DLog(@"prepareXCodeSupportProject: Reading the content of the project.pbxproj"); + NSMutableString *PBXContent = [NSMutableString stringWithContentsOfFile:XCodeSupportPBXPath encoding:NSUTF8StringEncoding error:nil]; + + [PBXContent writeToFile:XCodeSupportPBXPath atomically:YES encoding:NSUTF8StringEncoding error:nil]; + DLog(@"prepareXCodeSupportProject: PBX file adapted to the project"); + + [self createXcodeSupportProjectSourcesDirIfNecessary]; + return NO; + } + + [self createXcodeSupportProjectSourcesDirIfNecessary]; + return YES; +} + +/*! + Create the .XcodeSupport/Sources folder if necessary. + */ +- (void)createXcodeSupportProjectSourcesDirIfNecessary +{ + if ([fm fileExistsAtPath:[XCodeSupportProjectSources path]]) + return; + + DLog(@"createXcodeSupportProjectSourcesDirIfNecessary: Creating source folder %@", [XCodeSupportProjectSources path]); + [fm createDirectoryAtPath:[XCodeSupportProjectSources path] withIntermediateDirectories:YES attributes:nil error:nil]; +} + +/*! + Initialize the creation of the .XcodeSupport project. This + Operation is threaded + @param arguments Thread arguments (not used) + @param shouldNotify is YES, XCCDidPopulateProjectNotification will be send + */ +- (void)populateXCodeProject:(NSNumber *)shouldNotify +{ + if ([shouldNotify boolValue]) + [delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Loading project" withObject:[currentProjectURL path]]; + + NSArray *subdpaths = [fm subpathsAtPath:[currentProjectURL path]]; + + for (NSString *p in subdpaths) + { + NSString *filePath = [NSString stringWithFormat:@"%@/%@", [currentProjectURL path], p]; + + BOOL isDir = NO; + [fm fileExistsAtPath:filePath isDirectory:&isDir]; + + if (isDir || ![self isObjJFile:filePath] || [self isPathMatchingIgnoredPaths:filePath]) + continue; + + NSURL *eventualShadow = [self shadowHeaderURLForSourceURL:[NSURL fileURLWithPath:filePath]]; + + if (![fm fileExistsAtPath:[eventualShadow path]]) + { + DLog(@"populateXCodeProject: Computing missing shadow file for %@", filePath); + [self handleFileModification:filePath notify:NO]; + } + } + + if ([shouldNotify boolValue]) + { + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:currentProjectURL, @"URL", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:XCCDidPopulateProjectNotification object:self userInfo:info]; + } +} + +/*! + Start all needed processes for listening to a given path + @param path The folder path to listen to + */ +- (void)listenProjectAtPath:(NSString *)path +{ + NSMutableString *tempName = [NSMutableString stringWithString:[path lastPathComponent]]; + + currentProjectURL = [NSURL fileURLWithPath:path]; + + [tempName replaceOccurrencesOfString:@" " + withString:@"_" + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, [tempName length])]; + currentProjectName = [NSString stringWithString:tempName]; + + [self computeIgnoredPaths]; + + BOOL isProjectReady = [self prepareXCodeSupportProject]; + + [NSThread detachNewThreadSelector:@selector(populateXCodeProject:) toTarget:self withObject:[NSNumber numberWithBool:!isProjectReady]]; + + [self initializeEventStreamWithPath:[currentProjectURL path]]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:path, @"path", [NSNumber numberWithInt:(isProjectReady) ? 1 : 0], @"ready", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:XCCListeningStartNotification object:self userInfo:info]; + + [[NSUserDefaults standardUserDefaults] setObject:[currentProjectURL path] forKey:@"LastOpenedPath"]; +} + + +#pragma mark - Event Stream + /*! Initializes the FSEvent stream @param aPath the path of the folder to listen @@ -123,18 +252,18 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification { if ([self isListening]) return; - + [self stopEventStream]; - + NSMutableArray *pathsToWatch = [NSMutableArray arrayWithObject:aPath]; void *appPointer = (void *)self; FSEventStreamContext context = {0, appPointer, NULL, NULL, NULL}; CFTimeInterval latency = 2.0; FSEventStreamCreateFlags flags = 0; - + if (supportsFileBasedListening) { - DLog(@"Initializing the FSEventStream at file level (clean)"); + DLog(@"initializeEventStreamWithPath: Initializing the FSEventStream at file level (clean)"); flags = kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents; } else @@ -142,10 +271,10 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification NSLog(@"Initializing the FSEventStream at folder level (dirty)"); flags = kFSEventStreamCreateFlagUseCFTypes; } - + // add symlinked directories NSArray *fileList = [fm contentsOfDirectoryAtPath:aPath error:nil]; - + for (NSString *node in fileList) { NSDictionary *attributes = [fm attributesOfItemAtPath:aPath error:nil]; @@ -160,19 +289,15 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification } } } - + stream = FSEventStreamCreate(NULL, &fsevents_callback, &context, (CFArrayRef) pathsToWatch, [lastEventId unsignedLongLongValue], latency, flags); - + FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamStart(stream); [self setIsListening:YES]; } - -#pragma mark - -#pragma mark Utilities - /*! Stop listening the FSEvent stream if active */ @@ -210,7 +335,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification { if (![self supportFileLevelAPI]) { - DLog(@"System doesn't support file level API"); + DLog(@"configure: System doesn't support file level API"); [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:2] forKey:@"XCCAPIMode"]; } @@ -229,7 +354,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification if (supportsFileBasedListening) { - DLog(@"using 10.7+ mode listening (clean)"); + DLog(@"configure: using 10.7+ mode listening (clean)"); [self setCurrentAPIMode:@"File level (Lion)"]; [self setIsUsingFileLevelAPI:YES]; @@ -237,7 +362,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification } else { - DLog(@"using 10.6 mode listening (dirty)"); + DLog(@"configure: using 10.6 mode listening (dirty)"); reactToInodeModification = NO; [self setCurrentAPIMode:@"Folder level (Snow Leopard)"]; [self setIsUsingFileLevelAPI:NO]; @@ -273,6 +398,9 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification [[NSUserDefaults standardUserDefaults] synchronize]; } + +#pragma mark - Shell Helpers + /*! Run a NSTask with the given arguments @param arguments NSArray containing the NSTask arguments @@ -284,22 +412,25 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification NSData *stdOut; NSString *response; NSNumber *status; - + task = [[NSTask alloc] init]; - + [task setLaunchPath: @"/bin/bash"]; [task setArguments: arguments]; [task setStandardOutput:[NSPipe pipe]]; [task launch]; [task waitUntilExit]; - + stdOut = [[[task standardOutput] fileHandleForReading] availableData]; response = [[NSString alloc] initWithData:stdOut encoding:NSUTF8StringEncoding]; status = [NSNumber numberWithInt:[task terminationStatus]]; - + return [NSArray arrayWithObjects:status, response, nil]; } + +#pragma mark - Event Handlers + /*! Handle a file modification. If it's a .J or XIB or NIB, it will perform the according conversion. If it's .xcodecapp-ignore, it will @@ -315,7 +446,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification if ([self isPathMatchingIgnoredPaths:fullPath] || ![fm fileExistsAtPath:fullPath]) return; - DLog(@"Parsing modified file: %@", fullPath); + DLog(@"handleFileModification:notify: Parsing modified file: %@", fullPath); NSArray *arguments = nil; NSArray *PBXArguments = nil; @@ -324,16 +455,19 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification NSString *response = nil; NSNumber *status = [NSNumber numberWithInt:0]; NSString *splitPath = [fullPath substringFromIndex:[[currentProjectURL path] length] + 1]; - NSString *shadowPath = [[self shadowURLForSourceURL:[NSURL URLWithString:[fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]] path]; - - DLog(@"Shadow path: %@", shadowPath); - + NSURL *encodedURL = [NSURL URLWithString:[fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:encodedURL]; + NSURL *shadowImplementationURL = [self shadowImplementationURLForSourceURL:encodedURL]; + + DLog(@"handleFileModification:notify: Shadow header path: %@", shadowHeaderURL); + DLog(@"handleFileModification:notify: Shadow implementation path: %@", shadowImplementationURL); + [[NSNotificationCenter defaultCenter] postNotificationName:XCCConversionStartNotification object:self]; if ([self isXIBFile:fullPath]) { arguments = [NSArray arrayWithObjects: @"-c", - [NSString stringWithFormat:@"(%@; nib2cib '%@';) 2>&1", profilePath, fullPath],@"",nil]; + [NSString stringWithFormat:@"(%@; nib2cib '%@';) 2>&1", profilePath, fullPath],@"",nil]; successTitle = @"XIB converted"; successMsg = splitPath; @@ -341,19 +475,20 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification else if ([self isObjJFile:fullPath]) { arguments = [NSArray arrayWithObjects: @"-c", - [NSString stringWithFormat:@"(%@; objj '%@' '%@' '%@';) 2>&1", - profilePath, - parserPath, - fullPath, - shadowPath], - @"",nil]; + [NSString stringWithFormat:@"(%@; objj '%@' '%@' '%@' '%@';) 2>&1", + profilePath, + parserPath, + fullPath, + [shadowHeaderURL path], + [shadowImplementationURL path]],nil]; PBXArguments = [NSArray arrayWithObjects: @"-c", - [NSString stringWithFormat:@"(%@; python %@ add '%@' '%@' '%@' '%@') 2>&1", + [NSString stringWithFormat:@"(%@; python %@ add '%@' '%@' '%@' '%@' '%@') 2>&1", profilePath, PBXModifierScriptPath, XCodeSupportPBXPath, - shadowPath, + [shadowHeaderURL path], + [shadowImplementationURL path], fullPath, [currentProjectURL path]],nil]; @@ -371,22 +506,22 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification // Run the task and get the response if needed if (arguments) { - DLog(@"Running conversion task..."); + DLog(@"handleFileModification:notify: Running conversion task..."); NSArray *statusInfo = [self runTask:arguments]; - + status = [statusInfo objectAtIndex:0]; response = [statusInfo objectAtIndex:1]; - - DLog(@"Conversion task result/response: %@/%@", status, response); + + DLog(@"handleFileModification:notify: Conversion task result/response: %@/%@", status, response); } if (PBXArguments) { - DLog(@"Running update PBX task..."); + DLog(@"handleFileModification:notify: Running update PBX task..."); NSArray *statusInfo = [self runTask:PBXArguments]; status = [statusInfo objectAtIndex:0]; response = [statusInfo objectAtIndex:1]; - DLog(@"Update PBX Task result/response: %@/%@", status, response); + DLog(@"handleFileModification:notify: Update PBX Task result/response: %@/%@", status, response); } if ([status intValue] == 0 && shouldNotify) @@ -397,12 +532,12 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification { if (response) [errorList addObject:response]; - + [delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Error processing file" withObject:splitPath]; } [[NSNotificationCenter defaultCenter] postNotificationName:XCCConversionStopNotification object:self]; - DLog(@"Processed: %@", fullPath); + DLog(@"handleFileModification:notify: Processed: %@", fullPath); } /*! @@ -416,37 +551,18 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification { if ([self isPathMatchingIgnoredPaths:fullPath] || [fm fileExistsAtPath:fullPath]) return; - + + NSString *encodedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + if ([self isObjJFile:fullPath]) - { - NSString *shadowPath = [[self shadowURLForSourceURL:[NSURL URLWithString:[fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]] path]; - - DLog(@"Removing shadow file: %@", shadowPath); - [fm removeItemAtPath:shadowPath error:nil]; - - DLog(@"Removing PBX reference task..."); - -#if DEBUG - NSArray *PBXArguments = [NSArray arrayWithObjects: @"-c", - [NSString stringWithFormat:@"(%@; python %@ remove '%@' '%@' '%@' '%@') 2>&1", - profilePath, - PBXModifierScriptPath, - XCodeSupportPBXPath, - shadowPath, - fullPath, - [currentProjectURL path]],nil]; - NSArray *statusInfo = [self runTask:PBXArguments]; - NSNumber *status = [statusInfo objectAtIndex:0]; - NSString *response = [statusInfo objectAtIndex:1]; -#endif - DLog(@"PBX Reference removal status/response: %@/%@", status, response); - } + [self cleanUpShadowsRelatedToSourceURL:[NSURL URLWithString:encodedPath]]; else if ([self isXCCIgnoreFile:fullPath]) - { [self computeIgnoredPaths]; - } } + +#pragma mark - Source Files Management + /*! Check if given full path is Objective-J file @param path the path to check @@ -480,111 +596,20 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification } -/*! - Check if .XcodeSupport needs to be initialized. - If not needed, check that all J files are mirrored. If no, - then launch conversion for missing mirrored h files - @return YES or NO - */ -- (BOOL)prepareXCodeSupportProject -{ - XCodeSupportProjectName = [NSString stringWithFormat:@"%@.xcodeproj/", currentProjectName]; - XCodeTemplatePBXPath = [[NSBundle mainBundle] pathForResource:@"project.pbxproj" ofType:@"sample"]; - XCodeSupportProject = [NSURL URLWithString:XCodeSupportProjectName relativeToURL:currentProjectURL]; - XCodeSupportProjectSources = [NSURL URLWithString:@".XcodeSupport/" relativeToURL:currentProjectURL]; - XCodeSupportPBXPath = [NSString stringWithFormat:@"%@/project.pbxproj", [XCodeSupportProject path]]; - PBXModifierScriptPath = [[NSBundle mainBundle] pathForResource:@"pbxprojModifier" ofType:@"py"]; - - - //[fm removeItemAtURL:XCodeSupportProjectSources error:nil]; - //[fm removeItemAtURL:XCodeSupportProject error:nil]; - - // create the template project if it doesn't exist - if (![fm fileExistsAtPath:[XCodeSupportProjectSources path]]) - { - NSLog(@"Xcode support folder created at: %@", [XCodeSupportProject path]); - [fm createDirectoryAtPath:[XCodeSupportProject path] withIntermediateDirectories:YES attributes:nil error:nil]; - - DLog(@"Copying project.pbxproj from %@ to %@", XCodeTemplatePBXPath, [XCodeSupportProject path]); - [fm copyItemAtPath:XCodeTemplatePBXPath toPath:XCodeSupportPBXPath error:nil]; - - DLog(@"Reading the content of the project.pbxproj"); - NSMutableString *PBXContent = [NSMutableString stringWithContentsOfFile:XCodeSupportPBXPath encoding:NSUTF8StringEncoding error:nil]; - - [PBXContent writeToFile:XCodeSupportPBXPath atomically:YES encoding:NSUTF8StringEncoding error:nil]; - DLog(@"PBX file adapted to the project"); - - [self createXcodeSupportProjectSourcesDirIfNecessary]; - return NO; - } - - [self createXcodeSupportProjectSourcesDirIfNecessary]; - return YES; -} +#pragma mark - Shadow Files Management /*! - Create the .XcodeSupport/Sources folder if necessary. - */ -- (void)createXcodeSupportProjectSourcesDirIfNecessary -{ - if ([fm fileExistsAtPath:[XCodeSupportProjectSources path]]) - return; - - DLog(@"Creating source folder %@", [XCodeSupportProjectSources path]); - [fm createDirectoryAtPath:[XCodeSupportProjectSources path] withIntermediateDirectories:YES attributes:nil error:nil]; -} - -/*! - Initialize the creation of the .XcodeSupport project. This - Operation is threaded - @param arguments Thread arguments (not used) - @param shouldNotify is YES, XCCDidPopulateProjectNotification will be send - */ -- (void)populateXCodeProject:(NSNumber *)shouldNotify -{ - if ([shouldNotify boolValue]) - [delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Loading project" withObject:[currentProjectURL path]]; - - NSArray *subdpaths = [fm subpathsAtPath:[currentProjectURL path]]; - - for (NSString *p in subdpaths) - { - NSString *filePath = [NSString stringWithFormat:@"%@/%@", [currentProjectURL path], p]; - - BOOL isDir = NO; - [fm fileExistsAtPath:filePath isDirectory:&isDir]; - - if (isDir || ![self isObjJFile:filePath] || [self isPathMatchingIgnoredPaths:filePath]) - continue; - - NSURL *eventualShadow = [self shadowURLForSourceURL:[NSURL fileURLWithPath:filePath]]; - - if (![fm fileExistsAtPath:[eventualShadow path]]) - { - DLog(@"Computing missing shadow file for %@", filePath); - [self handleFileModification:filePath notify:NO]; - } - } - - if ([shouldNotify boolValue]) - { - NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:currentProjectURL, @"URL", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:XCCDidPopulateProjectNotification object:self userInfo:info]; - } -} - -/*! - Compute the mirorred (shadow) name for a given path + Compute the mirorred (shadow) header file name for a given path @param aSourceURL the origin path - @return NSURL representing the shadow path + @return NSURL representing the shadow URL for the header file */ -- (NSURL *)shadowURLForSourceURL:(NSURL*)aSourceURL +- (NSURL *)shadowHeaderURLForSourceURL:(NSURL*)aSourceURL { if (!aSourceURL) - [NSException raise:NSInvalidArgumentException format:@"shadowURLForSourceURL: aSource URL must not be null"]; + [NSException raise:NSInvalidArgumentException format:@"shadowHeaderURLForSourceURL: aSource URL must not be null"]; NSMutableString *flattenedPath = [NSMutableString stringWithString:[aSourceURL path]]; - + // Replace "_" with a substring that is unlikely to be in a filename [flattenedPath replaceOccurrencesOfString:@"_" withString:@"≤‹°∞°›≥" @@ -596,12 +621,33 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification options:0 range:NSMakeRange(0, [flattenedPath length])]; - DLog(@"Flattened path: %@", flattenedPath); - NSString *basename = [NSString stringWithFormat:@"%@.m", [[flattenedPath stringByDeletingPathExtension] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; - + DLog(@"shadowHeaderURLForSourceURL: Flattened path: %@", flattenedPath); + NSString *basename = [NSString stringWithFormat:@"%@.h", [[flattenedPath stringByDeletingPathExtension] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + return [NSURL URLWithString:basename relativeToURL:XCodeSupportProjectSources]; } +/*! + Compute the mirorred (shadow) implementation file name for a given path + @param aSourceURL the origin path + @return NSURL representing the shadow URL for the implementation file + */ +- (NSURL *)shadowImplementationURLForSourceURL:(NSURL*)aSourceURL +{ + if (!aSourceURL) + [NSException raise:NSInvalidArgumentException format:@"shadowImplementationURLForSourceURL: aSource URL must not be null"]; + + NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:aSourceURL]; + NSURL *shadowImplPath = [[shadowHeaderURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"m"]; + + return shadowImplPath; +} + +/*! + Compute the Cappuccino source file that is related to the given shadow header file URL + @param aString the origin path + @return NSURL representing the URL for the related Cappuccino source file + */ - (NSURL *)sourceURLForShadowName:(NSString *)aString { NSMutableString * unshadowedPath = [NSMutableString stringWithString:aString]; @@ -612,11 +658,11 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification range:NSMakeRange(0, [unshadowedPath length])]; [unshadowedPath replaceOccurrencesOfString:@"≤‹°∞°›≥" - withString:@"_" - options:0 - range:NSMakeRange(0, [unshadowedPath length])]; + withString:@"_" + options:0 + range:NSMakeRange(0, [unshadowedPath length])]; - [unshadowedPath replaceOccurrencesOfString:@".m" + [unshadowedPath replaceOccurrencesOfString:@".h" withString:@".j" options:0 range:NSMakeRange(0, [unshadowedPath length])]; @@ -624,6 +670,64 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification return [NSURL fileURLWithPath:[NSString stringWithString:unshadowedPath]]; } +/*! + Clean up any shadow files and PBX entries related to given the Cappuccino source file URL + @param anURL the Cappuccino source file URL + */ +- (void)cleanUpShadowsRelatedToSourceURL:(NSURL *)anURL +{ + NSURL *shadowHeaderURL = [self shadowHeaderURLForSourceURL:anURL]; + NSURL *shadowImplementationURL = [self shadowImplementationURLForSourceURL:anURL]; + + DLog(@"cleanUpShadowsRelatedToSourceURL: Removing shadow header file: %@", shadowHeaderURL); + [fm removeItemAtURL:shadowHeaderURL error:nil]; + + DLog(@"cleanUpShadowsRelatedToSourceURL: Removing shadow implementation file: %@", shadowImplementationURL); + [fm removeItemAtURL:shadowImplementationURL error:nil]; + + DLog(@"cleanUpShadowsRelatedToSourceURL:Removing PBX reference task..."); + NSArray *PBXArguments = [NSArray arrayWithObjects: @"-c", + [NSString stringWithFormat:@"(%@; python %@ remove '%@' '%@' '%@' '%@' '%@') 2>&1", + profilePath, + PBXModifierScriptPath, + XCodeSupportPBXPath, + [shadowHeaderURL path], + [shadowImplementationURL path], + [anURL path], + [currentProjectURL path]],nil]; + + NSArray *statusInfo = [self runTask:PBXArguments]; + NSNumber *status = [statusInfo objectAtIndex:0]; + NSString *response = [statusInfo objectAtIndex:1]; + DLog(@"cleanUpShadowsRelatedToSourceURL: PBX Reference removal status/response: %@/%@", status, response); +} + +/*! + Clean the support folder according to files present in given path + */ +- (void)tidyShadowedFiles +{ + NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[XCodeSupportProjectSources path] error:NULL]; + + for (NSString *subpath in subpaths) + { + if ([subpath pathExtension] != @".h" || [subpath lastPathComponent] == @"xcc_general_include.h") + continue; + + NSURL *unshadowed = [self sourceURLForShadowName:subpath]; + + if (![fm fileExistsAtPath:[unshadowed path]]) + { + [self cleanUpShadowsRelatedToSourceURL:unshadowed]; + + if (![self supportFileLevelAPI] && [self respondsToSelector:@selector(updateLastModificationDate:forPath:)]) + [self performSelector:@selector(updateLastModificationDate:forPath:) withObject:nil withObject:unshadowed]; + } + } +} + + +#pragma mark - XCC Ignore management /*! Compute the ignored paths according to any existing @@ -645,7 +749,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification [ignoredFilePaths addObject:pattern]; } } - + [ignoredFilePaths addObject:@"*/.git/*"]; [ignoredFilePaths addObject:@"*/.svn/*"]; [ignoredFilePaths addObject:@"*/.hg/*"]; @@ -656,7 +760,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification [ignoredFilePaths addObject:@"*main.j"]; [ignoredFilePaths addObject:@"*.xcodeproj/*"]; [ignoredFilePaths addObject:@"*.DS_Store"]; - + NSLog(@"Ignoring file paths: %@", ignoredFilePaths); } @@ -706,76 +810,6 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification return NO; } -/*! - Start all needed processes for listening to a given path - @param path The folder path to listen to - */ -- (void)listenProjectAtPath:(NSString *)path -{ - NSMutableString *tempName = [NSMutableString stringWithString:[path lastPathComponent]]; - - currentProjectURL = [NSURL fileURLWithPath:path]; - - [tempName replaceOccurrencesOfString:@" " - withString:@"_" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [tempName length])]; - currentProjectName = [NSString stringWithString:tempName]; - - [self computeIgnoredPaths]; - - BOOL isProjectReady = [self prepareXCodeSupportProject]; - - [NSThread detachNewThreadSelector:@selector(populateXCodeProject:) toTarget:self withObject:[NSNumber numberWithBool:!isProjectReady]]; - - [self initializeEventStreamWithPath:[currentProjectURL path]]; - - NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:path, @"path", [NSNumber numberWithInt:(isProjectReady) ? 1 : 0], @"ready", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:XCCListeningStartNotification object:self userInfo:info]; - - [[NSUserDefaults standardUserDefaults] setObject:[currentProjectURL path] forKey:@"LastOpenedPath"]; -} - -/*! - Clean the support folder according to files present in given path - */ -- (void)tidyShadowedFiles -{ - NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[XCodeSupportProjectSources path] error:NULL]; - - for (NSString *subpath in subpaths) - { - NSString *unshadowed = [[self sourceURLForShadowName:subpath] path]; - NSString *shadowFullPath = [NSString stringWithFormat:@"%@/%@", [XCodeSupportProjectSources path], subpath]; - - if (![fm fileExistsAtPath:unshadowed]) - { - DLog(@"cleaning shadow file: %@", subpath); - [fm removeItemAtPath:shadowFullPath error:nil]; - -#if DEBUG - NSArray *PBXArguments = [NSArray arrayWithObjects: @"-c", - [NSString stringWithFormat:@"(%@; python %@ remove '%@' '%@' '%@' '%@') 2>&1", - profilePath, - PBXModifierScriptPath, - XCodeSupportPBXPath, - shadowFullPath, - unshadowed, - [currentProjectURL path]],nil]; - - DLog(@"Cleaning PBX reference task..."); - NSArray *statusInfo = [self runTask:PBXArguments]; - NSNumber *status = [statusInfo objectAtIndex:0]; - NSString *response = [statusInfo objectAtIndex:1]; -#endif - DLog(@"PBX Reference cleaning status/response: %@/%@", status, response); - - if (![self supportFileLevelAPI] && [self respondsToSelector:@selector(updateLastModificationDate:forPath:)]) - [self performSelector:@selector(updateLastModificationDate:forPath:) withObject:nil withObject:unshadowed]; - } - } -} - @end @@ -786,7 +820,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification if (!pathModificationDates) { pathModificationDates = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"pathModificationDates"] mutableCopy]; - + if (!pathModificationDates) pathModificationDates = [NSMutableDictionary new]; } @@ -804,7 +838,7 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification if (!pathModificationDates) { pathModificationDates = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"pathModificationDates"] mutableCopy]; - + if (!pathModificationDates) pathModificationDates = [NSMutableDictionary new]; } diff --git a/Tools/XcodeCapp/XcodeCapp.xcodeproj/project.pbxproj b/Tools/XcodeCapp/XcodeCapp.xcodeproj/project.pbxproj index 94becb428..13d898cad 100644 --- a/Tools/XcodeCapp/XcodeCapp.xcodeproj/project.pbxproj +++ b/Tools/XcodeCapp/XcodeCapp.xcodeproj/project.pbxproj @@ -194,6 +194,7 @@ isa = PBXNativeTarget; buildConfigurationList = 027D99A313696A7000D3DB13 /* Build configuration list for PBXNativeTarget "XcodeCapp" */; buildPhases = ( + 024699001602C04A00F0AE43 /* ShellScript */, 027D999A13696A7000D3DB13 /* Resources */, 027D999D13696A7000D3DB13 /* Sources */, 02998CF81369C383006C73DB /* CopyFiles */, @@ -255,6 +256,24 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 024699001602C04A00F0AE43 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/*.m", + "$(SRCROOT)/*.h", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /usr/bin/perl; + shellScript = "#!/usr/bin/perl\n\nwhile (<>) {\n s/\\s+$//;\n print \"$_\\n\";\n}"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 027D999D13696A7000D3DB13 /* Sources */ = { isa = PBXSourcesBuildPhase;