Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate build of iOS signed ipa file for App Store submission #2625

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions .github/autobuild/ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,76 @@ setup() {
fi
}


prepare_signing() {
[[ "${SIGN_IF_POSSIBLE:-0}" == "1" ]] || return 1

# Signing was requested, now check all prerequisites:
[[ -n "${IOSDIST_CERTIFICATE:-}" ]] || return 1
[[ -n "${IOSDIST_CERTIFICATE_ID:-}" ]] || return 1
[[ -n "${IOSDIST_CERTIFICATE_PWD:-}" ]] || return 1
[[ -n "${NOTARIZATION_PASSWORD:-}" ]] || return 1
[[ -n "${IOS_PROV_PROFILE_B64:-}" ]] || return 1
[[ -n "${KEYCHAIN_PASSWORD:-}" ]] || return 1

echo "Signing was requested and all dependencies are satisfied"

# use this as filename for Provisioning Profile
IOS_PP_PATH="embedded.mobileprovision"

## Put the cert to a file
# IOSDIST_CERTIFICATE - iOS Distribution
echo "${IOSDIST_CERTIFICATE}" | base64 --decode > iosdist_certificate.p12

## Echo Provisioning Profile to file
echo -n "${IOS_PROV_PROFILE_B64}" | base64 --decode > $IOS_PP_PATH
Comment on lines +46 to +64
Copy link
Member

@ann0see ann0see Jul 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in the macOS case, probably worth putting distribution related stuff in an if.


# Set up a keychain for the build:
security create-keychain -p "${KEYCHAIN_PASSWORD}" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" build.keychain
security import iosdist_certificate.p12 -k build.keychain -P "${IOSDIST_CERTIFICATE_PWD}" -A -T /usr/bin/codesign
# security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${KEYCHAIN_PASSWORD}" build.keychain
security set-key-partition-list -S apple-tool:,apple: -s -k "${KEYCHAIN_PASSWORD}" build.keychain
# set lock timeout on keychain to 6 hours
security set-keychain-settings -lut 21600
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in the macOS case - maybe this is no longer needed.


# apply provisioning profile
#FIXME - maybe redundant?
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $IOS_PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles

# for debug
echo "Checking found identities..."
security find-identity -v

# Tell Github Workflow that we need notarization & stapling:
echo "::set-output name=macos_signed::true"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be renamed to apple_signed if we merge this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe ios_signed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ann0see it is actually possible to upload the binary to the App Store, using "altool" as outlined here: https://help.apple.com/app-store-connect/#/devb1c185036

However, as the App Store submission process involves a number of manual setup steps anyway (ie creating the actual submission on the App Store Connect website, adding screenshots, descriptions, compliance details and all sorts) and you likely only want to upload specific versions to the App Store (rather than having it run automatically), I thought the generation of the signed, verifiable IPA is a big enough win for this automation.

Quick note: the only manual step remaining once you have the signed IPA is to drag and drop the IPA file into Transporter (on macOS), and click the Upload button.

If I have time today though I will experiment with adding the upload directly using xcrun altool.

return 0
}

build_app_as_ipa() {
# Add the Qt binaries to the PATH:
export PATH="${QT_DIR}/${QT_VERSION}/ios/bin:${PATH}"
./ios/deploy_ios.sh

# Mac's bash version considers BUILD_ARGS unset without at least one entry:
BUILD_ARGS=("")
if prepare_signing; then
BUILD_ARGS=("-s" "${IOSDIST_CERTIFICATE_ID}" "-k" "${KEYCHAIN_PASSWORD}")
fi
./ios/deploy_ios.sh "${BUILD_ARGS[@]}"
}

