Using Xcode Configuration and Scheme to Manage Multiple Build Environments

Photo by Heather Ford on Unsplash
Photo by Heather Ford on Unsplash
When developing a project, we need to build multiple versions, and each version will have individual environment settings. For example, the development version connects to the development server, and the QA version connects to the QA testing server.

When developing a project, we need to build multiple versions, and each version will have individual environment settings. For example, the development version connects to the development server, and the QA version connects to the QA testing server. This article will explain how to use Xcode’s Configuration and Scheme to build multiple versions.

The complete code for this chapter can be found in .

Overview

In this article, we will create two versions, Develop and Production. The Develop version is for development, and the Production version is for the Apple Store. Each version will have Debug build and Release build.

Create Configuration Files (.xcconfig)

First, we need to create individual configuration files for the Develop and Production versions. Add a Configuration Settings File to the project.

Choose Configuration Settings File
Choose Configuration Settings File

Enter Develop.xcconfig as the file name. In the red box, do not select any targets, because we do not want to package the Develop.xcconfig into the targets. After creation, repeat the same steps to create a Configuration Settings File called Production.xcconfig.

The Develop version will use Develop.xcconfig, and the Production version will use Production.xcconfig.

Create Develop.xcconfig
Create Develop.xcconfig

Creating Builds

Next, we need to assign configuration files to each build.

Xcode creates Debug and Release builds by default. We renamed Debug and Release to DevDebug and DevRelease respectively.

Rename Debug to DevDebug and Release to DevRelease.
Rename Debug to DevDebug and Release to DevRelease.

Click the + button, select Duplicate DevDebug Configuration, and name it ProDebug. Click again, select Duplicate DevRelease Configuration, and name it ProRelease.

Create ProDebug by duplicating DevDebug.
Create ProDebug by duplicating DevDebug.

So, we have four builds in total.

Four configurations: DevDebug, DevRelease, ProDebug, ProRelease.
Four configurations: DevDebug, DevRelease, ProDebug, ProRelease.

Assign Develop.xcconfig to DevDebug and DevRelease, and Production.xcconfig to ProDebug and ProRelease.

Set a configuration settings file to each configuration.
Set a configuration settings file to each configuration.

Creating Schemes

Finally, we assign each scheme its Debug and Release builds.

Click Manage Scheme.

Select Manage Schemes.
Select Manage Schemes.

Rename the existing “ConfigurationExample” scheme to “Develop ConfigurationExample”. As the name suggests, it is for the Develop version.

Rename ConfigurationExample scheme to "Develop ConfigurationExample" scheme.
Rename ConfigurationExample scheme to “Develop ConfigurationExample” scheme.

Confirm that the “Develop ConfigurationExample” scheme uses DevDebug and DevRelease builds.

"Develop Configuration Example" uses DebDebug and DevRelease configurations.
“Develop Configuration Example” uses DebDebug and DevRelease configurations.

Next, we need to create a scheme for the Production version. Click New Scheme.

Select New Scheme.
Select New Scheme.

Enter “Production ConfigurationExample”.

Create "Production Configuration Examples".
Create “Production Configuration Examples”.

After creation, assign all the builds of the “Production ConfigurationExample” scheme as ProDebug and ProRelease builds.

"Product ConfigurationExample" uses ProDebug and ProRelease configurations.
“Product ConfigurationExample” uses ProDebug and ProRelease configurations.

Adding Settings in the Configuration file

Now let’s adding a new value to a setting in the configuration file.

Configuration files use the following format:

<SettingName> = <SettingValue>

If you set a value to a setting that does not exist, it declares a new setting.

NEW_SETTING = Hello Wayne's Talk

If you set a value to an existing setting, it will overwrite the original value of the setting. We can also use $(inherited) to get the original value of the setting. You can find more pre-defined settings in the Build settings reference.

OTHER_SWIFT_FLAGS = $(inherited) -v

We declare two new settings in Develop.xcconfig and Production.xcconfig: APP_NAME and APP_ENV. The next part of this article will explain how to use these two settings.

// Develop.xcconfig
APP_NAME = Develop
APP_ENV = DEV
// Production.xcconfig
APP_NAME = Production
APP_ENV = PRO

Using a Different App Name for Each Version

Now we have two versions. We want different versions to display different App names.

In TARGETS -> ConfigurationExample -> Build Settings, search for PRODUCT_NAME.

Find Product Name in Build Settings.
Find Product Name in Build Settings.

Change the original value $(TARGET_NAME) to $(APP_NAME)APP_NAME is the setting we just added in the configuration file. You can try to run “Develop ConfigurationExample” and “Production ConfigurationExample” to see if their App names have changed.

