AppStore submission for Ruby/Glimmer app on MacOS without Xcode

Background

I've repeatedly run into codesigning (and missing provisioning profile) issues for my Ruby/Glimmer app and am looking for ways to troubleshoot this outside of Xcode. The app structure is as follows:

PATHmanager.app
└── Contents
    ├── Info.plist
    ├── MacOS
    │   └── PATHmanager
    ├── PkgInfo
    ├── Resources
    │   └── AppIcon.icns
    ├── _CodeSignature
    │   └── CodeResources
    └── embedded.provisionprofile

Architecture

I have a Mac mini Apple M2 Pro with macOS Ventura 13.4. Xcode is not used directly, but the underlying command line tools (e.g., codesign, productbuild, pkgutil, xcrun) are run from a custom Ruby script.

xcodebuild -version
Xcode 14.3.1
Build version 14E300c

Questions

  1. Is the .app directory and file structure/naming sufficient? If not, can you point me in the direction of a minimal example that does not use Xcode?

  2. Info.plist is an XML text document (not binary), which I believe is in an acceptable format, but how do I lint this file and determine if it contains all of the necessary key/value pairs?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
	<string>PATH manager</string>
	<key>CFBundleExecutable</key>
	<string>PATHmanager</string>
	<key>CFBundleIconFile</key>
	<string>AppIcon.icns</string>
	<key>CFBundleIdentifier</key>
	<string>com.chipcastle.pathmanager</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>PATHmanager</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.15</string>
	<key>CFBundleSupportedPlatforms</key>
	<array>
		<string>MacOSX</string>
	</array>
	<key>CFBundleVersion</key>
	<string>1.15</string>
	<key>ITSAppUsesNonExemptEncryption</key>
	<false/>
	<key>LSApplicationCategoryType</key>
	<string>public.app-category.developer-tools</string>
	<key>LSMinimumSystemVersion</key>
	<string>12.0</string>
	<key>LSUIElement</key>
	<false/>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
	<key>NSHumanReadableCopyright</key>
	<string>© 2025 Chip Castle Dot Com, Inc.</string>
	<key>NSMainNibFile</key>
	<string>MainMenu</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
</dict>
</plist>
  1. PATHmanager is a Mach-O 64-bit executable arm64 file created by using Tebako. Does this executable need to be codesigned, or is codesigning the .app folder sufficient?

  2. Does the .app directory need an entitlements file? Here's how I codesign it:

codesign --deep --force --verify --verbose=4 --options runtime --timestamp --sign 'Apple Distribution: Chip Castle Dot Com, Inc. (BXN9N7MNU3)' '/Users/chip/Desktop/distribution/PATHmanager.app'
  1. Does the PATHmanager binary need an entitlements file? Here's how I codesign it:
codesign --deep --force --verify --verbose=4 --options runtime --timestamp --entitlements '/Users/chip/Desktop/PATHmanager.entitlements' --sign 'Apple Distribution: Chip Castle Dot Com, Inc. (BXN9N7MNU3)' '/Users/chip/Desktop/distribution/PATHmanager.app/Contents/MacOS/PATHmanager'

  1. How can I verify what entitlements, if any, are required for codesigning the binary? The PATHmanager.entitlements file is an XML text file containing only the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>
  1. Is the embedded.provisionprofile necessary, and if so, how do I know determine if it matches the certificate or entitlements that I'm using? Additionally, is it named and located properly?

  2. I submitted this to the AppStore several weeks ago and the reviewer reported that the executable would not load on their machine (even though it worked on mine.) Is it better for me to release via TestFlight for testing, and if so, do I need to following a separate process for codesigning (i.e., using different entitlements, profiles, certs, etc) when doing so?

I've been playing whack-a-mole with this for too long to mention and am hoping to nail down a better deployment flow, so any suggestions for improvement will be greatly appreciated. Thank you in advance.

Answered by DTS Engineer in 826040022
Is the .app directory and file structure/naming sufficient?

It looks reasonable enough. A good place to start with this stuff is Placing Content in a Bundle. If you need more info then create a test project in Xcode, build it, and see what it did.

how do I lint this file … ?

You can lint it with plutil. Indeed, I recommend you do that.

Actually, my general advice is that you use plutil to convert it to the XML format, which means it’s not just technically correct but in the canonical format.

