1# iOS {#ios} 2 3This component is basically a wrapper/workaround that makes it possible to 4expose an Xcode installation as a Nix package by means of symlinking to the 5relevant executables on the host system. 6 7Since Xcode can't be packaged with Nix, nor we can publish it as a Nix package 8(because of its license) this is basically the only integration strategy 9making it possible to do iOS application builds that integrate with other 10components of the Nix ecosystem 11 12The primary objective of this project is to use the Nix expression language to 13specify how iOS apps can be built from source code, and to automatically spawn 14iOS simulator instances for testing. 15 16This component also makes it possible to use [Hydra](https://nixos.org/hydra), 17the Nix-based continuous integration server to regularly build iOS apps and to 18do wireless ad-hoc installations of enterprise IPAs on iOS devices through 19Hydra. 20 21The Xcode build environment implements a number of features. 22 23## Deploying a proxy component wrapper exposing Xcode {#deploying-a-proxy-component-wrapper-exposing-xcode} 24 25The first use case is deploying a Nix package that provides symlinks to the Xcode 26installation on the host system. This package can be used as a build input to 27any build function implemented in the Nix expression language that requires 28Xcode. 29 30```nix 31let 32 pkgs = import <nixpkgs> {}; 33 34 xcodeenv = import ./xcodeenv { 35 inherit (pkgs) stdenv; 36 }; 37in 38xcodeenv.composeXcodeWrapper { 39 version = "9.2"; 40 xcodeBaseDir = "/Applications/Xcode.app"; 41} 42``` 43 44By deploying the above expression with `nix-build` and inspecting its content 45you will notice that several Xcode-related executables are exposed as a Nix 46package: 47 48```bash 49$ ls result/bin 50lrwxr-xr-x 1 sander staff 94 1 jan 1970 Simulator -> /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator 51lrwxr-xr-x 1 sander staff 17 1 jan 1970 codesign -> /usr/bin/codesign 52lrwxr-xr-x 1 sander staff 17 1 jan 1970 security -> /usr/bin/security 53lrwxr-xr-x 1 sander staff 21 1 jan 1970 xcode-select -> /usr/bin/xcode-select 54lrwxr-xr-x 1 sander staff 61 1 jan 1970 xcodebuild -> /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild 55lrwxr-xr-x 1 sander staff 14 1 jan 1970 xcrun -> /usr/bin/xcrun 56``` 57 58## Building an iOS application {#building-an-ios-application} 59 60We can build an iOS app executable for the simulator, or an IPA/xcarchive file 61for release purposes, e.g. ad-hoc, enterprise or store installations, by 62executing the `xcodeenv.buildApp {}` function: 63 64```nix 65let 66 pkgs = import <nixpkgs> {}; 67 68 xcodeenv = import ./xcodeenv { 69 inherit (pkgs) stdenv; 70 }; 71in 72xcodeenv.buildApp { 73 name = "MyApp"; 74 src = ./myappsources; 75 sdkVersion = "11.2"; 76 77 target = null; # Corresponds to the name of the app by default 78 configuration = null; # Release for release builds, Debug for debug builds 79 scheme = null; # -scheme will correspond to the app name by default 80 sdk = null; # null will set it to 'iphonesimulator` for simulator builds or `iphoneos` to real builds 81 xcodeFlags = ""; 82 83 release = true; 84 certificateFile = ./mycertificate.p12; 85 certificatePassword = "secret"; 86 provisioningProfile = ./myprovisioning.profile; 87 signMethod = "ad-hoc"; # 'enterprise' or 'store' 88 generateIPA = true; 89 generateXCArchive = false; 90 91 enableWirelessDistribution = true; 92 installURL = "/installipa.php"; 93 bundleId = "mycompany.myapp"; 94 appVersion = "1.0"; 95 96 # Supports all xcodewrapper parameters as well 97 xcodeBaseDir = "/Applications/Xcode.app"; 98} 99``` 100 101The above function takes a variety of parameters: 102 103* The `name` and `src` parameters are mandatory and specify the name of the app 104 and the location where the source code resides 105* `sdkVersion` specifies which version of the iOS SDK to use. 106 107It also possible to adjust the `xcodebuild` parameters. This is only needed in 108rare circumstances. In most cases the default values should suffice: 109 110* Specifies which `xcodebuild` target to build. By default it takes the target 111 that has the same name as the app. 112* The `configuration` parameter can be overridden if desired. By default, it 113 will do a debug build for the simulator and a release build for real devices. 114* The `scheme` parameter specifies which `-scheme` parameter to propagate to 115 `xcodebuild`. By default, it corresponds to the app name. 116* The `sdk` parameter specifies which SDK to use. By default, it picks 117 `iphonesimulator` for simulator builds and `iphoneos` for release builds. 118* The `xcodeFlags` parameter specifies arbitrary command line parameters that 119 should be propagated to `xcodebuild`. 120 121By default, builds are carried out for the iOS simulator. To do release builds 122(builds for real iOS devices), you must set the `release` parameter to `true`. 123In addition, you need to set the following parameters: 124 125* `certificateFile` refers to a P12 certificate file. 126* `certificatePassword` specifies the password of the P12 certificate. 127* `provisioningProfile` refers to the provision profile needed to sign the app 128* `signMethod` should refer to `ad-hoc` for signing the app with an ad-hoc 129 certificate, `enterprise` for enterprise certificates and `app-store` for App 130 store certificates. 131* `generateIPA` specifies that we want to produce an IPA file (this is probably 132 what you want) 133* `generateXCArchive` specifies that we want to produce an xcarchive file. 134 135When building IPA files on Hydra and when it is desired to allow iOS devices to 136install IPAs by browsing to the Hydra build products page, you can enable the 137`enableWirelessDistribution` parameter. 138 139When enabled, you need to configure the following options: 140 141* The `installURL` parameter refers to the URL of a PHP script that composes the 142 `itms-services://` URL allowing iOS devices to install the IPA file. 143* `bundleId` refers to the bundle ID value of the app 144* `appVersion` refers to the app's version number 145 146To use wireless adhoc distributions, you must also install the corresponding 147PHP script on a web server (see section: 'Installing the PHP script for wireless 148ad hoc installations from Hydra' for more information). 149 150In addition to the build parameters, you can also specify any parameters that 151the `xcodeenv.composeXcodeWrapper {}` function takes. For example, the 152`xcodeBaseDir` parameter can be overridden to refer to a different Xcode 153version. 154 155## Spawning simulator instances {#spawning-simulator-instances} 156 157In addition to building iOS apps, we can also automatically spawn simulator 158instances: 159 160```nix 161let 162 pkgs = import <nixpkgs> {}; 163 164 xcodeenv = import ./xcodeenv { 165 inherit (pkgs) stdenv; 166 }; 167in 168xcode.simulateApp { 169 name = "simulate"; 170 171 # Supports all xcodewrapper parameters as well 172 xcodeBaseDir = "/Applications/Xcode.app"; 173} 174``` 175 176The above expression produces a script that starts the simulator from the 177provided Xcode installation. The script can be started as follows: 178 179```bash 180./result/bin/run-test-simulator 181``` 182 183By default, the script will show an overview of UDID for all available simulator 184instances and asks you to pick one. You can also provide a UDID as a 185command-line parameter to launch an instance automatically: 186 187```bash 188./result/bin/run-test-simulator 5C93129D-CF39-4B1A-955F-15180C3BD4B8 189``` 190 191You can also extend the simulator script to automatically deploy and launch an 192app in the requested simulator instance: 193 194```nix 195let 196 pkgs = import <nixpkgs> {}; 197 198 xcodeenv = import ./xcodeenv { 199 inherit (pkgs) stdenv; 200 }; 201in 202xcode.simulateApp { 203 name = "simulate"; 204 bundleId = "mycompany.myapp"; 205 app = xcode.buildApp { 206 # ... 207 }; 208 209 # Supports all xcodewrapper parameters as well 210 xcodeBaseDir = "/Applications/Xcode.app"; 211} 212``` 213 214By providing the result of an `xcode.buildApp {}` function and configuring the 215app bundle id, the app gets deployed automatically and started. 216 217## Troubleshooting {#troubleshooting} 218 219In some rare cases, it may happen that after a failure, changes are not picked 220up. Most likely, this is caused by a derived data cache that Xcode maintains. 221To wipe it you can run: 222 223```bash 224$ rm -rf ~/Library/Developer/Xcode/DerivedData 225```