Permalink
Browse files

Rudimentary progress/cancel support.

  • Loading branch information...
Jim Puls + Zach Margolis
Jim Puls + Zach Margolis committed Nov 8, 2011
1 parent 670ac08 commit 38ce8cda4a74d826f309ed9e6d4bf4890bf45afa
@@ -15,6 +15,8 @@
A848E80613ECA621004A55DA /* ZappRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = A848E80513ECA621004A55DA /* ZappRepository.m */; };
A855039414061F29004CA7AA /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A855039314061F29004CA7AA /* AVFoundation.framework */; };
A8783CE413F114130001493A /* Zapp.icns in Resources */ = {isa = PBXBuildFile; fileRef = A8783CE313F114130001493A /* Zapp.icns */; };
+ A87BB37B145B7D5800F52025 /* reveal.png in Resources */ = {isa = PBXBuildFile; fileRef = A87BB37A145B7D5800F52025 /* reveal.png */; };
+ A87BB37E145B7DEF00F52025 /* hide.png in Resources */ = {isa = PBXBuildFile; fileRef = A87BB37D145B7DEF00F52025 /* hide.png */; };
A8A49ED313EB99FB00A0338E /* ZappRepositoriesController.m in Sources */ = {isa = PBXBuildFile; fileRef = A8A49ED213EB99FB00A0338E /* ZappRepositoriesController.m */; };
A8B31D3513E4D610001D593D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8B31D3413E4D610001D593D /* Cocoa.framework */; };
A8B31D3F13E4D611001D593D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A8B31D3D13E4D611001D593D /* InfoPlist.strings */; };
@@ -46,6 +48,8 @@
A848E80513ECA621004A55DA /* ZappRepository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZappRepository.m; sourceTree = "<group>"; };
A855039314061F29004CA7AA /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
A8783CE313F114130001493A /* Zapp.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Zapp.icns; sourceTree = "<group>"; };
+ A87BB37A145B7D5800F52025 /* reveal.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = reveal.png; sourceTree = "<group>"; };
+ A87BB37D145B7DEF00F52025 /* hide.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hide.png; sourceTree = "<group>"; };
A8A49ED113EB99FB00A0338E /* ZappRepositoriesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZappRepositoriesController.h; sourceTree = "<group>"; };
A8A49ED213EB99FB00A0338E /* ZappRepositoriesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZappRepositoriesController.m; sourceTree = "<group>"; };
A8B31D3013E4D610001D593D /* Zapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Zapp.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -151,6 +155,8 @@
2FF09B941440FA07008E2AF9 /* ZappMail.png */,
A8F5E5E2142EAB3100CC4726 /* greencircle.png */,
A8F5E5E3142EAB3100CC4726 /* redtriangle.png */,
+ A87BB37D145B7DEF00F52025 /* hide.png */,
+ A87BB37A145B7D5800F52025 /* reveal.png */,
A8B31D3C13E4D611001D593D /* Zapp-Info.plist */,
A8B31D3D13E4D611001D593D /* InfoPlist.strings */,
A8B31D4013E4D611001D593D /* main.m */,
@@ -262,6 +268,8 @@
A8F5E5E5142EAB3100CC4726 /* redtriangle.png in Resources */,
2FF09B951440FA07008E2AF9 /* ZappMail.png in Resources */,
A81A90091464715800232B33 /* Credits.rtf in Resources */,
+ A87BB37B145B7D5800F52025 /* reveal.png in Resources */,
+ A87BB37E145B7DEF00F52025 /* hide.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
@@ -14,6 +14,9 @@
@interface ZappAppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDelegate>
+@property (nonatomic, strong) IBOutlet NSButton *activityButton;
+@property (nonatomic, strong) IBOutlet NSArrayController *activityController;
+@property (nonatomic, strong) IBOutlet NSSplitView *activitySplitView;
@property (nonatomic, strong) IBOutlet NSArrayController *buildsController;
@property (nonatomic, strong) IBOutlet NSArrayController *logController;
@property (nonatomic, strong) IBOutlet NSScrollView *logScrollView;
@@ -28,9 +31,13 @@
@property (nonatomic, strong) IBOutlet NSTableView *sourceListView;
@property (nonatomic, strong) IBOutlet NSWindow *window;
@property (nonatomic, getter = isBuilding) BOOL building;
+@property (nonatomic, strong, readonly) NSMutableArray *buildQueue;
+@property (nonatomic, strong) IBOutlet NSTableView *activityTableView;
- (IBAction)build:(id)sender;
- (IBAction)chooseLocalPath:(id)sender;
- (IBAction)clone:(id)sender;
+- (IBAction)toggleActivity:(id)sender;
+- (IBAction)cancelBuild:(id)sender;
@end
View
@@ -17,7 +17,7 @@
@interface ZappAppDelegate ()
-@property (nonatomic, strong) NSMutableOrderedSet *buildQueue;
+@property (nonatomic, strong) NSMutableArray *buildQueue;
@property (nonatomic, readonly) ZappRepository *selectedRepository;
- (void)hideProgressPanel;
@@ -32,8 +32,12 @@ - (void)updateSourceListBackground:(NSNotification *)notification;
@implementation ZappAppDelegate
+@synthesize activityButton;
+@synthesize activityController;
@synthesize building;
+@synthesize activitySplitView;
@synthesize buildQueue;
+@synthesize activityTableView;
@synthesize buildsController;
@synthesize logController;
@synthesize logScrollView;
@@ -89,6 +93,34 @@ - (IBAction)clone:(id)sender;
}];
}
+- (IBAction)toggleActivity:(id)sender;
+{
+ NSRect boundsRect = activitySplitView.bounds;
+ CGFloat multiplier = 0.8;
+ if (activityButton.state == NSOffState) {
+ multiplier = 1.0;
+ }
+
+ NSRect newTopFrame, newBottomFrame;
+ NSDivideRect(boundsRect, &newTopFrame, &newBottomFrame, multiplier * boundsRect.size.height, NSMaxYEdge);
+
+ newBottomFrame.size.height -= activitySplitView.dividerThickness;
+ [[[[activitySplitView subviews] objectAtIndex:0] animator] setFrame:newTopFrame];
+ [[[[activitySplitView subviews] objectAtIndex:1] animator] setFrame:newBottomFrame];
+}
+
+- (IBAction)cancelBuild:(id)sender;
+{
+ NSInteger row = [self.activityTableView rowForView:sender];
+ ZappBuild *build = [self.buildQueue objectAtIndex:row];
+
+ if (build.status == ZappBuildStatusPending) {
+ [self.activityController removeObject:build];
+ }
+
+ [build cancel];
+}
+
#pragma mark NSApplicationDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@@ -97,7 +129,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSourceListBackground:) name:NSApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSourceListBackground:) name:NSApplicationDidResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:nil];
- self.buildQueue = [NSMutableOrderedSet orderedSet];
+
+ [self toggleActivity:nil];
+
+ self.buildQueue = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"startTimestamp" ascending:NO];
self.buildsController.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
@@ -192,16 +227,17 @@ - (void)pollRepositoriesForUpdates;
- (void)pumpBuildQueue;
{
+ [self.activityController rearrangeObjects];
if (!self.buildQueue.count || self.building) {
return;
}
ZappBuild *build = [self.buildQueue objectAtIndex:0];
self.building = YES;
[build startWithCompletionBlock:^{
self.building = NO;
+ [self.buildQueue removeObject:build];
[self pumpBuildQueue];
}];
- [self.buildQueue removeObject:build];
}
- (ZappBuild *)scheduleBuildForRepository:(ZappRepository *)repository;
View
@@ -36,6 +36,10 @@
@property (nonatomic, readonly) ZappBuild *previousBuild;
@property (nonatomic, readonly) ZappBuild *previousSuccessfulBuild;
+@property (nonatomic) double progress;
+@property (nonatomic, readonly) NSString *activityTitle;
+
- (void)startWithCompletionBlock:(void (^)(void))completionBlock;
+- (void)cancel;
@end
View
@@ -12,6 +12,10 @@
#import "ZappSimulatorController.h"
+#define UPDATE_PHASE_PROGRESS 0.1
+#define BUILD_PHASE_PROGRESS 0.1
+
+
@interface ZappBuild ()
@property (nonatomic, strong, readwrite) NSArray logLines;
@@ -20,6 +24,8 @@ @interface ZappBuild ()
@property (nonatomic, strong, readwrite) NSFetchRequest *previousBuildFetchRequest;
@property (nonatomic, strong, readwrite) NSFetchRequest *previousSuccessfulBuildFetchRequest;
+@property (readonly, getter = isRunning) BOOL running;
+
- (void)appendLogLines:(NSString *)newLogLinesString;
- (NSURL *)appSupportURLWithExtension:(NSString *)extension;
- (void)runSimulatorWithAppPath:(NSString *)appPath initialSkip:(NSInteger)initialSkip failureCount:(NSInteger)initialFailureCount startsWithRetry:(BOOL)startsWithRetry;
@@ -44,9 +50,17 @@ @implementation ZappBuild
@synthesize previousSuccessfulBuildFetchRequest;
@synthesize simulatorController;
@synthesize completionBlock;
+@synthesize progress;
#pragma mark Accessors
+- (BOOL)isRunning;
+{
+ @synchronized(self) {
+ return self.status == ZappBuildStatusRunning;
+ }
+}
+
- (NSString *)commitLog;
{
if (!commitLog) {
@@ -115,6 +129,16 @@ + (NSSet *)keyPathsForValuesAffectingDescription;
return [NSSet setWithObjects:@"startTimestamp", @"status", @"latestRevision", nil];
}
+- (NSString *)activityTitle;
+{
+ return [NSString stringWithFormat:@"%@: %@", self.repository.name, self.abbreviatedLatestRevision];
+}
+
++ (NSSet *)keyPathsForValuesAffectingActivityTitle;
+{
+ return [NSSet setWithObjects:@"repository.name", @"abbreviatedLatestRevision", nil];
+}
+
- (NSArray *)logLines;
{
[self willAccessValueForKey:@"logLines"];
@@ -205,15 +229,28 @@ - (NSFetchRequest *)previousBuildFetchRequest;
#pragma mark ZappBuild
+- (void)cancel;
+{
+ @synchronized (self) {
+ if (self.status <= ZappBuildStatusRunning) {
+ self.status = ZappBuildStatusFailed;
+ }
+ }
+}
+
- (void)startWithCompletionBlock:(void (^)(void))theCompletionBlock;
{
- self.status = ZappBuildStatusRunning;
+ @synchronized (self) {
+ self.status = ZappBuildStatusRunning;
+ }
self.startTimestamp = [NSDate date];
self.scheme = self.repository.lastScheme;
self.platform = self.repository.lastPlatform;
self.branch = self.repository.lastBranch;
self.completionBlock = theCompletionBlock;
+ self.progress = 0.0;
+
self.logLines = nil;
ZappRepository *repository = self.repository;
@@ -228,13 +265,15 @@ - (void)startWithCompletionBlock:(void (^)(void))theCompletionBlock;
BOOL (^runGitCommandWithArguments)(NSArray *) = ^(NSArray *arguments) {
NSString *errorOutput = nil;
NSLog(@"running %@ %@", GitCommand, [arguments componentsJoinedByString:@" "]);
- int exitStatus = [repository runCommandAndWait:GitCommand withArguments:arguments standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output) {
+ int exitStatus = [repository runCommandAndWait:GitCommand withArguments:arguments standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output, BOOL *stop) {
[fileHandle writeData:[output dataUsingEncoding:NSUTF8StringEncoding]];
[self appendLogLines:output];
+
+ *stop = (self.status != ZappBuildStatusRunning);
}];
[fileHandle writeData:[errorOutput dataUsingEncoding:NSUTF8StringEncoding]];
[self appendLogLines:errorOutput];
- if (exitStatus > 0) {
+ if (exitStatus > 0 || !self.running) {
[self callCompletionBlockWithStatus:exitStatus];
return NO;
}
@@ -246,27 +285,31 @@ - (void)startWithCompletionBlock:(void (^)(void))theCompletionBlock;
if (!runGitCommandWithArguments([NSArray arrayWithObjects:@"checkout", self.branch, nil])) { return; }
if (!runGitCommandWithArguments([NSArray arrayWithObjects:@"submodule", @"sync", nil])) { return; }
if (!runGitCommandWithArguments([NSArray arrayWithObjects:@"submodule", @"update", @"--init", nil])) { return; }
- [repository runCommandAndWait:GitCommand withArguments:[NSArray arrayWithObjects:@"rev-parse", @"HEAD", nil] standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output) {
+ [repository runCommandAndWait:GitCommand withArguments:[NSArray arrayWithObjects:@"rev-parse", @"HEAD", nil] standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output, BOOL *stop) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^() {
self.latestRevision = [output stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ self.progress = UPDATE_PHASE_PROGRESS;
+
+ *stop = !self.running;
}];
}];
// Step 2: Build
NSArray *buildArguments = [NSArray arrayWithObjects:@"-sdk", @"iphonesimulator", @"-scheme", self.scheme, @"VALID_ARCHS=i386", @"ARCHS=i386", @"ONLY_ACTIVE_ARCH=NO", @"DSTROOT=build", @"install", nil];
NSRegularExpression *appPathRegex = [NSRegularExpression regularExpressionWithPattern:@"^SetMode .+? \"?([^\"]+\\.app)\"?$" options:NSRegularExpressionAnchorsMatchLines error:nil];
- exitStatus = [repository runCommandAndWait:XcodebuildCommand withArguments:buildArguments standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output) {
+ exitStatus = [repository runCommandAndWait:XcodebuildCommand withArguments:buildArguments standardInput:nil errorOutput:&errorOutput outputBlock:^(NSString *output, BOOL *stop) {
[fileHandle writeData:[output dataUsingEncoding:NSUTF8StringEncoding]];
[self appendLogLines:output];
[appPathRegex enumerateMatchesInString:output options:0 range:NSMakeRange(0, output.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
appPath = [output substringWithRange:[result rangeAtIndex:1]];
*stop = YES;
}];
+ *stop = !self.running;
}];
[fileHandle writeData:[errorOutput dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
[self appendLogLines:errorOutput];
- if (exitStatus > 0) {
+ if (exitStatus > 0 || !self.running) {
[self callCompletionBlockWithStatus:exitStatus];
return;
}
@@ -275,6 +318,10 @@ - (void)startWithCompletionBlock:(void (^)(void))theCompletionBlock;
return;
}
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ self.progress = UPDATE_PHASE_PROGRESS + BUILD_PHASE_PROGRESS;
+ }];
+
// Step 3: Run
[self runSimulatorWithAppPath:appPath initialSkip:0 failureCount:0 startsWithRetry:NO];
}];
@@ -298,12 +345,15 @@ - (void)runSimulatorWithAppPath:(NSString *)appPath initialSkip:(NSInteger)initi
self.simulatorController.videoOutputURL = self.buildVideoURL;
self.simulatorController.environment = [NSDictionary dictionaryWithObjectsAndKeys:@"1", @"KIF_AUTORUN", @"1", @"KIF_EXIT_ON_FAILURE", [NSString stringWithFormat:@"%d", lastStartedScenario], @"KIF_INITIAL_SKIP_COUNT", nil];
NSLog(@"starting simulator with skip count of %ld", lastStartedScenario);
- [self.simulatorController launchSessionWithOutputBlock:^(NSString *output) {
+ [self.simulatorController launchSessionWithOutputBlock:^(NSString *output, BOOL *stop) {
[self appendLogLines:output];
lastOutput = output;
[progressRegex enumerateMatchesInString:output options:0 range:NSMakeRange(0, output.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
lastStartedScenario = [[output substringWithRange:[result rangeAtIndex:1]] integerValue];
scenarioCount = [[output substringWithRange:[result rangeAtIndex:2]] integerValue];
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ self.progress = (double)lastStartedScenario / (double)scenarioCount * (1.0 - UPDATE_PHASE_PROGRESS - BUILD_PHASE_PROGRESS) + UPDATE_PHASE_PROGRESS + BUILD_PHASE_PROGRESS;
+ }];
}];
[failureRegex enumerateMatchesInString:output options:0 range:NSMakeRange(0, output.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (failureCount < 0) {
@@ -312,6 +362,7 @@ - (void)runSimulatorWithAppPath:(NSString *)appPath initialSkip:(NSInteger)initi
failureCount += [[output substringWithRange:[result rangeAtIndex:1]] integerValue];
*stop = YES;
}];
+ *stop = !self.running;
} completionBlock:^(int exitCode) {
// exitCode is probably always going to be 0 (success) coming from the simulator. Use the failure count as our status instead.
NSLog(@"Simulator exited with code %d, failure count is %ld after %ld of %ld scenarios. Last output is %@", exitCode, failureCount, lastStartedScenario, scenarioCount, lastOutput);
@@ -322,24 +373,36 @@ - (void)runSimulatorWithAppPath:(NSString *)appPath initialSkip:(NSInteger)initi
failureCount = 1;
}
NSInteger newFailureCount = failureCount + initialFailureCount;
- if (lastStartedScenario >= scenarioCount) {
- NSLog(@"finished: %ld/%ld", lastStartedScenario, scenarioCount);
- [self callCompletionBlockWithStatus:(int)newFailureCount];
- } else if (startsWithRetry && lastStartedScenario == initialSkip + 1) {
- NSLog(@"retry failed: %ld/%ld", lastStartedScenario, scenarioCount);
- [self runSimulatorWithAppPath:appPath initialSkip:lastStartedScenario failureCount:newFailureCount startsWithRetry:NO];
+ if (self.running) {
+ if (lastStartedScenario >= scenarioCount) {
+ NSLog(@"finished: %ld/%ld", lastStartedScenario, scenarioCount);
+ [self callCompletionBlockWithStatus:(int)newFailureCount];
+ } else if (startsWithRetry && lastStartedScenario == initialSkip + 1) {
+ NSLog(@"retry failed: %ld/%ld", lastStartedScenario, scenarioCount);
+ [self runSimulatorWithAppPath:appPath initialSkip:lastStartedScenario failureCount:newFailureCount startsWithRetry:NO];
+ } else {
+ NSLog(@"retrying: %ld/%ld", lastStartedScenario, scenarioCount);
+ [self runSimulatorWithAppPath:appPath initialSkip:lastStartedScenario - 1 failureCount:newFailureCount - 1 startsWithRetry:YES];
+ }
} else {
- NSLog(@"retrying: %ld/%ld", lastStartedScenario, scenarioCount);
- [self runSimulatorWithAppPath:appPath initialSkip:lastStartedScenario - 1 failureCount:newFailureCount - 1 startsWithRetry:YES];
+ [self callCompletionBlockWithStatus:-1];
}
}];
}
- (void)callCompletionBlockWithStatus:(int)exitStatus;
{
- NSAssert(self.completionBlock, @"Expected a completion block");
[[NSOperationQueue mainQueue] addOperationWithBlock:^() {
- self.status = exitStatus != 0 ? ZappBuildStatusFailed : ZappBuildStatusSucceeded;
+ if (!self.completionBlock) {
+ return;
+ }
+
+ @synchronized (self) {
+ if (self.running) {
+ self.status = exitStatus != 0 ? ZappBuildStatusFailed : ZappBuildStatusSucceeded;
+ }
+ }
+
[ZappMessageController sendMessageIfNeededForBuild:self];
NSLog(@"build complete, exit status %d", exitStatus);
self.endTimestamp = [NSDate date];
View
@@ -29,6 +29,7 @@ typedef enum {
} ZappNotificationOption;
typedef void (^ZappOutputBlock)(NSString *output);
+typedef void (^ZappIntermediateOutputBlock)(NSString *output, BOOL *stop);
typedef void (^ZappResultBlock)(int exitCode);
#endif
Oops, something went wrong.

0 comments on commit 38ce8cd

Please sign in to comment.