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.)


  • Lifetime Qt Champion

    Hi,

    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 macdeployqt to 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:

    1. 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”.

    2. Go to command line and cd to inside your build directory that QtCreator made.

    3. 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 macdeployqt to determine the path.

    1. 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.

    2. Open the TextEdit application, ensure you are in RichText mode, and create a file welcome.rtf, readme.rtf, and license.rtf. Now, there’s a couple exceptions here. If you add an image into your welcome.rtf file, TextEdit switches it to welcome.rtfd when you go to save it -- that’s actually perfectly fine. You also don’t need a readme.rtf (or readme.rtfd), but it’s an option. Most people will likely want a license.rtf, however -- it’d be silly not to include one.

    3. Move these RTF/RTFD files into a Resources folder. 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.

    4. In the Resources folder, also create two very important files with no file extension: preinstall and 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 Applications folder. These scripts also are automatically passed 4 variables in this order:

    $0 = script path
    $1 = package path
    $2 = target path
    $3 = target volume

    1. Use the chmod command to give these script files the proper launch permission:
    chmod a+x Resources/preinstall
    chmod a+x Resources/postinstall
    
    1. Use the chown command if you want to make it such that these scripts can run under root permission like so:

    chown -R root:wheel Resources

    1. 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.

    1. Now we need to analyze it to create what's called a Distribution.xml file out of it:

    productbuild --synthesize --package MyApp.pkg Distribution.xml

    1. Now we need to edit this Distribution.xml file 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:

    auth="Root" installKBytes="xxx"

    ...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>

    1. This optional step here is to edit your preinstall and postinstall scripts 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 touch statement so that we can test things for now to prove that this works by examining our /tmp folder after installation. Note that the Apple Installer will automatically show a progress bar that will sit there until it successfully completes the preinstall and postinstall scripts -- you won't have to code that.

    2. Now that we have a working Distribution.xml file, and a working Resources folder, 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 Distribution.xml, Resources, and MyApp.pkg file inside 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.pkg file 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 /tmp folder 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 preinstall and postinstall scripts 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:

    https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/DistributingApplicationsOutside/DistributingApplicationsOutside.html#//apple_ref/doc/uid/TP40012582-CH12-SW2

    (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 MyApp.1.0.1.pkg.


  • Lifetime Qt Champion

    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.

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.