Managing different Environments using XCode Build Schemes and Configurations

Ali Akhtar
12 min readAug 4, 2019

Hello! In this article I covered the following things:

  1. What is Workspace, Project , Target, Scheme, Build Settings, Configurations, Build Phases
  2. How to manage different environment (QA. DEV , PROD) Using Multiple Configurations and Schemes
  3. How to Setup third-party entries in Single Info.plist for All environment
  4. How to Manage Different Server Url for Different Environment Using UDS(User Defined Setting)
  5. How to Manage Different Server Url for Different Environment Using Xcode Configuration File
  6. How to incorporate FireBase GoogleService-Info In Multiple Configuration By Injecting File through Code
  7. How to incorporate FireBase GoogleService-Info In Multiple Configuration Using Run Script
  8. Should we use multiple targets for managing Different Environment ?

Workspace

A workspace is an Xcode document that groups projects

  1. A workspace can contain any number of Xcode projects
  2. File in one Xcode project can be visible to Other project within a same workspace because By default, all the Xcode projects in a workspace are built in the same directory, referred to as the workspace build directory
  3. One Xcode project can be used by multiple workspace because each project in a workspace continues to have its own independent identity.

Xcode Project

An Xcode project is a repository for all the files, resources, and information required to build one or more software products

  1. It contains one or more targets, which specify how to build products. (Extension , Unit and UITest)
  2. A project defines default build settings for all the targets in the project (each target can also specify its own build settings, which override the project build settings).
  3. You can specify more than one build configuration ((DEBUG, RELEASE)) for a project

As shown in Figure 1 we have a project build setting and a Target build setting. If we don’t override it then it will used the project setting . (Inheritance like)

Figure 1

Xcode Target

A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace

Target is a combination of inputs usually source files and resources and then instruction how to build them and what to build them. A testing target can add bunch of tests that you can run and project contains one or more target . They can depend on other target . UI and unit test depend on your app target so when you run your unit test it has to build your app .

  1. Each target defines a list of build settings for that project
  2. Each target also defines a list of classes, resources, custom scripts etc to include/ use when building.

Xcode Scheme

A build scheme is a blueprint for an entire build process, a way of telling Xcode what build configurations you want to use to create the development, unit test, and production builds for a given target (framework or app bundle).

  1. You can have as many schemes as you want, but only one can be active at a time.
  2. Schemes define configuration to use when building, and a collection of tests to execute.
  3. One target should have at least one scheme

As shown in Figure 2 I created two schemes MultipleEnvironmentWithAddressSanitizer(enable address sanitizer) and the one created by default MultipleEnvironment (disable address sanitizer)

Figure 2

Build Settings

Build setting are options for the build phases basically they are just bunch of variables when building a target

Configurations

Just a set of build settings so if you have a target which has build setting you can have a configuration which is another set of build settings usually way you get from xcode template debug and release configurations so you will notice that your app kind of gets build differently if you are debugging it from xcode vs archiving release

Build Phases

Another part of target is build phases each target has a set of build phases , there are some built in ones like the compile the code plus you can create

You can create run script that can be a bash script, perl script , ruby script pretty much everything and do whatever you want that will be included as part of the build process. If you are familiar with makefile is kind of like that , if you want to run swiftlint maybe as part of your build process or a code formatter do some other stuff that maybe Xcode does not give out of the box, All build setting are available as environment variable within the script

Managing Different environment Using Multiple Configurations and Schemes

As shown in Figure 3 we created a single view application. It created a Project “MultipleScheme” with three targets (Main app , Unit and UI Test) and with one Scheme and two configuration (DEBUG, RELEASE). We will be creating three environments with the following purpose

  1. DEBUG (For Development and Pointing to Dev Server)
  2. RELEASE (For Production and Pointing to Prod Server)
  3. QA (Distribute Build To QA and Pointing to QA Server)

Rule of Thumb:

Project can have multiple targets which create a Single Project. We create target when we have lots of difference like Unit and Main application can’t be same and one example would be extension which has a lot of architecture difference and finally Scheme when we create a project it created a scheme with two configurations and the best practice is to use one scheme per configuration and the question is when to create configuration when we have a same project or target with little tweaks like configuration changes / example would be managing different environment

Figure 3

Select the top-level element in the project navigator and make sure the “YOUR_PROJECT_NAME” item in the Project section is selected. Once done, you should see that Xcode already provides you two different configuration levels: Debug and Release. If you didn't’ aware that before, this means you can create a build for debug and another one for release with a different setting.

Figure 4

Now we are going to create a new configuration. Let’s just call it “QA”. Click on the + sign right beneath the configuration list and select “Duplicate Debug configuration”. This step is very important since it will clone the configuration of DEBUG and as shown in Figure 5.3 QA and DEBUG currently have same settings. So let’s say if you want QA configuration acts like a Production you should duplicate it with Release Configuration.

Figure 5.1
Figure 5.2
Figure 5.3

Click on the Project name just before the simulator and tap on Manage Schemes. We are doing this step as I said earlier it’s a best practice to have one scheme per configuration

Figure 6

First uncheck current Scheme and click on + icon

Name the scheme as Debug and hit OK and Repeat same step for QA and Release

As shown in Figure 7 we are done with creating Schemes Now we need to Edit each schemes and configured it to use only one configuration