pass_artifact_to_job() {
local artifact="jamulus_${JAMULUS_BUILD_VERSION}_iOSUnsigned${ARTIFACT_SUFFIX:-}.ipa"
local artifact="jamulus_${JAMULUS_BUILD_VERSION}_iOS_unsigned${ARTIFACT_SUFFIX:-}.ipa"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much a signed IPA on the web without the app store helps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's any point to that, no - the signed IPA is purely for App Store submission. So it's really for @emlynmac or whoever is managing the Jamulus App Store account to have a ready-built file for submission.

I assume the unsigned IPA is useful for general testing purposes so obviously I've kept that in.

echo "Moving build artifact to deploy/${artifact}"
mv ./deploy/Jamulus.ipa "./deploy/${artifact}"
mv ./deploy/Jamulus_unsigned.ipa "./deploy/${artifact}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not worth keeping this around, only signed version would be useful past making sure the build works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I left a note above, I wasn't sure if people were maybe using the unsigned IPA for local testing or somesuch.
No problem to remove it if you prefer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be removed IMO.

echo "::set-output name=artifact_1::${artifact}"

local artifact2="jamulus_${JAMULUS_BUILD_VERSION}_iOS_signed${ARTIFACT_SUFFIX:-}.ipa"
echo "Moving build artifact to deploy/${artifact2}"
mv ./deploy/Jamulus_signed.ipa "./deploy/${artifact2}"
echo "::set-output name=artifact_2::${artifact2}"
}

case "${1:-}" in
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/autobuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ jobs:

- config_name: iOS (artifacts)
target_os: ios
building_on_os: macos-10.15
base_command: QT_VERSION=5.15.2 ./.github/autobuild/ios.sh
building_on_os: macos-11
base_command: QT_VERSION=5.15.2 SIGN_IF_POSSIBLE=1 ./.github/autobuild/ios.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the presence of a certificate in the environment to determine whether to sign, so not needing another parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather follow the lead as being done for macOS, which uses that flag. Or we can remove from both macOS and iOS but to have in just one feels weird.

# Build failed with CodeQL enabled when last tested 03/2022 (#2490).
# There are no hints that iOS is supposed to be supported by CodeQL.
# Therefore, disable it:
run_codeql: false
xcode_version: 12.1.1
xcode_version: 13.2.1

- config_name: Windows (artifact+codeQL)
target_os: windows
Expand Down Expand Up @@ -232,6 +232,10 @@ jobs:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERT}}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERT_PWD }}
MACOS_CERTIFICATE_ID: ${{ secrets.MACOS_CERT_ID }}
IOSDIST_CERTIFICATE: ${{ secrets.IOSDIST_CERT}}
IOSDIST_CERTIFICATE_PWD: ${{ secrets.IOSDIST_CERT_PWD }}
IOSDIST_CERTIFICATE_ID: ${{ secrets.IOSDIST_CERT_ID }}
IOS_PROV_PROFILE_B64: ${{ secrets.IOS_PROVISIONING_PROFILE }}
NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}

Expand Down
57 changes: 57 additions & 0 deletions ios/Info-make.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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>CFBundleExecutable</key>
<string>@EXECUTABLE@</string>
<key>CFBundleIdentifier</key>
<string>@BUNDLEIDENTIFIER@</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>@EXECUTABLE@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone to let others hear you.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchScreen</key>
<dict/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>

</dict>
</plist>
59 changes: 59 additions & 0 deletions ios/Info-xcode.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone to let others hear you.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchScreen</key>
<dict/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>

</dict>
</plist>
119 changes: 107 additions & 12 deletions ios/deploy_ios.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,113 @@
#!/bin/bash
set -eu

## Builds an ipa file for iOS. Should be run from the repo-root
root_path="$(pwd)"
project_path="${root_path}/Jamulus.pro"
iosdeploy_path="${root_path}/ios"
resources_path="${root_path}/src/res"
build_path="${root_path}/build"
deploy_path="${root_path}/deploy"
iosdist_cert_name=""
keychain_pass=""

# Create Xcode file and build
qmake -spec macx-xcode Jamulus.pro
/usr/bin/xcodebuild -project Jamulus.xcodeproj -scheme Jamulus -configuration Release clean archive -archivePath "build/Jamulus.xcarchive" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO CODE_SIGN_ENTITLEMENTS=""
while getopts 'hs:k:' flag; do
case "${flag}" in
s)
iosdist_cert_name=$OPTARG
if [[ -z "$iosdist_cert_name" ]]; then
echo "Please add the name of the certificate to use: -s \"<name>\""
fi
;;
k)
keychain_pass=$OPTARG
if [[ -z "$keychain_pass" ]]; then
echo "Please add keychain password to use: -k \"<name>\""
fi
;;
h)
echo "Usage: -s <cert name> for signing ios build"
exit 0
;;
*)
exit 1
;;
esac
done

