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