1

I have a Cocoa app that does a number of things, but among them, uses 'xcodebuild' to clean and re-build a iOS project in XCode. The Cocoa app was developed using XCode 4.1, and runs only on OS X 10.7 (because of the NSTask termination handlers). I hadn't touched it for a few months, and after I had upgraded to XCode 4.2 I found that only the first NSTask that ran xcodebuild actually did anything. Subsequent calls do nothing.

Some details:

  • The iOS project being built sits within the resource bundle of the Cocoa app. So basically, the Cocoa app is a wrapper for an iOS project. Imagine a GUI with a big red button that says: "Build me an iOS app!"
  • This code worked perfectly in XCode 4.1. Task 1 executed, then in the termination handler for task 1, task 2 was called, and ran just fine. The problem is that task 2 no longer runs at all.
  • I have since upgraded to xcode 4.3, which required me to also install the standalone Xcode command line tools. The code still doesn't work properly though.
  • I can run both the 'xcodebuild clean' and 'xcodebuild' commands in Terminal with no problems.

Below is a (paraphrased) version of the offending code.

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self runTaskOne];
}

// Clean the iOS Project
- (void) runTaskOne
{
NSTask *task = [[NSTask alloc] init];
[task setCurrentDirectoryPath:@".../myproject/DerivedData/myproject/Build/Products/Debug/myproject.app/Contents/Resources/iOSProjectFolder"];
[task setLaunchPath:@"/usr/bin/xcodebuild"];
[task setArguments:[NSArray arrayWithObjects:@"clean", nil]];
[task setTerminationHandler:^(NSTask *task)
 {
     [self runTaskTwo];
 }];
[task launch];

}

// Build the same iOS project that was just cleaned
- (void) runTaskTwo;
{
NSTask *task = [[NSTask alloc] init];
[task setCurrentDirectoryPath:@".../myproject/DerivedData/myproject/Build/Products/Debug/myproject.app/Contents/Resources/iOSProjectFolder"];
[task setLaunchPath:@"/usr/bin/xcodebuild"];
[task launch];
} 

@end

So just to re-iterate: Task 1 (the clean) works fine. Task 2 (the build) doesn't even seem launch. This behaviour is only observed after upgrading to XCode 4.2. I must be doing something wrong, but what?

churowa
  • 220
  • 1
  • 12
  • It does. Actually, I've just noticed that the terminationHandler for Task 1 is never run. ... Aaand after a bit of searching, I've found the answer. Writing it up now. – churowa Feb 28 '12 at 16:10

2 Answers2

1

It seems as though I ran into a bug in Xcode, something similar to this: NSTask NSPipe - objective c command line help

Basically, Xcode will hold on to the output of the NSTask if NSTask has already been launched once.

The solution was to switch the Task's standard input thusly:

[task setStandardInput:[NSPipe pipe]];

As soon as I put that into the runTaskOne method, everything started working nicely.

Community
  • 1
  • 1
churowa
  • 220
  • 1
  • 12
0

If your project uses ARC (Automatic Reference Counting), the NSTask instance is automatically released at the end of the runTaskOne method. So the completion handler is never invoked because the object that is supposed to observe the process no longer exists.

You need to keep a reference to the task by storing it in an instance variable.

Nicolas Bachschmidt
  • 6,475
  • 2
  • 26
  • 36
  • Thanks for the reply Nicolas. What I've found is that by adding a pipe as standard input to the task like: `[task setStandardInput:[NSPipe pipe]];` solves the problem. The termination handler is run, and everything's ok. – churowa Feb 28 '12 at 16:27