Features of assembly and delivery of iOS applications

In this article, we share the experience of building and delivering iOS applications to users, which Plarium Krasnodar has gained in the process of debugging CI/CD.

Features of assembly and delivery of iOS applications

Prepare

Every person, one way or another connected with the development of applications for Apple devices, has already managed to appreciate the controversial convenience of the infrastructure. Complexities are everywhere, from the developer profile menu to the debug and build tools.

There are plenty of articles about the “basics” on the net, so we will try to highlight the main thing. Here's what you need to successfully build the application:

  • developer account;
  • a macOS-based device acting as a build server;
  • generated developer certificate, which will be further used to sign the application;
  • created application with a unique ID (The importance of the Bundle Identifier should be noted, because the use of a wildcard ID makes it impossible to use many of the application's functions, for example: Associated Domains, Push Notifications, Apple Sign In and others);
  • profile application signature.

A developer certificate must be generated via Keychain on any macOS device. The type of certificate is very important. Depending on the application environment (Dev, QA, Staging, Production), it will differ (Development or Distribution), as well as the application signing profile type.

Main types of profiles:

  • Development - designed to sign the application of the development team, the Development certificate is used (type name iPhone Developer: XXXXX);
  • Ad Hoc - intended for signing a test application and internal verification by the QA department, using the developer's Distribution certificate (type name iPhone Distribution: XXXXX);
  • App Store is a release build for external testing via TestFlight and uploading to the App Store, using a developer's Distribution certificate.

When generating the Development and Ad Hoc profiles, it is also indicated device list, on which you can install the build, which allows you to further restrict access for users. There is no list of devices in the App Store profile, since TestFlight, which will be discussed later, is responsible for access control during closed beta testing.

For clarity, you can present the developer profile in the form of a table below. This makes it easier to understand what parameters for the assembly we need and where to get them from.

Features of assembly and delivery of iOS applications

Assembly

To make it easier to separate assemblies by project and environment, we use the names of profiles of the form ${ProjectName}_${Instance}, that is, the project name + instance (depending on the application environment: Dev, QA, GD, Staging, Live, and so on).

When imported to the build server, the profile changes its name to a unique ID and moves to the folder /Users/$Username/Library/MobileDevice/Provisioning Profiles (Where $Username matches the user account name of the build server).

There are two ways to assemble the *.ipa file - outdated (PackageApplication) and modern (through XcAchive creation and export). The first method is considered obsolete, since the app file packaging module has been removed from the Xcode distribution since version 8.3. To use it, you need to copy the module from the old Xcode (version 8.2 and earlier) to the folder:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/

And then run the command:

chmod +x /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/*

Next, you need to build the *.app file of the application:

xcodebuild 
-workspace $ProjectDir/$ProjectName.xcworkspace 
-scheme $SchemeName 
-sdk iphoneos 
build 
-configuration Release 
-derivedDataPath build 
CODE_SIGN_IDENTITY=”$DevAccName”
PROVISIONING_PROFILE=”$ProfileId”
DEPLOYMENT_POSTPROCESSING=YES 
SKIP_INSTALL=YES 
ENABLE_BITCODE=NO

Where:

-workspace - path to the project file.

-scheme - used scheme specified in the project.

-derivedDataPath - the path to unload the assembled application (*.app).

CODE_SIGN_IDENTITY — developer account name, which can be checked in Keychain (iPhone Developer: XXXX XXXXXXX, no TeamID in brackets).

Features of assembly and delivery of iOS applications

PROVISIONING_PROFILE — Profile ID for signing the application, which can be obtained with the command:

cd "/Users/$Username/Library/MobileDevice/Provisioning Profiles/" && find *.mobileprovision -type f | xargs grep -li ">${ProjectName}_${Instance}<" | sed -e 's/.mobileprovision//'

If the application uses an additional profile (for example, for Push Notifications), then instead of PROVISIONING_PROFILE indicate:

APP_PROFILE=”$AppProfile” 
EXTENSION_PROFILE=”$ExtProfile” 

Next, the resulting *.app file should be packed into *.ipa. To do this, you can use a command like:

/usr/bin/xcrun --sdk iphoneos PackageApplication 
-v $(find "$ProjectDir/build/Build/Products/Release-iphoneos" -name "*.app") 
-o "$ProjectDir/$ProjectName_$Instance.ipa"

However, this method is considered obsolete from the point of view of Apple. Relevant is getting *.ipa by exporting from the application archive.

First you need to build the archive with the command:

xcodebuild 
-workspace $ProjectDir/$ProjectName.xcworkspace 
-scheme $SchemeName 
-sdk iphoneos 
-configuration Release 
archive 
-archivePath $ProjectDir/build/$ProjectName.xcarchive 
CODE_SIGN_IDENTITY=”$DevAccName” 
PROVISIONING_PROFILE=”$ProfileId”
ENABLE_BITCODE=NO 
SYNCHRONOUS_SYMBOL_PROCESSING=FALSE

The differences are in the assembly method and options SYNCHRONOUS_SYMBOL_PROCESSING, which disables symbol unloading at build time.

Next, we need to generate a file with export settings:

ExportSettings="$ProjectDir/exportOptions.plist"

cat << EOF > $ExportSettings
<?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>compileBitcode</key>
<false/>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<false/>
<key>method</key>
<string>$Method</string>
<key>provisioningProfiles</key>
<dict>
<key>$BundleID</key>
<string>$ProfileId</string>
</dict>
<key>signingCertificate</key>
<string>$DevAccName</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>$TeamID</string>
<key>thinning</key>
<string><none></string>
</dict>
</plist>
EOF

Where:

$Method - delivery method, corresponds to the application signature profile type, that is, for Development, the value will be development, for Ad Hoc - ad-hoc, and for the App Store - app-store.

$BundleID - Application ID, which is specified in the application settings. You can check with the command:

defaults read $ProjectDir/Info CFBundleIdentifier

$DevAccName и $ProfileId - the developer name and signature profile ID settings that were previously used and must match the values ​​in the export settings.

$TeamID - ten-digit ID in brackets after the name of the developer, example: iPhone Developer: ...... (XXXXXXXXXX); can be checked in Keychain.

Next, using the export command, we get the necessary * .ipa file:

xcodebuild 
-exportArchive 
-archivePath $ProjectDir/build/$ProjectName.xcarchive 
-exportPath $ProjectDir 
-exportOptionsPlist $ExportSettings

Delivery

Now the assembled file needs to be delivered to the end user, that is, installed on the device.

There are many services for distributing Development and Ad Hoc builds, such as HockeyApp, AppBlade and others, but in this article we will focus on a standalone server for distributing applications.

Installing the iOS application takes place in 2 stages:

  1. Obtaining the application installation manifest through the Items Service.
  2. Installing the *.ipa file according to the information specified in the manifest via HTTPS.

Thus, we first need to generate an installation manifest (file type *.plist) with the command:

cat << EOF > $manifest
<?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>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>$ipaUrl</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>$BundleID</string>
<key>bundle-version</key>
<string>$AppVersion</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>$ProjectName_$Instance</string>
<key>subtitle</key>
<string>$Instance</string>
</dict>
</dict>
</array>
</dict>
</plist>
EOF

As you can see, the manifest contains almost all the parameters involved in building the application.

Application version ($AppVersion) can be checked with the command:

defaults read $ProjectDir/Info CFBundleVersion

Parameter $ipaUrl contains a direct link to download the *.ipa file. From the seventh version of iOS, the application must be installed via HTTPS. In the eighth version, the manifest format has slightly changed: the blocks with the settings for the application icons of the view have been removed.

<images>
   <image>...</image>
</images>

Thus, to install the application, a simple html page with a link like this is enough:

itms-services://?action=download-manifest&url=https://$ServerUrl/$ProjectName/$Instance/iOS/$AppVersion/manifest.plist

For the needs of development and testing departments, Plarium has created its own build installation application, which gives us:

  • autonomy and independence,
  • centralization of access control and secure installation of applications through "temporary", dynamically created links,
  • extensible functionality (that is, the development team, if necessary, can integrate the missing functions into an existing application).

The test is

Now we will talk about pre-release testing of the application using TestFlight.

Prerequisites for downloading are the type of App Store signing profile and the presence of generated API keys.

There are several ways to download the application:

  • via Xcode (Organizer),
  • via altool,
  • via Application Loader for older versions of Xcode (now Transporter).

For automatic download, altool is used, which also has two authorization methods:

  • App Specific Password,
  • API key.

It is preferable to download the application using the API Key.

To get the API Key, go to link and generate a key. In addition to the key itself in *.p8 format, we need two parameters: IssuerID and KeyID.

Features of assembly and delivery of iOS applications

Next, import the downloaded key to the build server:

mkdir -p ~/.appstoreconnect/private_keys
mv ~/Downloads/AuthKey_${KeyID}.p8 ~/.appstoreconnect/private_keys/

Before uploading the application to TestFlight, you need to validate the application, we do this with the command:

xcrun altool 
--validate-app 
-t ios 
-f $(find "$ProjectDir" -name "*.ipa") 
--apiKey “$KeyID” 
--apiIssuer “$IssuerID” 

Where apiKey и apiIssuer have field values ​​from the API key generation page.

Further, upon successful validation, we load the application with the command --upload-app with the same parameters.

The application will be tested by Apple within one or two days and after that it will become available to external testers: they will be sent links for installation by mail.

Another way to download an application through altool is to use the App-Specific Password.

To get the App-Specific Password, you need to go to link and generate it in the Security section.

Features of assembly and delivery of iOS applications

Next, create a build server entry in the Keychain with this password. From version 11 of Xcode, this can be done with the command:

xcrun altool --store-password-in-keychain-item "Altool" -u "$DeveloperName" -p $AppPswd

Where:

$DeveloperName — iOS developer account name used to log in to Apple services.

$AppPswd - generated App-Specific Password.

Next, we get the value of the asc-provider parameter and check the success of the password import with the command:

xcrun altool --list-providers -u "$DeveloperName" -p "@keychain:Altool"

We get the output:

Provider listing:
- Long Name - - Short Name -
XXXXXXX        XXXXXXXXX

As you can see, the Short Name (asc-provider) value we are looking for matches the $TeamID parameter that we used when building the application.

To validate and load the application in TestFlight, use the command:

xcrun altool 
--(validate|upload)-app   
-f $(find "$ProjectDir" -name "*.ipa") 
-u "$DeveloperName" 
-p "@keychain:Altool" 

As parameter value -p you can take the value $AppPswd in an unencrypted (explicit) form.

However, as already mentioned, from the point of view of performance, it is better to choose the API Key for altool authorization, since different versions of Xcode have certain problems (“does not see” the Keychain, authorization errors when uploading, etc.).

That, in fact, is all. I wish everyone involved successful builds and trouble-free releases in the App Store.

Source: habr.com

Add a comment