I'm developing an app that uses SMAppService to register a launch daemon. I'm running into big troubles with having the daemon start up reliably because, over the course of testing, the location of my app bundle has changed. Various workflows for this app require testing it running from with /Applications, /Applications/Utilities, or in the project's build directories, which includes both debug and release builds. And I need to account for users moving the app bundle around.
The trouble is that the launch daemon seems to want to launch from the first path it was registered, not at the path it was last registered. For example, if my app originally registered it while located in /Applications/MyApp.app, then the launch daemon will only run from within an app bundle at that specific location, even if there is no app bundle at that path.
As per advice on these forums, I've tried accounting for this by unregistering the daemon, waiting two seconds, and then re-registering it. But in practice this isn't working. I can use sfltool dumbtm and see that the app is still registered in /Applications/MyApp.app and not its new location, and the launch daemon simply silently fails to launch -- SMAppService returns no error. sudo launchctl list displays the job as having status 78. If I set up a stdout/stderr redireciton in its job file, the files are empty.
(I'm aware that I can use sfltool resetbtm to completely reset all login items and background tasks and temporarily fix this issue at least on my own system, but I can hardly have my app use that as part of its standard installation procedure. I need some way of dealing with this that doesn't requiring reseting the state of every app that has a background item.)
I'm starting to pull my hair out dealing with this! (Figuratively, mind you. Not literally, at least not yet.) How can I get this to work? This API is starting to seem like a downgrade from placing a plist file in /Library/LaunchDaemons because at least that was able to work very reliably.
So, yeah, SMAppService is gluing together two subsystems:
- Launch Services, where keeps track of multiple instances of an app and has the concept of the default instance
launchd, which is all about fixed paths
This combo is fundamentally problematic, even if everything goes well.
In practice this isn’t usually as bad as it could be because most users maintain a single copy of your app and put it in the Applications folder. However, not all users do that.
And, oof course, your development machine will likely have multiple copies of your app, and thus you’re much more likely to see problems like this there.
Let’s start by talking about user machines. Many apps have a first launch check, one that checks whether they’re located in Applications and prompts the user to move the app there if they’re not. I’m not super enthusiastic about that concept in general, but if your app has an embedded launchd daemon I think it makes sense.
Also, it probably makes sense to have some sort of self-repair mechanism, so something that detects that things have gone wrong and either tells the user how to fix it or offers to fix it for them.
Second, there’s the question as to what to do about your development machine. Personally, I tackle that in one of two ways:
- I do whatever it takes to speed up my build-run-debug cycle. Usually this is something hacky. You can find an example of that in Debugging a Network Extension Provider (different technology, similar issue).
- When it comes to testing user scenarios, I do that in a VM. That way I can revert to a snapshot between tests. For example, I might have a snapshot for testing initial install and another snapshot for testing upgrade.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"