23
23
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
26
#include <AppKit/AppKit.h>
28
#include <ProjectCenter/PCDefines.h>
29
#include <ProjectCenter/PCSplitView.h>
30
#include <ProjectCenter/PCButton.h>
32
#include <ProjectCenter/PCFileManager.h>
34
#include <ProjectCenter/PCProjectManager.h>
35
#include <ProjectCenter/PCProject.h>
36
#include <ProjectCenter/PCProjectBuilder.h>
37
#include <ProjectCenter/PCProjectBuilderOptions.h>
39
#include <ProjectCenter/PCProjectEditor.h>
40
#include <Protocols/CodeEditor.h>
42
#include <ProjectCenter/PCLogController.h>
43
#include <ProjectCenter/PCPrefController.h>
26
#import <AppKit/AppKit.h>
28
#import <ProjectCenter/PCDefines.h>
29
#import <ProjectCenter/PCButton.h>
31
#import <ProjectCenter/PCFileManager.h>
33
#import <ProjectCenter/PCProjectManager.h>
34
#import <ProjectCenter/PCProject.h>
35
#import <ProjectCenter/PCProjectWindow.h>
36
#import <ProjectCenter/PCProjectBuilder.h>
37
#import <ProjectCenter/PCProjectBuilderOptions.h>
39
#import <ProjectCenter/PCProjectEditor.h>
40
#import <Protocols/CodeEditor.h>
41
#import <ProjectCenter/PCSaveModified.h>
43
#import <ProjectCenter/PCLogController.h>
44
#import <Protocols/Preferences.h>
46
#import "../Modules/Preferences/Build/PCBuildPrefs.h"
46
49
#define IMAGE(X) [NSImage imageNamed: X]
419
450
- (void)cleanupAfterMake
452
NSString *statusString;
421
454
if (_isBuilding || _isCleaning)
423
[statusField setStringValue:[NSString stringWithFormat:
424
@"%@ - %@ terminated", [project projectName], buildStatusTarget]];
456
statusString =[NSString stringWithFormat:
457
@"%@ - %@ terminated", [project projectName], buildStatusTarget];
458
[statusField setStringValue:statusString];
459
[[project projectWindow] updateStatusLineWithText:statusString];
427
462
// Restore buttons state
428
if ([buildStatusTarget isEqualToString:@"Build"])
430
465
[buildButton setState:NSOffState];
431
466
[cleanButton setEnabled:YES];
433
else if ([buildStatusTarget isEqualToString:@"Clean"])
469
else if (_isCleaning)
435
471
[cleanButton setState:NSOffState];
436
472
[buildButton setEnabled:YES];
439
476
[buildArgs removeAllObjects];
440
477
[buildStatusTarget setString:@"Default"];
444
[currentBuildPath release];
445
[currentBuildFile release];
479
// Initiated in [self build:]
480
[currentBuildPath release];
481
[currentBuildFile release];
453
485
- (BOOL)prebuildCheck
455
PCPrefController *prefs = [PCPrefController sharedPCPreferences];
456
PCFileManager *pcfm = [PCFileManager defaultManager];
457
NSFileManager *fm = [NSFileManager defaultManager];
458
NSString *buildDir = [prefs objectForKey:RootBuildDirectory];
459
NSString *projectBuildDir;
487
PCFileManager *pcfm = [PCFileManager defaultManager];
488
NSFileManager *fm = [NSFileManager defaultManager];
490
PCProjectEditor *projectEditor;
461
// Checking prerequisites
493
// Checking for project 'edited' state
462
494
if ([project isProjectChanged])
464
if (NSRunAlertPanel(@"Project Changed!",
465
@"Should it be saved first?",
466
@"Yes", @"No", nil) == NSAlertDefaultReturn)
496
ret = NSRunAlertPanel(@"Project Build",
497
@"Project was changed and not saved.\n"
498
@"Do you want to save project before building it?",
499
@"Stop Build", @"Save and Build", nil);
502
case NSAlertDefaultReturn: // Stop Build
506
case NSAlertAlternateReturn: // Save Project
473
// Synchronize PC.project and generated files just for case
513
// Synchronize PC.project and generate files
477
// Get make tool path
478
makePath = [[NSUserDefaults standardUserDefaults] objectForKey:BuildTool];
517
// Checking if edited files exist
518
projectEditor = [project projectEditor];
519
if ([projectEditor hasModifiedFiles])
521
if (!PCRunSaveModifiedFilesPanel(projectEditor,
480
if (!makePath || ![[NSFileManager defaultManager] fileExistsAtPath:makePath])
530
// Check build tool path
531
if (!buildTool || ![fm fileExistsAtPath:buildTool])
482
NSRunAlertPanel(@"Build terminated",
483
@"Build tool not found.\nFile \"%@\" doesn't exist!",
484
@"OK", nil, nil, makePath);
533
NSRunAlertPanel(@"Project Build",
534
@"Build tool '%@' not found. Check preferences.\n"
535
@"Build will be terminated.",
536
@"Close", nil, nil, buildTool);
488
540
// Create root build directory if not exist
489
projectBuildDir = [NSString stringWithFormat:@"%@.build",
490
[project projectName]];
491
projectBuildDir = [buildDir stringByAppendingPathComponent:projectBuildDir];
492
if (![fm fileExistsAtPath:buildDir] ||
493
![fm fileExistsAtPath:projectBuildDir])
541
if (rootBuildDir && ![rootBuildDir isEqualToString:@""])
495
[pcfm createDirectoriesIfNeededAtPath:projectBuildDir];
544
stringWithFormat:@"%@.build", [project projectName]];
545
buildDir = [rootBuildDir stringByAppendingPathComponent:buildDir];
546
if (![fm fileExistsAtPath:rootBuildDir] ||
547
![fm fileExistsAtPath:buildDir])
549
[pcfm createDirectoriesIfNeededAtPath:buildDir];
516
571
// Prepearing to building
517
logPipe = [NSPipe pipe];
518
readHandle = [logPipe fileHandleForReading];
519
[readHandle waitForDataInBackgroundAndNotify];
573
stdOutPipe = [[NSPipe alloc] init];
574
stdOutHandle = [stdOutPipe fileHandleForReading];
575
[stdOutHandle waitForDataInBackgroundAndNotify];
521
577
[NOTIFICATION_CENTER addObserver:self
522
578
selector:@selector(logStdOut:)
523
579
name:NSFileHandleDataAvailableNotification
580
object:stdOutHandle];
527
errorPipe = [NSPipe pipe];
528
errorReadHandle = [errorPipe fileHandleForReading];
529
[errorReadHandle waitForDataInBackgroundAndNotify];
582
_isErrorLogging = YES;
583
stdErrorPipe = [[NSPipe alloc] init];
584
stdErrorHandle = [stdErrorPipe fileHandleForReading];
585
[stdErrorHandle waitForDataInBackgroundAndNotify];
531
587
[NOTIFICATION_CENTER addObserver:self
532
588
selector:@selector(logErrOut:)
533
589
name:NSFileHandleDataAvailableNotification
534
object:errorReadHandle];
535
_isErrorLogging = YES;
590
object:stdErrorHandle];
536
592
[errorsCountField setStringValue:[NSString stringWithString:@""]];
538
594
warningsCount = 0;
540
596
[statusField setStringValue:buildStatus];
597
[[project projectWindow] updateStatusLineWithText:buildStatus];
543
600
[logOutput setString:@""];
667
// TODO: Strange behaviour of pipe and file handlers alloc/release. Also
668
// they have big retain count here (2 or 3). Why? Notification retains it?
604
669
RELEASE(makeTask);
607
// Wait for logging end
672
// Wait while logging ends
608
673
while (_isLogging || _isErrorLogging)
610
675
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
611
676
beforeDate:[NSDate distantFuture]];
680
RELEASE(stdErrorPipe);
614
682
[self updateErrorsCountField];
619
[NSString stringWithFormat:@"=== %@ succeeded! ===", buildStatusTarget]
622
[statusField setStringValue:[NSString stringWithFormat:
623
@"%@ - %@ succeeded", [project projectName], buildStatusTarget]];
686
logString = [NSString stringWithFormat:@"=== %@ succeeded! ===",
688
statusString = [NSString stringWithFormat:@"%@ - %@ succeeded",
689
[project projectName], buildStatusTarget];
628
[NSString stringWithFormat:@"=== %@ terminated! ===", buildStatusTarget]
693
logString = [NSString stringWithFormat:@"=== %@ terminated! ===",
631
695
if (errorsCount > 0)
633
[statusField setStringValue:[NSString stringWithFormat:
697
statusString = [NSString stringWithFormat:
634
698
@"%@ - %@ failed (%i errors)",
635
[project projectName], buildStatusTarget, errorsCount]];
699
[project projectName], buildStatusTarget, errorsCount];
639
[statusField setStringValue:[NSString stringWithFormat:
641
[project projectName], buildStatusTarget]];
703
statusString = [NSString stringWithFormat:@"%@ - %@ failed",
704
[project projectName], buildStatusTarget];
707
[statusField setStringValue:statusString];
708
[[project projectWindow] updateStatusLineWithText:statusString];
709
[self logBuildString:logString newLine:YES];
645
711
// Run post process if configured
646
712
/* if (status && postProcess)
649
715
postProcess = NULL;
654
718
[self cleanupAfterMake];
657
- (void)logStdOut:(NSNotification *)aNotif
661
// NSLog(@"logStdOut");
663
if ((data = [readHandle availableData]) && [data length] > 0)
665
[self logData:data error:NO];
670
[readHandle waitForDataInBackgroundAndNotify];
675
[NOTIFICATION_CENTER removeObserver:self
676
name:NSFileHandleDataAvailableNotification
681
- (void)logErrOut:(NSNotification *)aNotif
685
// NSLog(@"logErrOut");
687
if ((data = [errorReadHandle availableData]) && [data length] > 0)
689
[self logData:data error:YES];
694
[errorReadHandle waitForDataInBackgroundAndNotify];
698
_isErrorLogging = NO;
699
[NOTIFICATION_CENTER removeObserver:self
700
name:NSFileHandleDataAvailableNotification
701
object:errorReadHandle];
721
// --- BuilderOptions delegate
722
- (void)targetDidSet:(NSString *)target
724
[buildTarget setString:target];
725
[self updateTargetField];
730
@implementation PCProjectBuilder (Logging)
705
732
- (void)updateErrorsCountField
707
734
NSString *string;
708
NSString *errorsString = [NSString stringWithString:@""];;
735
NSString *errorsString = [NSString stringWithString:@""];
709
736
NSString *warningsString = [NSString stringWithString:@""];
711
738
if (errorsCount > 0)
738
765
[errorsCountField setStringValue:string];
741
// --- BuilderOptions delgate
742
- (void)targetDidSet:(NSString *)target
744
[buildTarget setString:target];
745
[self updateTargetField];
750
@implementation PCProjectBuilder (BuildLogging)
752
- (void)logString:(NSString *)str
754
newLine:(BOOL)newLine
756
// NSTextView *out = (yn) ? errorOutput : logOutput;
757
NSTextView *out = logOutput;
759
[out replaceCharactersInRange:
760
NSMakeRange([[out string] length],0) withString:str];
764
[out replaceCharactersInRange:
765
NSMakeRange([[out string] length], 0) withString:@"\n"];
769
[out replaceCharactersInRange:
770
NSMakeRange([[out string] length], 0) withString:@" "];
773
[out scrollRangeToVisible:NSMakeRange([[out string] length], 0)];
774
[out setNeedsDisplay:YES];
768
// --- Data notifications
769
// Both methods make call to dipatcher logData:error:
770
- (void)logStdOut:(NSNotification *)aNotif
774
if ((data = [stdOutHandle availableData]) && [data length] > 0)
776
[self logData:data error:NO];
781
[stdOutHandle waitForDataInBackgroundAndNotify];
785
[NOTIFICATION_CENTER removeObserver:self
786
name:NSFileHandleDataAvailableNotification
787
object:stdOutHandle];
792
- (void)logErrOut:(NSNotification *)aNotif
796
if ((data = [stdErrorHandle availableData]) && [data length] > 0)
798
[self logData:data error:YES];
803
[stdErrorHandle waitForDataInBackgroundAndNotify];
807
[NOTIFICATION_CENTER removeObserver:self
808
name:NSFileHandleDataAvailableNotification
809
object:stdErrorHandle];
810
_isErrorLogging = NO;
777
815
- (void)logData:(NSData *)data
780
818
NSString *dataString;
781
819
NSRange newLineRange;
826
869
RELEASE(dataString);
829
- (void)parseBuildLine:(NSString *)string
831
NSArray *components = [string componentsSeparatedByString:@" "];
838
if ([components containsObject:@"Compiling"] &&
839
[components containsObject:@"file"])
841
NSLog(@"Current build file: %@", [components objectAtIndex:3]);
842
[currentBuildFile setString:[components objectAtIndex:3]];
844
else if ([components containsObject:@"Entering"] &&
845
[components containsObject:@"directory"])
848
NSString *pathComponent = [components objectAtIndex:3];
850
NSLog(@"Go down to %@", pathComponent);
874
@implementation PCProjectBuilder (BuildLogging)
876
// --- Parsing utilities
877
- (BOOL)line:(NSString *)lineString startsWithString:(NSString *)substring
880
NSRange range = NSMakeRange(position,1);
882
while ([[lineString substringFromRange:range] isEqualToString:@" "])
884
range.location = ++position;
887
/* NSLog(@"Line '%@' position: %i substring '%@'",
888
lineString, position, substring);*/
890
range = [lineString rangeOfString:substring];
891
if ((range.location == NSNotFound) ||
892
(range.location != position))
900
// Clean leading spaces and return cleaned array of components
901
- (NSArray *)componentsOfLine:(NSString *)lineString
903
NSArray *lineComponents;
904
NSMutableArray *tempComponents;
906
lineComponents = [lineString componentsSeparatedByString:@" "];
907
tempComponents = [NSMutableArray arrayWithArray:lineComponents];
909
while ([[tempComponents objectAtIndex:0] isEqualToString:@""])
911
[tempComponents removeObjectAtIndex:0];
914
return tempComponents;
917
// Line starts with 'gmake' or 'make'.
918
// Changes 'currentBuildPath' if line starts with
919
// "Entering directory" or "Leaving directory".
921
// gmake[1]: Entering directory '/Users/me/Project/Subproject.subproj'
922
- (void)parseMakeLine:(NSString *)lineString
924
NSMutableArray *makeLineComponents;
926
NSString *pathComponent;
929
// NSLog(@"parseMakeLine: %@", lineString);
931
makeLineComponents = [NSMutableArray
932
arrayWithArray:[lineString componentsSeparatedByString:@" "]];
934
// Don't check for item at index 0 contents (it's 'gmake[1]:' or 'make[1]:')
936
[makeLineComponents removeObjectAtIndex:0];
937
makeLine = [makeLineComponents componentsJoinedByString:@" "];
939
if ([self line:makeLine startsWithString:@"Entering directory"])
941
pathComponent = [makeLineComponents objectAtIndex:2];
851
942
path = [pathComponent
852
943
substringWithRange:NSMakeRange(1,[pathComponent length]-3)];
853
[currentBuildPath addObject:path];
854
NSLog(@"%@", [currentBuildPath lastObject]);
856
else if ([components containsObject:@"Leaving"] &&
857
[components containsObject:@"directory"])
859
NSLog(@"Go up from %@", [components objectAtIndex:3]);
860
[currentBuildPath removeLastObject];
861
NSLog(@"%@", [currentBuildPath lastObject]);
944
// NSLog(@"Go down to %@", path);
945
[currentBuildPath setString:path];
947
else if ([self line:makeLine startsWithString:@"Leaving directory"])
949
// NSLog(@"Go up from %@", [makeLineComponents objectAtIndex:2]);
951
setString:[currentBuildPath stringByDeletingLastPathComponent]];
953
// NSLog(@"Current build path: %@", currentBuildPath);
959
// Also updates currentBuildFile
960
- (NSString *)parseCompilerLine:(NSString *)lineString
962
NSArray *lineComponents = [self componentsOfLine:lineString];
963
NSString *outputString = nil;
965
if ([lineComponents containsObject:@"-c"])
967
[currentBuildFile setString:[lineComponents objectAtIndex:1]];
968
outputString = [NSString
969
stringWithFormat:@" Compiling %@...\n", currentBuildFile];
971
else if ([lineComponents containsObject:@"-rdynamic"])
973
outputString = [NSString
974
stringWithFormat:@" Linking %@...\n",
975
[lineComponents objectAtIndex:[lineComponents indexOfObject:@"-o"]+1]];
980
// --- Parsing utilities end
983
- (void)logBuildString:(NSString *)string
984
newLine:(BOOL)newLine
986
NSString *logString = [self parseBuildLine:string];
993
[logOutput replaceCharactersInRange:
994
NSMakeRange([[logOutput string] length],0) withString:logString];
998
[logOutput replaceCharactersInRange:
999
NSMakeRange([[logOutput string] length], 0) withString:@"\n"];
1002
[logOutput scrollRangeToVisible:NSMakeRange([[logOutput string] length], 0)];
1003
[logOutput setNeedsDisplay:YES];
1006
// Standard out is parsed for detection of directory, file, etc.
1007
// Gets complete line (ended with '\n') as argument
1008
- (NSString *)parseBuildLine:(NSString *)string
1010
NSArray *components = [self componentsOfLine:string];
1011
NSString *parsedString = nil;
1018
if ([self line:string startsWithString:@"gmake"] ||
1019
[self line:string startsWithString:@"make"])
1020
{// Do current path detection
1021
[self parseMakeLine:string];
1023
else if ([self line:string startsWithString:@"gcc"])
1024
{// Parse compiler output
1025
parsedString = [self parseCompilerLine:string];
1027
else if ([self line:string startsWithString:@"Making"] ||
1028
[self line:string startsWithString:@"==="])
1029
{// It's a gnustep-make and self output
1030
parsedString = string;
1033
if (parsedString && ![self line:parsedString startsWithString:@"==="])
1035
[statusField setStringValue:parsedString];
1036
[[project projectWindow] updateStatusLineWithText:parsedString];
1039
if (verboseBuilding)
1045
return parsedString;