3

In OS X how can I automatically re-start my app after pulling down an updated version?

I've looked around some and launchd seems to be the likely way to do this, but I can't seem to get my head around it. I can't seem to find any good resources talking about this specifically either.

I could also create a script or separate process to do this, but that seems clunky at best, I'm expecting there is a better solution out there.

slycrel
  • 4,275
  • 2
  • 30
  • 30
  • I may end up following the advice on this article, though I am still not happy with a separate process to relaunch. http://13bold.com/tutorials/relaunching-your-application/ – slycrel Nov 25 '10 at 00:02

3 Answers3

3

Separate process is good solution, look at Sparkle framework (most of apps use it for autoupdating), they also use for this standalone app - https://github.com/andymatuschak/Sparkle/blob/master/relaunch.m

  • Thanks for the heads up about sparkle Julia, it looks interesting. If I have to go for the separate process route I'll mark you as my answer. Hoping for something without having to write a separate app though. – slycrel Nov 24 '10 at 23:53
  • 1
    Don't re-invent the wheel. Use Sparkle. It's a tried-and-proved solution, used by thousands of 3rd party apps on OS X. – Yuji Nov 25 '10 at 02:36
2

Just expanding on Julia's answer, since I had to restart my App for another reason than updating, I looked into how Sparkle does it -

Latest version(as of 11/2011) of Sparkle has a project target called finish_installation.app that is included in the Resources dir of the Sparkle framework. Sparkle, running as part of the host App, copies finish_application to the application_support directory and uses launched to run its binary executable like this, passing in the host process id and relaunch path:

NSString *relaunchToolPath = [NSString stringWithFormat:@"%@/finish_installation.app/Contents/MacOS/finish_installation", tempDir];
[NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:[NSArray arrayWithObjects:pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], tempDir, relaunch ? @"1" : @"0", nil]];
[NSApp terminate:self];

Seems like with this function, when the parent process quits(?), finish_application's parent becomes launched.

finish_installation waits for the passed in process id to disappear, also has an initial check to see if it's parent is launched (pid=1)

if(getppid() == 1)...
if (GetProcessForPID(parentprocessid, &psn) == procNotFound)...

then launches app with:

[[NSWorkspace sharedWorkspace] openFile: appPath];

Last interesting tidbit: If installation takes a long time, finish_installation changes itself to a foreground process so the user can see that some app is running:

ProcessSerialNumber     psn = { 0, kCurrentProcess };
TransformProcessType( &psn, kProcessTransformToForegroundApplication );
Colin
  • 2,089
  • 25
  • 34
1

Source Blog

- (void) restartOurselves
    {
       //$N = argv[N]
       NSString *killArg1AndOpenArg2Script = @"kill -9 $1 \n open \"$2\"";

       //NSTask needs its arguments to be strings
       NSString *ourPID = [NSString stringWithFormat:@"%d",
                      [[NSProcessInfo processInfo] processIdentifier]];

       //this will be the path to the .app bundle,
       //not the executable inside it; exactly what `open` wants
       NSString * pathToUs = [[NSBundle mainBundle] bundlePath];

       NSArray *shArgs = [NSArray arrayWithObjects:@"-c", // -c tells sh to execute the next argument, passing it the remaining arguments.
                    killArg1AndOpenArg2Script,
                    @"", //$0 path to script (ignored)
                    ourPID, //$1 in restartScript
                    pathToUs, //$2 in the restartScript
                    nil];
       NSTask *restartTask = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:shArgs];
       [restartTask waitUntilExit]; //wait for killArg1AndOpenArg2Script to finish
       NSLog(@"*** ERROR: %@ should have been terminated, but we are still running", pathToUs);
       assert(!"We should not be running!");
    }
Sawan Cool
  • 158
  • 3
  • 13
  • I don't know why, but when I try to do similar stuff from within a GUI app, it always results in `signal: killed` for the child process. – MarSoft Jan 27 '21 at 23:09
  • Turned out that *after* I overwrite my app's executable, MacOS does not allow it to launch anything anymore. So I'll have to first launch "restarter" process and make it wait for signal to do the actual restarting. – MarSoft Jan 28 '21 at 00:11