How to access xcconfig variables from XCUITest code - environment-variables

I need to access an environment variable named ENV that is set in the xcconfig file for my main app target, but from inside my UI test target.
After doing a ton of googling this is what I've tried:
set the main xcconfig as the configuration for the UITest target
add an environment variable ENV=$(ENV) in the test plan configuration
print the environment dictionary like this
print(ProcessInfo.processInfo.environment)
But $(ENV) is not expanding to the real value.
I also tried adding a custom entry in the Info.plist for the UITest target
<key>ENV</key>
<string>$(ENV)</string>
And then accessing it via Bundle.main.infoDictionary but again I just get the string $(ENV). Any idea what I am missing, or is this just impossible because of the way UI tests work in Xcode?

What you have to do is to either use the app target's xcconfig as the build config for your test target. Or if you need to have specific test target's build config, you include the app's xcconfig into your test target xcconfig.
Then in your test plan settings you define your test plan environment var as ENV=$(ENV)
Basically:
apptarget.xcconfig:
ENV=appenv
testtarget.xcconfig:
#include apptarget.xcconfig
in testPlan environment you define:
ENV=$(ENV)
XCConfig is used during build time. The $(ENV) has to be expanded in build time to the value you want. You will accomplish that by either using the apptarget.xcconfig as the xcconfig of your test arget or including it into your testarget.xcconfig

Related

Multiple Configuration Files Per Target

In Xcode I am able to assign a single xcconfig file to my project and targets:
Is there any way to assign more than one xcconfig file to a target? In the dropdown it only allows a single selection.
I understand that I am able to override build settings from my project configuration file in my target configuration file, I'm up against a problem where I may need to have separate configuration for environments (dev, QA, prod) and separate configurations for different "flavours" of the app so being able to have multiple configuration files per target seems like a potential solution.
Configuration files support the #include statement so you could build your higher-level files from one-or-more lower-level files.

Set build variable in scheme instead of target

I am currently working on an iOS project in XCode that uses configurations to specify the different API environments my app can connect to. Additionally, I use targets to override one of my project configuration's user-defined values to specify a particular configuration file to use in the app. However, this is the only value that changes in the target. I work with multiple different configuration files (maybe 10 to 20 at a time) and creating a new target for each file to update one value seems clunky.
My question: Is there a way to pass this one value in from the scheme instead of setting it in the target?
I have seen that there is a pre-build script that can be run but I have not yet had any success exporting environment variables.
CONFIG_FILE="My Config File"
export CONFIG_FILE
I have also seen that some people have had success using PlistBuddy to write the values into the info.plist file during the pre-build phase of the scheme. This may be an option as well although it would require I redo a lot of my build process. I wanted to see if there was any other options before heading down this path.
Thanks for the help.
I was able to do this by using a .xcconfig file that is updated during my pre-build action in my scheme.
I used this tutorial to learn how to set up the project: http://www.jontolof.com/cocoa/using-xcconfig-files-for-you-xcode-project/

How do Xcode's ":configuration" conditional compilation directives work in an xcconfig file?

When copying a target's build settings and pasting them into an xcconfig file as plain text, one finds the output sectioned off by conditions for each build configuration, e.g.:
//:configuration = Debug
SETTING = value
SETTING = value
SETTING = value
//:configuration = Release
SETTING = value
SETTING = value
SETTING = value
//:completeSettings = some
SETTING
SETTING
SETTING
Some questions about how the ":configuration" directives are used:
If I specify a setting above all of them in the same file, will it apply to all build configurations?
I assume they apply to a group of settings below them, but do they apply to settings following empty lines?
Short answer:
The comments are neither conditions, nor directives. They are just comments, used to clarify what build configuration the entries that follow apply to. When you copy the complete build settings of a target, the resulting text is not meant to be used in a single xcconfig file. Rather, it can be used to extract the elements you need for different config files. The comments make it easy to find the settings you need for a particular build configuration.
So, if you assign an xcconfig file to the Debug build configuration of a target, you can copy the settings under //:configuration = Debug to that file, and then make any changes there, as needed.
Long answer:
You can assign exactly one xcconfig file per build configuration per target.
(You do this in the project's Info editor, under Configurations, where you'll find a list of configurations, where each configuration can be expanded to show a list comprising of the project and its targets. You can assign one config file to each of these by choosing from a list of config files. The listed config files consists of all the config files you added to the project.)
The settings listed in an assigned xcconfig file are used in the construction of the build settings for the target and build configuration combination it was assigned to, and they will be reflected in the Build Settings editor in Xcode. The build system uses a hierarchy of configuration settings that is used to construct the settings that will finally be applied.
*(You can see this reflected in the Xcode Build settings editor Select All and Levels at the top of the Build Settings editor to everything.) The build system starts out with defaults for the platform (e.g. iOS Default). These can be changed by settings at the project level, and then by settings at the target level. The Build Settings editor finally lists the Resolved settings, so that you can see exactly which settings will be applied.)*
Settings are applied in the following order, where each subsequent entry will override the previous setting:
Default -> Project Config File -> Project -> Target Config File -> Target.
So, if you specify, say, a Valid Architecture in a config file assigned to the project for a certain build configuration, then that setting will override the default setting. However, if, in Xcode's build settings editor you specify another value for that setting at the project level, then that value will override the value specified in the xcconfig file. Next, if there is an entry in an xcconfig file for the target for that build configuration, then that value will be applied, unless of course, it is overridden by a value for that setting at the target level in the Build Settings Editor. (Confused yet? :-)
As I wrote, only a single xcconfig file can be applied per target/build-configuration combination. Fortunately xcconfig files can import other xcconfig files so that groups of settings can be combined into logical units and then imported into the final xcconfig files, that are to be assigned to a target/build-configuration. If you want to learn more about the contents of xcconfig files take a look here:
The Unofficial Guide to xcconfig files

