Understanding Qt and Mac PKG Setups
Can you point me to some tutorials for the following thing?
I've made my first Qt5.5 app for the Mac. However, because it's including the embedded webkit, it's going to be a little large on the install. I need to learn how to make a PKG setup that is a small initial download. Then, when one runs the PKG setup, it downloads the "payload" part of the setup (the bulky part such as the webkit part).
I can't use a DMG because I need to use a post-install Bash script to set some things up in root mode, which I understand a PKG setup can allow you to do, but a DMG cannot. (I am under NDA at the moment and cannot explain what my application is -- but it needs powerful permissions. All I can say is that it's in the software utility market.)
You have more information about it in Xcode's documentation here
Hope it helps
I figured out how to make this work. There's no easy doc on this -- there are out of date docs all over the place, with bits of info in one not in the other. I piecemealed it all together and did enough testing to prove that I have a system that works.
The following instructions won't likely work in the Apple Store. So, this is for distributing your application outside of that. For that, you'll probably be more straight-forward and create a payload-oriented pkg file, or a dmg file, rather than a payload-less pkg file.
Basically, you use
macdeployqtto create your .dmg of your .app, which also fills in all the necessary components your app might need. Then, you run the .dmg and drag the .app out to a folder. Zip that folder and copy it up to your web server. Then, you create what's called a "no payload" setup as a .pkg file, which has a preinstall and postinstall script that can either be Bash or Perl scripts, but because Macs also come with PHP, they could also be PHP scripts. (I’m not certain, but if Python or Ruby is shipped by default too, then I guess you could use Python or Ruby too.) You also have the option of specifying whether it requires root:wheel privilege to install this, and can specify the application title (a friendly title), as well as a welcome message, optional readme, and optional license file. So, when people download this .pkg file, it's very tiny. (You can fatten it with extra bogus stuff if you want to make it seem like it's a quality app and not a mistake.) When they run the .pkg file, you can use the preinstall and postinstall scripts to download a payload from your web server, unzip it, and put it in the Applications folder, as well as perhaps install stuff in other parts of the volume as well, and do other powerful things.
Here are the steps:
In QtCreator, build your application and run it once so that you know it’s working. Let’s assume your app is called “MyApp.app”.
Go to command line and
cdto inside your build directory that QtCreator made.
Run this command:
~/Qt/5.5/clang_64/bin/macdeployqt “MyApp.app" -dmg -always-overwrite
Note that your Qt path may be different than that. You an use
which macdeployqtto determine the path.
This creates a “MyApp.dmg”. Launch it and drag out the MyApp.app to a folder. Now zip this file as MyApp.app.zip and copy it up to your web server.
Open the TextEdit application, ensure you are in RichText mode, and create a file
license.rtf. Now, there’s a couple exceptions here. If you add an image into your
welcome.rtffile, TextEdit switches it to
welcome.rtfdwhen you go to save it -- that’s actually perfectly fine. You also don’t need a
readme.rtfd), but it’s an option. Most people will likely want a
license.rtf, however -- it’d be silly not to include one.
Move these RTF/RTFD files into a
Resourcesfolder. You can also stick a large binary file (like a large image or .dat file) in there just to fatten this very thin install so that it looks substantial. For instance, if you’re making a graphics program, people are more likely to install something that is 5MB than something that is only 1MB.
Resourcesfolder, also create two very important files with no file extension:
postinstall. For now, make it easy on yourself while testing this and simply make each one do something like:
#!/bin/bash touch /tmp/preinstall-script-was-run.txt # or touch /tmp/postinstall-script-was-run.txt
...however, these files will eventually be the powerful scripts to download your application from a web server, unzip it, and install it in the
Applicationsfolder. These scripts also are automatically passed 4 variables in this order:
$0 = script path
$1 = package path
$2 = target path
$3 = target volume
- Use the
chmodcommand to give these script files the proper launch permission:
chmod a+x Resources/preinstall chmod a+x Resources/postinstall
- Use the
chowncommand if you want to make it such that these scripts can run under root permission like so:
chown -R root:wheel Resources
- Now create a payload-less pkg file like so:
pkgbuild --identifier com.MyCompany.MyApp --nopayload --scripts ./Resources MyApp.pkg
...change MyCompany and MyApp accordingly, of course.
- Now we need to analyze it to create what's called a
Distribution.xmlfile out of it:
productbuild --synthesize --package MyApp.pkg Distribution.xml
- Now we need to edit this
Distribution.xmlfile because it's missing some things we need.
Under the first OPTIONS tag, add these lines, changing the file names exactly as you named them, and the application title as it is. Do not specify folder paths -- that won't matter:
<title>My App</title> <welcome file="welcome.rtfd"/> <readme file="readme.rtf"/> <license file="license.rtf"/>
If you don't include a readme, then remove that line.
Now, skip to the bottom of that file and look for the element PKG-REF with an attribute VERSION. Change the version number to the version of your application. Then, add these other two attributes to that element:
...where xxx is the actual number of kilobytes of your zipped payload when fully uncompressed off the web server.
Thus it might look like so:
<pkg-ref id="com.MyCompany.MyApp" version="1.0" auth="Root" installKBytes="68393" onConclusion="none">MyApp.pkg</pkg-ref>
This optional step here is to edit your
postinstallscripts to do magical things like download a file off the server, unzip it, stick it in
Applications, and other stuff. However, for now, you can skip that because we're just testing. Remember in step # 7 we just implemented a
touchstatement so that we can test things for now to prove that this works by examining our
/tmpfolder after installation. Note that the Apple Installer will automatically show a progress bar that will sit there until it successfully completes the
postinstallscripts -- you won't have to code that.
Now that we have a working
Distribution.xmlfile, and a working
Resourcesfolder, we can make our final package:
productbuild --distribution Distribution.xml --resources ./Resources --package-path ./MyApp.pkg MyApp.1.01.pkg
What this does is stuff your
MyApp.1.01.pkg(where 1.01 might be your version number in this example). It also tells the package file your RTF/RTFD files.
And there you have it! The
MyApp.1.01.pkgfile is already compressed, so you don't need to zip it. Just copy it up to your web server and run it. When you open your
/tmpfolder later, you'll see that you have those two touch files that we created as a test.
Now that you know that this works, you can edit your
postinstallscripts to do the magical things you need with your server payload, and then follow it by doing this:
rm MyApp.pkg rm MyApp.1.0.1.pkg chmod a+x Resources/preinstall chmod a+x Resources/postinstall chown -R root:wheel Resources pkgbuild --identifier com.MyCompany.MyApp --nopayload --scripts ./Resources MyApp.pkg productbuild --distribution Distribution.xml --resources ./Resources --package-path ./MyApp.pkg MyApp.1.01.pkg
The remaining step out of this is the code-signing step on the MyApp.1.01.pkg file like so:
- Go through the hoops on the Apple Developer site to get your developer certificate:
(Note that you'll need to change those instructions if you want to make this work in the Apple Store.)
- Then run this:
productsign --sign "Developer ID Installer: My Company" MyApp.1.0.1.pkg MyApp.1.0.1.signed.pkg
You can then copy the signed pkg to another directory and rename it back to
Thanks for the detailed post !
I'd recommend that you make it a wiki article, that will make it easier to find.
Note that using that kind of installer rules out the Apple Store.
Some added notes from the trenches:
- Above, I describe a small stub setup that downloads a larger payload. However, if your payload is large from the server, you're going to be looking at the screen stuck at the "Running package scripts..." notice and a jammed progress bar while it continues to download over Curl your larger payload. One solution to that in your preinstall Bash script is to do this:
osascript -e 'tell application "System Events" to set visible of process "Installer" to false'
That super neat trick hides the installer so that you can show something else. That way, the customer doesn't think your installer just jammed.
Then, build yourself a simplistic ObjectiveC application that looks just like that Installer but shows a more active progress bar (use a timer) and then displays a few messages like "Downloading..." and "Finishing download..." and stuff like that. Of course, you can do it in Qt, but even the most minimum Mac-based Qt widget app (not statically compiled) is 8.9MB zipped, whereas in ObjectiveC you can make an app that does the same thing in a mere 32K (unzipped). (Oh, and for you QML die-hards out there, a widget-based app has you topped on file size. I was seeing 12.1MB zipped for something comparable in QML.)
Once that Curl has finished, it can then kill the ObjectiveC process and reverse the osascript to get the installer to show you the Finish page.
- If deploying a large commercial application, and especially if you need to hook up special high-permission items and require script control, then you may not want to use either the .pkg or .dmg formats at all. This is because, psychologically, customers are not likely to want to download a huge honking 200MB+ DMG or PKG file. Instead, they would be more likely to download a smaller file, run it, and then when it says Installing, it does the rest of the download steps. (I know it's the same wait time, but psychology is important in product marketing.) Take a look at Norton's Antivirus for the Mac. (There's a free trial on their website.) They basically made an ObjectiveC / Cocoa application and then zipped it. However, it's a stub setup that's very small. When you run it, it then shows the typical EULA and then starts downloading components from the web, and putting things into the appropriate places.