How to distribute a SwiftPM executable on macOS

Hey there,

I've been trying to build and distribute a simple internal CLI tool. I am building with SwiftPM

swift build -c release --product mytoolcli --arch arm64 --arch x86_64

And then code signing,

xcrun codesign -s ${CODESIGN_IDENTITY} \
               --options=runtime \
               --timestamp \
               ${BINARY}

Then package the CLI in a DMG together with a simple Bash Script INSTALL that copies the file to /usr/local/bin.

hdiutil create -volname "mytoolcli" -srcfolder "${PKG_DIR}" -ov -format UDZO "${PKG_DMG}"
xcrun codesign -s ${CODESIGN_IDENTITY} "${PKG_DMG}"

The I upload for notarization:

xcrun altool --notarize-app \
               --primary-bundle-id ${BUNDLE_ID} \
               --username "${USERNAME}" \
               --password "@keychain:${PASSWORD_ID}" \
               --asc-provider ${ASC_PROVIDER} \
               --file "${PKG_DMG}"

And once the notarization is successful, I staple it on the DMG

xcrun stapler staple "${SIGNED_PKG}"

But, when I send the DMG to a coworker and she tries to open the INSTALL script in the DMG (or the cli executable), she gets the infamous "macOS cannot verify the developer".

Am I missing something obvious?

$ spctl -a -t install -vvv /path/to/mytoolcli.dmg
/path/to/mytoolcli.dmg: accepted
source=Notarized Developer ID
origin=Developer ID Application: Name (IDXXXX)

When I try running

$ spctl -a -t exec -vvv /Volumes/mytoolcli/mytoolcli

on the executable in the mounted disk image, I get

/Volumes/mytoolcli/mytoolcli: rejected (the code is valid but does not seem to be an app)
origin=Developer ID Application: Name (IDXXXX)

So, I guess my question is: how can I package a swift executable built with swift for distribution on macOS?

Thanks!

Tobias

2 Likes

But, when I send the DMG to a coworker and she tries to open the
INSTALL script in the DMG (or the cli executable), she gets the
infamous "macOS cannot verify the developer".

Open how? By running the executable from Terminal? Or by double clicking the Finder? If it’s the latter, try the former (the latter never works due to a bad interaction between Terminal and Gatekeeper (r. 58097824)).

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Thanks, I think this was indeed the problem. I always tried to just double-click the file. Now I've packaged the binary in a .pkg inside a .dmg and this seems to work fine. :crossed_fingers:

Would you be so kind as to show the process with a pkg instead of a dmg?

I have been wanting to do this with one of my projects gitlab-fusion.

Sure, essentially my workflow is now as follows

  1. Build the binary and code sign it with "Developer ID Application" identity.
  2. Create a .pkg for installation in /usr/local/bin and sign with with "Developer ID Installer".
  3. Submit .pkg for notarization.
  4. Staple ticket onto .pkg
  5. Package .pkg inside a .dmg.

or:

make notarize

and later

make image

VERSION = 0.1.0
PRODUCT = my-tool

BINARY = .build/apple/Products/Release/${PRODUCT}
PKG_ROOT = ./pkg/${PRODUCT}-${VERSION}
PKG_DIR =  ${PKG_ROOT}/usr/local/bin
PKG_DMG = ./pkg/${PRODUCT}-${VERSION}.dmg
PKG_DMG_ROOT = ./pkg/out
PKG = ${PKG_DMG_ROOT}/${PRODUCT}-${VERSION}.pkg

CODESIGN_IDENTITY = "Developer ID Application: John Appleseed (1234AAPL)"
PKG_CODESIGN_IDENTITY = "Developer ID Installer: John Appleseed (1234AAPL)"
BUNDLE_ID = dev.appleseed.${PRODUCT}
USERNAME = john.appleseed@me.com
PASSWORD_ID = AC_PASSWORD
ASC_PROVIDER = 47MVPF48NE
	
	
${BINARY}:
	swift build -c release --product ${PRODUCT}  --arch arm64 --arch x86_64
	xcrun codesign -s ${CODESIGN_IDENTITY} \
               --options=runtime \
               --timestamp \
               ${BINARY}
               
${PKG}: ${BINARY}
	rm -rf "${PKG_ROOT}" || true
	rm -rf "${PKG_DMG_ROOT}" || true
	mkdir -p ${PKG_DIR}
	mkdir -p ${PKG_DMG_ROOT}
	cp ${BINARY} ${PKG_DIR}
	xcrun pkgbuild --root ${PKG_ROOT} \
           --identifier "${BUNDLE_ID}" \
           --version "${VERSION}" \
           --install-location "/" \
           --sign ${PKG_CODESIGN_IDENTITY} \
           ${PKG}
           
${PKG_DMG}: ${PKG} staple
	hdiutil create -volname "${PRODUCT}" -srcfolder "${PKG_DMG_ROOT}" -ov -format UDZO "${PKG_DMG}"

.PHONY: build
build: ${BINARY}

.PHONY: package
package: ${PKG}

.PHONY: notarize
notarize: ${PKG}
	xcrun altool --notarize-app \
               --primary-bundle-id ${BUNDLE_ID} \
               --username "${USERNAME}" \
               --password "@keychain:${PASSWORD_ID}" \
               --asc-provider ${ASC_PROVIDER} \
               --file "${PKG}"
               
.PHONY: staple
staple:
	xcrun stapler staple "${PKG}"

.PHONY: image
image: ${PKG_DMG}
4 Likes

Really appreciate that. Thank you @t089.

Great summary @t089

Running something similar today, I got the following warning:

*** Warning: altool has been deprecated for notarization and starting in late 2023 will no longer be supported by the Apple notary service. You should start using notarytool to notarize your software. (-1030)

Here is an alternative notarize task that should be future-proof:

notarize: ${PKG}
	xcrun notarytool submit \
		--apple-id "${USERNAME}" \
		--password "${PASSWORD_ID}" \
		--team-id "${TEAM_ID}" \
		--wait \
		"${PKG}"

ASC_PROVIDER is replaced by the more common TEAM_ID (that is 1234AAPL in the above example).

There is also a cool option to run in CI:

--webhook <webhook>     Designate a public webhook URL to receive HTTP notifications on.

Yay.

Thanks for sharing! :tada: