Skip to content

Commit efd0e5f

Browse files
committed
ios: produce signed build
1 parent dee268e commit efd0e5f

File tree

4 files changed

+174
-12
lines changed

4 files changed

+174
-12
lines changed

ci/Jenkinsfile.ios

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env groovy
2-
library 'status-jenkins-lib@v1.9.29'
2+
library 'status-jenkins-lib@sign-ios-release'
33

44
/* Options section can't access functions in objects. */
55
def isPRBuild = utils.isPRBuild()
66
def isNightlyBuild = utils.isNightlyBuild()
7+
def isReleaseBranch = (env.CHANGE_BRANCH ?: env.BRANCH_NAME)?.startsWith('release')
78

89
pipeline {
910

@@ -65,8 +66,9 @@ pipeline {
6566
IPHONE_SDK = "iphoneos"
6667
ARCH = "x86_64"
6768
/* iOS app paths */
68-
STATUS_IOS_APP_ARTIFACT = "pkg/${utils.pkgFilename(ext: 'app.zip', arch: getArch(), version: env.VERSION, type: env.APP_TYPE)}"
69+
STATUS_IOS_APP_ARTIFACT = "pkg/${utils.pkgFilename(ext: 'ipa', arch: getArch(), version: env.VERSION, type: env.APP_TYPE)}"
6970
STATUS_IOS_APP = "${WORKSPACE}/mobile/bin/ios/qt6/Status.app"
71+
STATUS_IOS_IPA = "${WORKSPACE}/mobile/bin/ios/qt6/Status.ipa"
7072
}
7173

7274
stages {
@@ -91,15 +93,28 @@ pipeline {
9193

9294
stage('Build iOS App') {
9395
steps {
94-
sh 'make mobile-build'
96+
script {
97+
app.buildSignedIOS(target='mobile-build', verbose='3')
98+
}
9599
}
96100
}
97101

98102
stage('Package iOS App') {
99103
steps {
100104
sh 'mkdir -p pkg'
101-
sh "cd mobile/bin/ios/qt6 && zip -r ${env.WORKSPACE}/${env.STATUS_IOS_APP_ARTIFACT} Status.app"
102-
sh "ls -la ${env.STATUS_IOS_APP_ARTIFACT}"
105+
sh "cp ${env.STATUS_IOS_IPA} ${env.STATUS_IOS_APP_ARTIFACT}"
106+
sh "ls -lh ${env.STATUS_IOS_APP_ARTIFACT}"
107+
}
108+
}
109+
110+
stage('Upload to TestFlight') {
111+
when {
112+
expression { return isReleaseBranch }
113+
}
114+
steps {
115+
script {
116+
app.uploadToTestFlight(ipaPath: env.STATUS_IOS_APP_ARTIFACT)
117+
}
103118
}
104119
}
105120

@@ -109,7 +124,7 @@ pipeline {
109124
steps {
110125
script {
111126
env.PKG_URL = s5cmd.upload(env.STATUS_IOS_APP_ARTIFACT)
112-
jenkins.setBuildDesc(APP: env.PKG_URL)
127+
jenkins.setBuildDesc(IPA: env.PKG_URL)
113128
}
114129
}
115130
}

mobile/ios/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,7 @@
6464
<string>Status uses Media Library to save and send Images. The Media Library module internally requires permissions to Apple Music</string>
6565
<key>NSFaceIDUsageDescription</key>
6666
<string>Log in securely to your account.</string>
67+
<key>ITSAppUsesNonExemptEncryption</key>
68+
<false/>
6769
</dict>
6870
</plist>

mobile/scripts/buildApp.sh

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ BIN_DIR=${BIN_DIR:-"$CWD/../bin/ios"}
1010
BUILD_DIR=${BUILD_DIR:-"$CWD/../build"}
1111
ANDROID_ABI=${ANDROID_ABI:-"arm64-v8a"}
1212
BUILD_TYPE=${BUILD_TYPE:-"apk"}
13+
SIGN_IOS=${SIGN_IOS:-"false"}
1314

1415
echo "Building wrapperApp for ${OS}, ${ANDROID_ABI}"
1516

@@ -109,17 +110,161 @@ if [[ "${OS}" == "android" ]]; then
109110
fi
110111
fi
111112
else
113+
# Generate timestamp-based build version (seconds * 1000 / 60000 = seconds / 60)
114+
# This gives us minutes since epoch, similar to Android's milliseconds / 60000
115+
BUILD_VERSION=$(($(date +%s) * 1000 / 60000))
116+
117+
# Include PR number in build version if available (for TestFlight visibility)
118+
if [[ -n "${CHANGE_ID:-}" ]]; then
119+
# Format: PR{number}.{timestamp} -> e.g., 18993.29353586
120+
VERSION_STRING="${CHANGE_ID}.${BUILD_VERSION}"
121+
else
122+
# For non-PR builds, just use timestamp
123+
VERSION_STRING="${BUILD_VERSION}"
124+
fi
125+
126+
echo "Using version: $VERSION_STRING"
127+
112128
QMAKE_BIN="${QMAKE:-qmake}"
113-
"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" -spec macx-ios-clang CONFIG+=release CONFIG+="$SDK" CONFIG+=device -after
129+
"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" -spec macx-ios-clang CONFIG+=release CONFIG+="$SDK" CONFIG+=device VERSION="$VERSION_STRING" -after
130+
114131
# Compile resources
115132
xcodebuild -configuration Release -target "Qt Preprocess" -sdk "$SDK" -arch "$ARCH" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcbeautify
116133
# Compile the app
117134
xcodebuild -configuration Release -target Status install -sdk "$SDK" -arch "$ARCH" DSTROOT="$BIN_DIR" INSTALL_PATH="/" TARGET_BUILD_DIR="$BIN_DIR" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcbeautify
118135

119-
if [[ -e "$BIN_DIR/Status.app/Info.plist" ]]; then
120-
echo "Build succeeded"
121-
else
136+
if [[ ! -e "$BIN_DIR/Status.app/Info.plist" ]]; then
122137
echo "Build failed"
123138
exit 1
124139
fi
140+
141+
if [[ "$SIGN_IOS" == "true" ]]; then
142+
echo "Signing iOS app..."
143+
144+
if [[ -z "$IOS_CERT_PATH" || -z "$IOS_CERT_PASSWORD" || -z "$IOS_PROVISIONING_PROFILE" ]]; then
145+
echo "Error: Missing iOS signing credentials"
146+
exit 1
147+
fi
148+
149+
# Import certificate to keychain
150+
KEYCHAIN_NAME="build.keychain"
151+
KEYCHAIN_PASSWORD=$(openssl rand -base64 16)
152+
153+
# Cleanup function to delete keychain
154+
cleanup_keychain() {
155+
echo "Cleaning up keychain..."
156+
security default-keychain -s login.keychain 2>/dev/null || true
157+
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
158+
}
159+
160+
# Set trap to cleanup keychain on script exit (success or failure)
161+
trap cleanup_keychain EXIT
162+
163+
# Delete any existing keychain from previous failed builds
164+
security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
165+
166+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
167+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
168+
security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"
169+
security list-keychains -s "$KEYCHAIN_NAME" login.keychain
170+
security default-keychain -s "$KEYCHAIN_NAME"
171+
172+
# Import Apple WWDR G3 intermediate certificate to establish trust chain
173+
echo "Importing Apple WWDR G3 certificate..."
174+
WWDR_TEMP_DIR=$(mktemp -d)
175+
curl -sS -o "$WWDR_TEMP_DIR/AppleWWDRCAG3.cer" https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
176+
security import "$WWDR_TEMP_DIR/AppleWWDRCAG3.cer" -k "$KEYCHAIN_NAME" -T /usr/bin/codesign
177+
rm -rf "$WWDR_TEMP_DIR"
178+
echo "Apple WWDR G3 certificate imported"
179+
180+
# Import user's certificate with private key
181+
security import "$IOS_CERT_PATH" -k "$KEYCHAIN_NAME" -P "$IOS_CERT_PASSWORD" -T /usr/bin/codesign
182+
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
183+
184+
# Install provisioning profile
185+
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
186+
mkdir -p "$PROFILE_DIR"
187+
188+
# Extract UUID from provisioning profile and copy with UUID as filename
189+
PROFILE_UUID=$(security cms -D -i "$IOS_PROVISIONING_PROFILE" 2>/dev/null | grep -A1 "<key>UUID</key>" | grep "<string>" | sed 's/.*<string>\(.*\)<\/string>.*/\1/')
190+
191+
# Remove existing profile if it exists (may have read-only permissions)
192+
rm -f "$PROFILE_DIR/$PROFILE_UUID.mobileprovision"
193+
194+
cp "$IOS_PROVISIONING_PROFILE" "$PROFILE_DIR/$PROFILE_UUID.mobileprovision"
195+
196+
echo "Installed provisioning profile: $PROFILE_UUID"
197+
198+
# Embed provisioning profile into the app bundle
199+
echo "Embedding provisioning profile into app..."
200+
cp "$IOS_PROVISIONING_PROFILE" "$BIN_DIR/Status.app/embedded.mobileprovision"
201+
202+
# Get signing identity (support both old "iPhone Distribution" and new "Apple Distribution")
203+
echo "Searching for signing identity in keychain..."
204+
security find-identity -v -p codesigning "$KEYCHAIN_NAME"
205+
206+
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_NAME" | grep -E "iPhone Distribution|Apple Distribution" | head -1 | awk '{print $2}')
207+
208+
if [[ -z "$SIGNING_IDENTITY" ]]; then
209+
echo "ERROR: No Distribution certificate found in keychain!"
210+
echo "Available identities:"
211+
security find-identity -v -p codesigning "$KEYCHAIN_NAME"
212+
exit 1
213+
fi
214+
215+
echo "Signing with identity: $SIGNING_IDENTITY"
216+
217+
# Extract entitlements from provisioning profile
218+
echo "Extracting entitlements from provisioning profile..."
219+
ENTITLEMENTS_PLIST=$(mktemp -t entitlements).plist
220+
221+
# Decode provisioning profile and extract entitlements in one step
222+
security cms -D -i "$IOS_PROVISIONING_PROFILE" | \
223+
plutil -extract Entitlements xml1 - -o "$ENTITLEMENTS_PLIST"
224+
225+
echo "Entitlements extracted to: $ENTITLEMENTS_PLIST"
226+
cat "$ENTITLEMENTS_PLIST"
227+
228+
# Sign all embedded frameworks first (required for App Store submission)
229+
echo "Signing embedded frameworks..."
230+
if [ -d "$BIN_DIR/Status.app/Frameworks" ]; then
231+
find "$BIN_DIR/Status.app/Frameworks" -name "*.framework" -type d | while read framework; do
232+
echo "Signing framework: $(basename "$framework")"
233+
codesign --force --sign "$SIGNING_IDENTITY" --timestamp "$framework"
234+
done
235+
fi
236+
237+
# Sign the main app bundle with entitlements from provisioning profile
238+
echo "Signing main app bundle..."
239+
codesign --force --sign "$SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS_PLIST" --timestamp "$BIN_DIR/Status.app"
240+
241+
# Clean up temporary entitlements file
242+
rm -f "$ENTITLEMENTS_PLIST"
243+
244+
# Verify signature
245+
echo "Verifying signature..."
246+
codesign --verify --verbose=4 "$BIN_DIR/Status.app"
247+
248+
# Display signing details for debugging
249+
echo "Signature details:"
250+
codesign -d --entitlements :- "$BIN_DIR/Status.app"
251+
252+
echo "iOS app signed successfully"
253+
254+
# Create IPA file
255+
echo "Creating IPA file..."
256+
IPA_DIR=$(mktemp -d)
257+
mkdir -p "$IPA_DIR/Payload"
258+
cp -R "$BIN_DIR/Status.app" "$IPA_DIR/Payload/"
259+
260+
# Create IPA (which is just a zip with .ipa extension)
261+
cd "$IPA_DIR"
262+
zip -r "$BIN_DIR/Status.ipa" Payload
263+
cd -
264+
265+
rm -rf "$IPA_DIR"
266+
echo "IPA created at $BIN_DIR/Status.ipa"
267+
fi
268+
269+
echo "Build succeeded"
125270
fi

mobile/wrapperApp/Status.pro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ ios {
4444

4545
QMAKE_INFO_PLIST = $$PWD/../ios/Info.plist
4646
QMAKE_IOS_DEPLOYMENT_TARGET=16.0
47-
QMAKE_TARGET_BUNDLE_PREFIX = im.status
48-
QMAKE_BUNDLE = app
47+
QMAKE_TARGET_BUNDLE_PREFIX = app.status
48+
QMAKE_BUNDLE = mobile
4949
QMAKE_ASSET_CATALOGS += $$PWD/../ios/Images.xcassets
5050
QMAKE_IOS_LAUNCH_SCREEN = $$PWD/../ios/launch-image-universal.storyboard
5151

0 commit comments

Comments
 (0)