I finally shipped .pkg (and .clap) support for Pamplejuce, my JUCE plugin template. Here’s what the distribution.xml looks like. I tried to get it as clean as possible (in the end it’s pretty simple).
I also wrote an article for moonbase about how to setup .pkg macOS installers:
As usual, a great article and wonderful dump of knowledge from you, sudara.
Only thing I don’t like is the dependence on Github Actions - it would be great to see an offline/non-GithubActions version of this article at some point in the future. I’ve done this already for my own purposes, but it would be cool to see an offline version of this in pamplejuce at some point down the line as well.
EDIT: Another thing that is ‘missing’ in your solution, in my opinion, is a full logging of all notarization/signing/stapling actions - this is important to have in log form. At least capture the NOTARIZATION_ID from notarytool, so that “xcrun notarytool log” can be run to capture the notarization log from Apple for safe-keeping (important to have in case of future triage events…)
This is awesome! Pamplejuce seems to just get better and better XD
The only problem is still requiring that pesky Apple Developer Account for pkgbuild. For smaller devs it is a little bit out of reach / daunting to pay $100 a year so i think Packages will still be a pretty good option for most people (me included lol)
Thanks for a great article @sudara! Packaging apps and making installers is something most of the merchants we have selling through Moonbase is dealing with, where some have gone to great lengths to build fully custom installer tech, spending weeks on it. Having better options, even if only for Mac is very nice.
One developer I was talking to a while back also baked in auto-updates into their custom installers, do you have any opinion on how to handle that using this approach, or have you not gotten that far with Pamplejuce yet? Maybe it doesn’t need to depend on these tools at all?
Thanks for the nice words! I intentionally only have installers building in CI, as it forces a single consistent automated pathway to building (post tests and/or pluginval passing) which doesn’t depend on a local machine’s (or developer’s!) state. Also, the local machine can then be used for QA/testing of signing, etc. To go local, you could pull the commands out of the github action and toss em in a script.
I tried that once… don’t remember exactly why I didn’t pursue. Ultimately, I preferred the directness of dealing with the first party apple tools. Have you made good experiences with cpack?
I daydream about this, but because plugins are middleware, it’s complicated. Even if you could install in-place, most DAWs would need to be restarted to pick up the new version, I think? Maybe @dave96 knows more. I’ve also heard the dll on windows has a write lock and can’t be replaced while in use? My latest hair-brained idea on “in-place-ish” installing on macOS was something like:
install a standalone along with the plugin that registers an url scheme (like myplugin://)
plugin pings moonbase for the latest version
when one exists, display a link in the plugin to open the standalone (like myplugin://update)
standalone downloads and installs in-place, maybe via sparkle (not actually sure if their api allows installing into /Library/Audio/Plug-Ins)
Plugin tells you to restart the DAW to get the use the update
Ultimately, I think we’d need DAW API be able to “rescan and hot swap” a new plugin version for any solution to be nicer UX than the “download the new pkg, install, restart DAW” workflow people are used to. Until then, I think plugins updates will be manual/crusty/infrequent compared with other modern software. But yeah, imagine devs being able to register new plugin versions with DAW vendors and users being able to update+rescan from their plugin list..
I’ve only used it for Linux deb packaging. It was a bit clunky to set up but I like the idea of setting up a cmake project correctly and then just being able to reuse (pretty much) the same cpack cmake everywhere.
The final idea is that this can all be packaged up in to a cmake workflow preset so you can just type cmake -workflow package on any OS and it does everything required.
Although having everything in the yml is tidy, it does make it very difficult to debug when something goes wrong with the build. For plugins that might not be too bad but for larger projects with longer build times that can be a pain. It’s nice to be able to replicate things locally.
I actually think moving more towards cmake modules to do things like this might be the best idea for the above workflow reasons.
Then the github yml is just the github specific stuff.
My 2¢ anyway.
I think (could be wrong though) you need to unload all the handles to the dll in order to replace it. In practice that might mean restarting (or terminating the hosting process if you’re sandboxing).
From what I understand cpack is great for trivial installers, but as soon as you try to do anything even slightly complicated it becomes a complete nightmare.
This is 2nd hand knowledge though, so feel free to dismiss as nonsense!
Probably. But generally I want to keep my installers simple.
We distribute nearly 100 products and, like many others, to make this easier for users we have a download manager. I don’t want to be in the position where we have to force people to use it though (as some people hate them) so we simply download the installers and run them in the background for the users.
This necessarily means they can’t be complicated or popup loads of UI and questions etc. You can only specify minimal information common to all installers (like enable/disable plugin types) effectively on the command line.
I think the general idea of cpack is that it picks up common information from your cmake target and gets you to an installer quickly.
Then you can customise it with properties rather than having to know the ins and outs of all the installer formats. But as far as I can tell, most of the supported formats allow you to override this e.g. by supplying a packagebuild xml file.
I’d be interested to hear what milage people have got from it as well.
On Mac, it only supported a subset of the actual features that exist in packagebuild and we just couldn’t make the installer do exactly what we needed.
At least in my installer scripts, a bunch of commands needed to run before the installer, like signing, stripping, etc, and doing it from CMake in a generic way that’s shared with other plugins meant a very unpleasant working experience because CMake isn’t a good scripting language.
I still like to have shared code for my installers but I just use a simple C++ script (that could also be in Python or any other language) that takes some plugin-specific data and generates Mac and Windows installers.
P.S. CMake can write variables (like the version, etc) from the build into a file using configure_file or file(WRITE) to use by following scripts, which I find useful.
This is true but cmake can call out to scripting tools when needed (or indeed run any process).
I actually do this the other way (which @sudara actually taught me) which is to have a VERSION file with the version number in next to the CMakeLists.txt file and then read this during cmake generation stage.
But yes, I also read this in to a GitHub env var to use in later stages.