iOS Settings Bundle with items that appear only in Debug builds

I have user setting that I want to have available in settings, but only if it is a debug build, or perhaps it is one of two separate targets.
In the settings bundle, I have the item in Root.plist, but I want it to only be visible if it is a debug build.
Is there any way to make something conditional in the settings bundle?
I know that I can use separate settings bundles for two different build targets, but that would require me to maintain two separate settings bundles, and that seems pretty messy.
Any ideas?
You could have a Settings.plist with everything in it, then add a build step for the target where you don't want to expose one of the settings. In that build step, use PlistBuddy to remove the setting you don't want.
This has the advantage of letting you use the great tools Xcode provides for editing your plist, while remaining DRY.
As far as I'm aware, there is no way to put conditionals (or, for that matter, any form of code) in the settings bundle. You could create an in-app settings page that is only displayed in debug mode or something of the sort though.
Edit your schema to run a script as one of the build pre-actions. The script can generate Root.plist and and include the extra settings or not as appropriate. Although you could use whatever language you want for your generator script (Ruby, Tcl, Forth,...), I would just use the C Pre-processor.
Specifically,
Create a file, Root.plist.orig which includes all the regular settings. Include your extra settings wrapped in #ifdef DEBUG and #endif
Create a build run-script pre-action in your schema. The script should consist of a shell command to run the pre-processor. So something like (I don't have the details 100% correct): cpp -DDEBUG Root.plist.orig > Root.plist
In you application delegate, read the plist and put into a dictionary. Then use a compile time flag for the DEBUG build, which turns on some code that adds or modifies the plist turned dictionary. From then on don't access the plist directly, interface to the dictionary through a method in the app delegate.
EDIT: write some command line level software to take your standard plist, and based on the build configuration, change it, then output it back as a plist that then is copied into your app bundle, as a build phase. You may be able to do this with standard system cli functions, or if you do it in a custom mac cli program, its a simple thing to construct.

Xcode / iOS: Unit Tests, Schemes, and Configurations

My iOS project has five schemes: Local Development, Integration, QA, Demo, and Production. Each scheme uses a differing configuration to control things like network poll frequency, API endpoints, analytics, and so on.
Similarly, we have five corresponding targets: Local Development, Integration, QA, Demo, and Production. Each target has several User-Defined Build Settings, which contain API keys, numeric values for timing, etc.
Our application's Info.plist file uses application variables such as ${SOME_ENDPOINT_URL} to draw in the corresponding User-Defined Build Settings.
To retrieve the variables, I do something like the following:
[[[NSBundle mainBundle] infoDictionary] valueForKey:#"Some Endpoint URL"]
That would correspond to the User-Defined Build Setting, like this:
"Some Endpoint URL" = ${SOME_ENDPOINT_URL}
I am now looking at how to configure the project appropriately to perform unit and logic tests.
To build out the tests to determine if the environments are configured correctly, I'm not certain what the best practice is.
Is it correct to build out five additional Test-specific targets for each environment?
Or is it better to override the "Run action's arguments" setting for the test component for each scheme, and provide something like an argument to specify which scheme we are looking at?
Are there any existing references for configuring unit and logic tests for iOS projects with multiple environments? This project's complexity seems to exceed the scope of most documentation.
The following are what I do.
Info.plist
Create a master Info.plist file
Write a run script (Shell Script) for each scheme to generate a environment-specific Info.plist by modifying settings in the master Info.plist (Use PlistBuddy -c)
Add the run script to Build Phases (above "Compile Sources")
.h file
Define configuration settings in a .h file (e.g. config.h)
#if defined (CONFIG_FILE)
#import CONFIG_FILE
#endif
Import config.h in your code
Use pre-processor macros for each scheme to select the target .h file.
-DCONFIG_FILE=local-env-config.h

Resources