Managing different Environments using XCode Build Schemes and Configurations
Hello! In this article I covered the following things:
- What is Workspace, Project , Target, Scheme, Build Settings, Configurations, Build Phases
- How to manage different environment (QA. DEV , PROD) Using Multiple Configurations and Schemes
- How to Setup third-party entries in Single Info.plist for All environment
- How to Manage Different Server Url for Different Environment Using UDS(User Defined Setting)
- How to Manage Different Server Url for Different Environment Using Xcode Configuration File
- How to incorporate FireBase GoogleService-Info In Multiple Configuration By Injecting File through Code
- How to incorporate FireBase GoogleService-Info In Multiple Configuration Using Run Script
- Should we use multiple targets for managing Different Environment ?
Workspace
- A workspace can contain any number of Xcode projects
- 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
- 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
- It contains one or more targets, which specify how to build products. (Extension , Unit and UITest)
- 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).
- 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)
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 .
- Each target defines a list of build settings for that project
- Each target also defines a list of classes, resources, custom scripts etc to include/ use when building.
Xcode Scheme
- You can have as many schemes as you want, but only one can be active at a time.
- Schemes define configuration to use when building, and a collection of tests to execute.
- 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)
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
- DEBUG (For Development and Pointing to Dev Server)
- RELEASE (For Production and Pointing to Prod Server)
- 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
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.
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.
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
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
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
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
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
As shown in Figure 10.1 and 10.2 we configured app icon based on the Scheme using Configurations
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
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
We added entry in Info.plist
file
In our code we get the SERVER_URL
, since our Scheme is QA it fetched the QA Url from the environment variable Super Cool
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
Now assign some values I added Dev for Dev config. You can add Server Url , token etc
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
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
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
- Get the value from
APPLICATION_NAME
we configured in Using Xcode Configuration File (.xcconfig) section - Copy Google service file to main Project with the name
GoogleService-Info.plist
that is required by the Firebase
Complete script code
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
- You need maintain only one plist file as compared to multiple targets
- While adding a new file you need to ensure to select all targets to keep your code synced in all configurations.
- Extra targets will be maintained in podfile