Assign $(APP_NAME) to Product Name.
Assign $(APP_NAME) to Product Name.
Product Name is different in each build.
Product Name is different in each build.

Using a Different Bundle Identifier for Each Version

We may want to install all versions to a device at the same time. In order to do that, we must set a different Bundle Identifier for each version.

In TARGETs -> ConfigurationExample -> Signing & Capabilities, we found that each build has an individual page. In the DevDebug and DevRelease pages, set the Bundle Identifier to com.waynestalk.ConfigurationExample.dev.

Set Bundle Identifier to .dev for DevDebug and DevRelease.
Set Bundle Identifier to .dev for DevDebug and DevRelease.

In the ProDebug and ProRelease pages, set the Bundle Identifier to com.waynestalk.ConfigurationExample.pro.

Set Bundle Identifier to .pro for ProDebug and ProRelease.
Set Bundle Identifier to .pro for ProDebug and ProRelease.

Switch to the All page, you can see all Bundle Identifiers.

All Bundle Identifiers.
All Bundle Identifiers.

Programmatically Accessing the Settings in the Configuration Files

It is not possible to directly access the settings in the configuration file programmatically. However, we can do this by adding a setting to Info. In this way, we can access it programmatically.

In TARGETS -> ConfigurationExample -> Info, add a new key named AppEnv and set its value as $(APP_ENV)APP_ENV is the setting we just added in the configuration file.

Add APP_ENV setting to info.plist.
Add APP_ENV setting to info.plist.

Then, we can access AppEnv programmatically, as the following code.

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let env = (Bundle.main.infoDictionary!["AppEnv"] as! String)
        print(env)
    }
}

Using Different Resource Files for Each Version

Sometimes we need to use different resource files in each version, such as Firebase’s GoogleService-Info.plist.

As we know, a configuration file is a text file. So the easiest way is to add resource files of all versions to the project and build them into the app. Then, set the path of the corresponding resource file for each version in the configuration file. In this way, at runtime, the app reads the path of the resource file, and then loads the resource file.

However, the above method has a disadvantage that we must build the resource files of all versions into the app.

We will introduce another method. This method is to add resource files of all versions to the project, but do not build them into the app. When building, copy the corresponding resource files to the build folder.

Below we will take Firebase’s GoogleService-Info.plist as an example.

First, add GoogleService-Info.plist of the Develop version to the project, and check Add to targets.

Add GoogleService-Info.plist to the project with adding to targets.
Add GoogleService-Info.plist to the project with adding to targets.

Under the project directory, add the Develop/ and Production/ folders. Add the Develop version of GoogleService-Info.plist to the Develop/ folder, and uncheck Add to targets. Then add the Production version of GoogleService-Info.plist to the Production/ folder, and uncheck Add to targets.

Add GoogleService-Info.plist to Develop/ and Production/ without adding to targets.
Add GoogleService-Info.plist to Develop/ and Production/ without adding to targets.

After the above is completed, we can see that there are three GoogleService-Info.plist in the project. But there is only one GoogleService-Info.plist in the Copy Bundle Resources list.

Three GoogleService-Info.plist in the project, but only one in Copy Bundle Resources.
Three GoogleService-Info.plist in the project, but only one in Copy Bundle Resources.

Under TARGETS -> ConfigurationExample -> Build Phrases, click the + button in the upper left corner, then click New Run Script Phrase.

Click New Run Script Phrase.
Click New Run Script Phrase.

Paste the following code into the Run Script Phrase. In the code, we copy the corresponding GoogleService-Info.plist to the build folder according to ${CONFIGURATION}.

echo "Copy GoogleService-info.plist"
echo "Configuration is ${CONFIGURATION}"
set -x
case "${CONFIGURATION}" in
  "DevDebug" | "DevRelease" )
    cp "${PROJECT_DIR}/ConfigurationExample/Develop/GoogleService-info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-info.plist"
    ;;
  "ProDebug" | "ProRelease" )
    cp "${PROJECT_DIR}/ConfigurationExample/Production/GoogleService-info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-info.plist"
    ;;
  *)
    ;;
esac
Shell script to copy GoogleService-Info.plist.
Shell script to copy GoogleService-Info.plist.

Finally let’s try to build the project. We can see that when building, the corresponding GoogleService-Info.plist is copied to the build folder.

Copy GoogleService-Info.plist to build folder when building the project.
Copy GoogleService-Info.plist to build folder when building the project.

Conclusion

When developing projects, we often need to have multiple versions of environment settings. Xcode’s configuration and scheme let us do it easily. In addition, it is easier to manage each environment setting in the configuration file.

refer to

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue Tutorial

Grand Central Dispatch (GCD) provides efficient concurrent processing so that we don’t need to directly manage multiple threads. Its dispatch queues can execute tasks serially or concurrently.
Read More