and determine if it contains all of the necessary key/value pairs?

It’s hard to answer that, because it depends what you app does. However, a good place to start is with the above-mentioned Xcode project.

is codesigning the .app folder sufficient?

This is answered by Creating distribution-signed code for macOS. I recommend that you follow the advice there, and in Packaging Mac software for distribution.

Does the .app directory need an entitlements file?

The file itself? No. Entitlements are baked into the program when you sign it. So you might need an .entitlements file as an input to codesign, but you don’t need to include that specific file in your app.

As to whether you need entitlements at all, that very much depends. See below.

Here's how I codesign it:

Don’t use --deep. See --deep Considered Harmful.

How can I verify what entitlements, if any, are required for codesigning the binary?

There’s no single answer to that question. It depends on what your code does.

However, if you’re distributing on the App Store then, yes, you definitely need the entitlement that enables the App Sandbox.

Also, if you plan to use TestFlight, which you should, see TestFlight, Provisioning Profiles, and the Mac App Store.

Is the embedded.provisionprofile necessary

Only if your app uses restricted entitlements. The App Sandox entitlement is not restricted, but the TestFlight ones are.

how do I know determine if it matches the certificate or entitlements that I'm using?

TN3125 Inside Code Signing: Provisioning Profiles explains that in gory detail.

Additionally, is it named and located properly?

This is another thing covered by Placing Content in a Bundle.

Is it better for me to release via TestFlight for testing

Yes. And that does complicate things somewhat, as I mentioned above.


The easiest solution is to setup a demo Xcode app with the same name and bundleID and see how Xcode does it.

This is good advice IMO.

You can both look at the output and also look build transcript to work out how Xcode created that output.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Bug number: FB17118773

Thanks. I’m in the process of getting access to that but I wanted to respond to your other points immediately.

I'm unclear on why the dylib would be located under …/tmp/tebako-runtime-20250404-48697-v1sra5/libui.dylib

Well, I’m pretty sure that TestFlight hasn’t copied it to your temporary directory (-:

I suspect that you have two copies of this library:

  • One in Contents/Frameworks, where it needs to be for App Store distribution to work.

  • Another embedded with your app via your third-party tooling.

When you run the app it’s extracting the embedded copy of the library to your temporary directory and something ends up trying to load it from there.

If you run a development-signed version of your app, do you see this library get loaded? One option here is to run the app and then force it to crash by sending it a SIGABRT from Terminal:

% kill -ABRT YourAppProcessID

The crash report will include a Binary Images section that lists all the libraries it loaded.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for the tips on creating the crash report. Very helpful, and yes, libui.dylib does show up on lines 128 and 270-271 of attached report. However, I'm unclear on next steps. Thanks again.

To recap, my Ruby files for the app are located under ./app, with the gems being "vendored". This library is located at ./app/vendor/bundle/ruby/3.3.0/gems/libui-0.1.2-arm64-darwin/vendor/libui.dylib.

I'm curious if I should codesign the libui.dylib before building the binary with Tebako?

If so, would I still need to codesign the Contents/Frameworks files under the app bundle? (I'm assuming yes on this latter point.)

Thanks in advance for your suggestions.

It’s clear that your third-party tooling is still sequestering the library somewhere, then unpacking the library to a temporary directory, and then trying to load that library. This technique won’t work for App Store apps.

Actually, it’s worse than that, it can’t be made to work for App Store apps [1]. You can’t get around this by signing your code prior to the sequester because the App Store needs to resign it.

The only solution is:

  • In your third-party tooling, disable the mechanism that sequesters and expands this library.

  • Instead, have it load the library from Contents/Frameworks.

  • Build your app with your third-party tool.

  • Put the library in Contents/Frameworks.

  • Sign the library for distribution.

  • Then sign the rest of the app.

Regarding the first two points, I can only provide general advice on that front. This sequestering is done by your third-party tooling and you’ll need to ask them how to disable it.

My general advice relates to how your library is identified: I recommend you use an rpath-relative install name, as explained in Dynamic Library Standard Setup for Apps.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] You can make this work for directly distributed apps, although it requires some faffing around.

AppStore submission for Ruby/Glimmer app on MacOS without Xcode
 
 
Q