Published: Fri 17 February 2017
By Sami J. Lehtinen
In blog .
tags: macos packaging signing
Arbitrary restrictions with paths
% codesign -s KEYID --deep -v ImageDir/MyApp.app
ImageDir/MyApp.app: bundle format unrecognized, invalid, or unsuitable
In subcomponent: /Users/sjl/obj/ImageDir/MyApp.app/Contents/MacOS/path/to/a/libary/7.7.0.1
(paths changed to protect the innocent)
If I rename "7.7.0.1" to "foo", it works. If I move the dynamic
libraries one directory up, it works.
The reason why a path component with a version number is problematic is
because of how the code signing machinery works. I stumbled on this
piece of documentation from Apple, macOS Code Signing In Depth :
Note that a location where code is expected to reside cannot generally
contain directories full of nested code, because those directories tend
to be interpreted as bundles. So this occasional practice is not
recommended and not officially supported. If you do do this, do not use
periods in the directory names. The code signing machinery interprets
directories with periods in their names as code bundles and will reject
them if they don't conform to the expected code bundle layout.
Main binary cannot be a symlink
The main binary, which is the one pointed to by CFBundleExecutable in
Info.plist could not be a symlink.
% codesign -s Y2AC5CKR85 --deep -v ImageDir/MyApp.app
ImageDir/MyApp.app: the main executable or Info.plist must be a regular file ( no symlinks, etc.)
I had previously had problems with using a relative path, but this may
be because of the caching of the applications performed by macOS.
Changing the main binary from a symbolic link to a direct path in the
Info.plist did the trick, codesign worked and the application
started up. This created problems loading the Qt libraries, however...
Do not copy Qt frameworks by hand
I had just used
% cp -a QtGui.framework MyApp.app/Contents/Frameworks/
but codesign complained about that. Instead, I got good results by
building the bundle otherwise, and making it standalone by using the
macdeployqt utility.
This checks the package, or at least the main binary in the
Info.plist , for what Qt frameworks are needed, and deploys the
necessary libraries and plugins. After that, there was less clutter in
the framework directories of my app and signing worked correctly.
Qt binaries seem to need to be at the top-level, i.e.,
MyApp.app/Contents, to load the Qt plugins correctly, the problem
mentioned on the previous section. To solve this, I moved the Qt main
application to the top-level Contents/MacOS . This required a bit of
tweaking to the dll loading of the app, as the directory layout changed,
but no biggie.
All's well that ends well
After going over the hurdles described above, I've got nice signed
packages.
% codesign -s SIGNING --deep -v ImageDir/MyApp.app
ImageDir/MyApp.app: signed app bundle with Mach-O thin ( x86_64) [ com.semeai.app]
% codesign -s SIGNING -v MyApp.dmg
MyApp.dmg: signed []
And verification:
% codesign -v --deep -v MyApp.dmg
MyApp.dmg: valid on disk
MyApp.dmg: satisfies its Designated Requirement
% codesign -v --deep -v ImageDir/MyApp.app/
ImageDir/MyApp.app/: valid on disk
ImageDir/MyApp.app/: satisfies its Designated Requirement
Easy as pie :)
Only thing remaining really is verifying that these packages work on
10.10. On 10.9 I get “the sealed resource directory is invalid.", which
indicates a difference in the bundle layout between the versions.