# Generate ipa by copying the .app file from the xcarchive directory
mkdir build/Payload
cp -r build/Jamulus.xcarchive/Products/Applications/Jamulus.app build/Payload/
cd build
zip -0 -y -r Jamulus.ipa Payload/
cleanup()
{
# Clean up previous deployments
rm -rf "${build_path}"
rm -rf "${deploy_path}"
mkdir -p "${build_path}"
mkdir -p "${build_path}/Exports"
mkdir -p "${deploy_path}"
}

# Make a deploy folder and copy file
mkdir ../deploy
mv Jamulus.ipa ../deploy
build_ipa()
{
## Builds an ipa file for iOS. Should be run from the repo-root

# Create Xcode project file
qmake -spec macx-xcode Jamulus.pro

# disable deprecation warnings re legacy build system - XCode 13 errors on this
/usr/libexec/PlistBuddy -c "Add :DisableBuildSystemDeprecationDiagnostic bool" Jamulus.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
/usr/libexec/PlistBuddy -c "Set :DisableBuildSystemDeprecationDiagnostic true" Jamulus.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

# Build
if [[ -z "$iosdist_cert_name" ]]; then
# Build unsigned
/usr/bin/xcodebuild -project Jamulus.xcodeproj -scheme Jamulus -configuration Release clean archive \
-archivePath "build/Jamulus.xcarchive" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGN_ENTITLEMENTS=""
else
# Build signed ipa file
# Ref: https://developer.apple.com/forums/thread/70326
# // Builds the app into an archive
/usr/bin/xcodebuild -project Jamulus.xcodeproj -scheme Jamulus -configuration Release clean archive \
-archivePath "build/Jamulus.xcarchive" \
DEVELOPMENT_TEAM="XXXXXXXXXX" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO

#FIXME this may be redundant - since provisioning profile is specified in exportOptionsRelease.plist
cp ~/Library/MobileDevice/Provisioning\ Profiles/embedded.mobileprovision build/Jamulus.xcarchive/Products/Applications/Jamulus.app/

# // Exports the archive according to the export options specified by the plist
# export signed installer to build/Exports/Jamulus.ipa
/usr/bin/xcodebuild -exportArchive \
-archivePath "build/Jamulus.xcarchive" \
-exportPath "build/Exports/" \
-exportOptionsPlist "ios/exportOptionsRelease.plist" \
DEVELOPMENT_TEAM="XXXXXXXXXX" \
CODE_SIGN_IDENTITY="${iosdist_cert_name}" \
CODE_SIGNING_REQUIRED=YES \
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_STYLE="Manual"
fi

# Generate unsigned ipa by copying the .app structure from the xcarchive directory
cd ${root_path}
mkdir -p build/unsigned/Payload
cp -r build/Jamulus.xcarchive/Products/Applications/Jamulus.app build/unsigned/Payload/
cd build/unsigned
zip -0 -y -r Jamulus.ipa Payload/

# copy files
cd ${root_path}
# unsigned IPA
mv build/unsigned/Jamulus.ipa deploy/Jamulus_unsigned.ipa
# signed IPA
mv build/Exports/Jamulus.ipa deploy/Jamulus_signed.ipa
}

# Cleanup previous deployments
cleanup

# Build ipa file for App Store submission (eg via Transporter etc)
build_ipa
15 changes: 15 additions & 0 deletions ios/exportOptionsRelease.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?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>method</key>
<string>app-store</string>
<key>teamID</key>
<string>XXXXXXXXXX</string>
<key>provisioningProfiles</key>
<dict>
<key>io.jamulus.Jamulus</key>
<string>UUID_GOES_HERE</string>
</dict>
</dict>
</plist>