Using Xcode to bundle a PyQt, pyqtdeploy’ed app that is sandboxed and code signed

You have successfully written an app using:

  • Python
  • PyQt and Qt
  • pyqtdeploy

On OSX, the result of pyqtdeploy is an app bundle, say ‘myApp.app’.  It runs, but it depends on installed Qt and is neither codesigned nor sandboxed.  You want to change that (because the Mac Store requires it.)  You want to use Xcode.

Assumptions

You have gotten your app (the bundle produced by pyqtdeploy) to run on OSX, which means you have installed:

  • Qt (the binary distribution)
  • Xcode
  • Python (and statically built a libpython.a that includes the C language modules your app needs.)
  • PyQt and SIP, statically built
  • pyqtdeploy

You have also registered with the Mac Developer Program so that you have a Developer ID.

Tasks

  • fix your code to load plugins from the app bundle, and redo pyqtdeploy
  • convert your qmakeproject ( .pro ) to an Xcode project (.xcodeproj)
  • add a build phase to your project to call macdeployqt
  • turn on sandboxing in your project
  • turn on code signing in your project
  • make your app bundle

Fix your code to load Qt plugins from an OSX bundle

Sadly, for this situation (OSX, sandboxed) you need to patch your Python app so Qt finds the platform plugin libqcocoa.dylib in the app bundle.

Download and install the qtLibPathFacade module for Python.  Follow the example given there to add one line to your main(), before you instantiate QApplication.

Then you will need to redo pyqtdeploy, adding the site-package qtLibPathFacade.

Convert your qmakeproject ( .pro ) to an Xcode project (.xcodeproj)

qmake is the tool that does this.  Do this in the build directory that pyqtdeploy created

cd ~/<myAppBuild>/build
/Users/<me>/Qt5.3.0/5.3/clang_64/bin/qmake -spec macx-xcode myApp.pro

Now your directories look like this:

~/myAppBuild
     myApp.pdy (pyqtdeploy project)
     build
         myApp.pro
         myApp.xcodeproj
         ... source and make files generated by pyqtdeploy ...
         myApp.app   (a non-sandboxed, rudimentary bundle generated by pyqtdeploy

Note that pyqtdeploy might destroy the entire build directory and your .xcodeproj if you run pyqtdeploy again.  There probably is a better arrangement.

Add a build phase to your project to call macdeployqt

  • Start Xcode and open the project.
  • In the left ‘Navigator’ pane, click on the folder icon (‘Project Navigator’).  A list of project files will appear.   Select your project
  • Under Targets  select ‘myApp’ in the middle pane.
  • Choose the tab Build Phases in the middle pane.
  • Click on the ‘+‘ button near the top of the middle pane.  A context menu will open to choose the type of build phase.
  • Choose New Run Script Build Phase.  A new item will appear at the end of the list of build phases.
  • Expand that build phase by clicking on the triangle to the left of it
  • In the text box for the script, paste
    /Users/<me>/Qt5.3.0/5.3/clang_64/bin/macdeployqt ${BUILT_PRODUCTS_DIR}/myApp.app

This means that near the end of building, Xcode will invoke macdeployqt on the bundle it is building.  Macdeployqt will add needed Qt libraries and plugins to the bundle (and fix them so they will link to each other properly.)  Thus the bundle will be self-contained, not depending on installed, shared Qt libraries.  (If your app is not simple, you may need more plugins than macdeployqt bundles.)

Turn on sandboxing in your project

  • In the middle pane, choose the Capabilities tab
  • toggle Sand Box to On

Later you will need to expand the Sand Box item and choose the capabilities your app actually needs (for example network access.)

Enable code signing in your project

  • In the middle pane, choose the Build Settings tab.
  • expand the Code Signing item
  • click in the textbox labeled  Code Signing Resource Rules Path
  • enter ‘ResourceRules.plist’

I am not sure why this works.  Xcode finds that file somewhere, and the file tells Xcode to sign certain binaries in the bundle, e.g. the Qt frameworks and plugins.  You can see the Resource Rules later, in  ‘myApp.app/Contents/_CodeSignature/CodeResources’,  a text file.

(You might also need to choose General>Identity>Signing=Developer ID and General>Identity>Team)

Make an app bundle

  • Choose Products>Archive in the Xcode menus.  After building succeeds, an Organizer – Archive window opens.
  • Choose the Distribute button.  A dialog opens to choose Method of Distribution
  • Choose Export DeveloperID-signed Application radio button and the Next button.  A list of Developer ID’s opens
  • Choose your developer ID and the Export button
  • Eventually, a file chooser dialog will open.  Choose a place for your app bundle, say the desktop

Now you should have a code-signed, self-contained, sandboxed app bundle which will run on a clean user machine.

I believe the above steps are complete, for trials with simple apps.  Later you will want to add more descriptive information (e.g. your real bundle ID) so you can distribute your app properly through channels such as the Mac Store.  You will also want to validate and verify.

In my case the app bundle was about 35 meg.  It is smaller when you proceed to compress it, put it in a disk image, package it, or distribute in the Mac Store.

 

3 thoughts on “Using Xcode to bundle a PyQt, pyqtdeploy’ed app that is sandboxed and code signed

    • I am not sure what you mean. Do you mean it could be done more simply if I stay in QtCreator or use the command line instead of the Xcode GUI?

      If you mean ‘the process is not very intuitive’ I agree. But I think it doesn’t need to be intuitive, since only a few people will do it. It needs to be documented and not fragile to slight changes.

  1. Pingback: Deploying PyQt Python Qt apps cross platform using pyqtdeploy | plashless

Leave a comment