Figure 7

As shown in Gif 1 we edit DEBUG scheme and make it to to follow single configuration Repeat the same process for Release and QA Schemes and choose Build Configuration respectively

Gif 1

As shown in Figure 8 we can select Provisional Profile based on our Configurations . Scheme is nothing but a blueprint as I said earlier when we select scheme it triggered associated configuration and create a build according to it

Figure 8

We have different bundle identifier for development , QA and release So using configurations we can define different bundle identifier for different configurations and as shown in Figure 9.1 currently we have same bundle identifier for all environment we changed it and as shown in Figure 9.2 we installed three build in the simulator with the specified configurations

Figure 9.1
Figure 9.2

As shown in Figure 10.1 and 10.2 we configured app icon based on the Scheme using Configurations

Figure 10.1
Figure 10.2

Setup third-party entries in Info.plist

Having a single target means having a single Info.plist file. This can be problematic when third-party vendors require entries to be added to the Info.plist file. For example, when integrating with Fabric , you need to add entries for “APIKey”. It’s common practice to setup a Fabric app for every environment. So how can we setup different values for those entries based on our environment? User-Defined Settings of course!

To create a UDS for the Bundle Name, select your Target and then go to the Build Settings tab. On the top ribbon, select the + and choose “Add User-Defined Setting”.

This will create a new entry for us. Give the setting a name of “API_KEY_FABRIC”. The setting name can be anything, Once added, expand the entry to show all of the environments. Enter your values for each configuration you have setup as shown Figure 11.1 to 11.4

Figure 11.1
Figure 11.2
Figure 11.3
Figure 11.4

Once the UDS is created, we’ll need to update the Info.plist to use the values we setup. We use UDS by setting the values in the Info.plist using the convention of ${UDS_NAME}. So head to Info.plist and change the value for “APIKey” to ${API_KEY_FABRIC}.

Managing Different Server Url for Different Environment

There will be two approaches for maintaining different server url for different environment

1. Using UDS (User Defined Setting)

As shown in Figure 12.1 we created UDS with name SERVER_URL with different values according to different configurations

Figure 12.1

We added entry in Info.plist file

Figure 12.2

In our code we get the SERVER_URL , since our Scheme is QA it fetched the QA Url from the environment variable Super Cool

Figure 12.3

2. Using Xcode Configuration File (.xcconfig)

we will use Xcode configuration file (.xcconfig) instead of using UDS to manage the things (such as which tokens, api keys, urls of backends should be used).

If you don’t know what an Xcode Configuration file (.xcconfig) is, it is actually a key/value based file. You can store your build settings in the form of key/value pairs, similar to what you did in dictionaries.

Now go back to the project to create a .xcconfig file. In the project navigator, right click the project folder and choose New file…. In the dialog that pops up, select the Configurations Settings File. In the next screen, give it the name “ConfigDev” and make sure the targets checkboxes are all unchecked, because you don’t want to include this in your app’s bundle

As shown in Figure 13 we created three config files ConfigDev, ConfigQA and ConfigProd

Figure 13

Now assign some values I added Dev for Dev config. You can add Server Url , token etc

Figure 14

Tap on Project and go the Info tab and Select each config file with the associated configuration. This step is very important

As shown in Figure 14 we added APPLICATION_NAME entry in Info.plist file and remember we didn’t create any UDS in the build settings

Figure 14.1

As shown in Figure 14.2 we are accessing our constant from the config file we created. So We can create different Url from different configurations using two approaches one is to define in as a UDS and other one is to define in config file

Figure 14.2

FireBase GoogleService-Info In Multiple Configuration

There can be two possible solutions to configured GoogleService-Info into different configurations

1. Inject File through Code

If the builds are part of a single target, the best option is to give configuration files unique names (e.g. GoogleService-Info-QA.plist,GoogleService-Info-Prod and GoogleService-Info-Dev.plist). Then choose at runtime which plist to load. This is shown in the following example:

// Load a named file.
let filePath = Bundle.main.path(forResource: "MyGoogleService", ofType: "plist")
guard let fileopts = FirebaseOptions(contentsOfFile: filePath!)
else { assert(false, "Couldn't load config file") }
FirebaseApp.configure(options: fileopts)

Warning: This approach can impact Analytics collection in some circumstances, see the reliable analytics section.

2. Run Script

For each configurations we put the file in folder

As shown in Figure 15 we created a run script that is doing the following things

  1. Get the value from APPLICATION_NAME we configured in Using Xcode Configuration File (.xcconfig) section
  2. Copy Google service file to main Project with the name GoogleService-Info.plist that is required by the Firebase
Figure 15

Complete script code

Figure 16

These steps are performed during the build process as I said earlier “ You can create run script that can be a bash script, perl script , ruby script pretty much everything and do whatever you want that will be included as part of the build process”.

Should we use multiple targets for managing Different Environment ?

The best practice is to use single target with multiple schemes and configuration and the reasons are

  1. You need maintain only one plist file as compared to multiple targets
  2. While adding a new file you need to ensure to select all targets to keep your code synced in all configurations.
  3. Extra targets will be maintained in podfile

Useful Links

--

--

Ali Akhtar

Senior iOS Engineer | HungerStation | Delivery Hero