One different key between multiple Info.plist files per scheme? - ios

My application's Info.plist file has around 20/30 keys inside. An external SDK we're implementing requires its app key to be set in the Info.plist, but requires separate keys for debug, enterprise distribution, and release schemes.
Is there a way I can create conditional additions to the Info.plist without having to maintain three duplicates of the file (and duplicate all of the other keys, which are identical across all targets)?
Basically what I'd like is the base plist exactly as it is now, then additional new -Debug, -Distribution and Release ones, which just contain this new key. What I'm trying to avoid is repetition of all keys, since it will make adding new ones in future a hassle.
Is this possible?

There are a few different things you might try.
Xcode run script executed before app build
This is a script that is automatically executed before you build. Let's say the 3 duplicate plist files are called InfoA.plist, InfoB.plist and InfoC.plist which are all exact duplicate. When you build the project the contents of InfoA.plist are duplicated to InfoB.plist and InfoC.plist. This would require some knowledge of writing a shell script to do that, but the script is fairly simple.
You could duplicate and rename the files in the script. Or you could use command line tools to copy the contents of the main file to the duplicate files. Two command line utilities you can use to modify the contents of plist files are plutil and https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/PlistBuddy.8.html. These links might help: Add files to an Xcode project from a script?,
Modify scheme and add pre-action
Your schemes also allow you to add pre-actions to your build and you can do the same as above, but have it configured as a pre-action instead of a build phase script.
Here are a few links that might help: how to make xcode run a script before dependencies?, https://www.objc.io/issues/6-build-tools/build-process/, Xcode: Running a script before every build that modifies source code directly but you could also google for “xcode run script before build” for more links.

Related

Xcode: How to add key into generated Info.plist file (or merge generated and existing Info.plist)?

I have been using Info.plist in my projects, but recently I noticed that freshly generated new projects don't contain them anymore, and Info.plist is instead generated by Xcode at build time, from Info.plist Values section in target's Build settings.
I like this, as it would allow me to ship two projects (beta and non-beta one) from a same folder, without special build steps modifying the Info.plist for each project.
However, I noticed that:
Many Info.plist keys are missing in Build settings (eg. ITSAppUsesNonExemptEncryption, or NSLocationAlwaysUsageDescription/NSLocationUsageDescription/NSLocationWhenInUseUsageDescription), so I cannot set them from there. There also doesn't appear to be any + sign when hovering, which would let me add another key into that section.
And the way of adding INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; into a .pbxproj file, which I have found documented somewhere, no longer works.
Is there a way of adding additional keys into generated Info.plist? Or, is there a way of having both both Info.plist file in a filesystem, and Info.plist Values section in build settings, and make Xcode merge them during build?
Here is how the section looks like:
So, this was helpful (thank you Andrew!), but the core insight I got from experimenting is:
Xcode doesn't want to let you enter invalid keys in places they don't belong.
Build Settings only have basic properties, anything else belongs into the 'Info' section. Editing it will create a new Info.plist file with only the changed properties, which will then be merged with info.plist keys from build settings (I couldn't find a documentation for build order).
Some settings, like ITSAppUsesNonExemptEncryption for standalone watch-only apps have no place to go.
Apple mostly abandoned standalone watchapps, there is a ton of bugs around them and they don't care.

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/

Copy file to root of Xcode project when building

I am trying to implement Google Analytics (GA) in my iOS apps. I have two different targets that have different tracking-ids for GA. GA requires a GoogleService-Info.plist (cannot be renamed) file to be placed in the root of the app folder structure. This file contains the tracking-id. However, since I have two different targets I need to have two different tracking-ids.
I cannot have two file named to files with the same name with different targets.
So, is there a way to either copy another file into the root in the build process. Have tried this and similar but does not seem to work:
Any suggestions?
The script:
PLIST_FILE="CustomGoogleService-Info.plist"
PLIST_PATH="${PROJECT_DIR}/path/To/Plist/Here/${PLIST_FILE}"
cp "${PLIST_PATH}" "${CONFIGURATION_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/GoogleService-Info.plist"
Instructions:
Add this script to the end of your Build Phases
Change name CustomGoogleService-Info.plist to your own
Change pathToPlistHere to the correct path where CustomGoogleService-Info.plist is located (starting from your project directory)
You should not have GoogleService-Info.plist file in your project - the script will create it for you.
Grab a coffee while it is running
You may be interested in reading Apple's Xcode Build Setting Reference
Alternatively:
You could have 2 files with the name GoogleService-Info.plist, but keep each in separate directory. Then you could add each to the corresponding target. Without any script.
I did correctly all along, however Destination should be set to Wrapper and subpath empty. No need to have them in the target either.
This one explained the Destination options: xcode copy files build phase - what do the destination options mean exactly?

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.

What's the best way to swap Xcode contents for projects intended for many clients?

I've got a relatively large Xcode project that produces a single app. However, I have many clients/customers who require deep customization and branding of said app. These configurations include different graphics, a few different interfaces and implementations, and, perhaps most importantly, .xcconfig files.
My Xcode project has a dedicated group that points to a particular client's customization folder on disk, so by opening the Xcode project and building, you get a build of the single app with the current client's customizations. To switch to another client, I change where that group points to on disk. (I also change and switch-back the xcconfig "Based On" settings in the project's Info pane to reload the full xcconfig inheritance; Simply changing the group containing one or more xcconfig files doesn't reload this!) This has worked great for 100+ clients. It's a little tedious to switch this folder every time you need to build the app for a different client and ensure the xcconfig is correct, but it works.
Now I'm in the process of automating builds via the command line, and running into troubles. The quick and dirty solution to pointing the aforementioned Xcode group at a different customization folder was to copy the ProjectName.xcodeproj/project.pbxproj file to ProjectName.xcodeproj/project-template.pbxproj and put placeholders inside this file that can be grepped and replaced with the name and path of the desired customization folder. Then, temporarily overwrite project.pbxproj with the modified project-template.pbxproj, and build to get the correct app.
As you've probably observed, the project.pbxproj was duplicated and modified, and will therefore get out of sync as developers modify the original and forget to also update the template. And besides, I shouldn't really be messing with pbxproj files in this fashion anyway -- that's Xcode's private stuff.
So, is there a better way to tell Xcode about a folder full of resources, code, and config files perhaps during the Build Phase with a script or environment variable, rather than at the project group level? The most complicated bit seems to be the xcconfig chain, since each client has their own xcconfig file that inherits from the single app's Debug, Development, and Distribution xcconfig files.
Sorry for the long-windedness of this question, but it's a little complicated! Any suggestions would be greatly appreciated!
I think you would way better off using the targets feature in Xcode. Have one project and the resources of every clients in that project.
You can then duplicate the target you already have (right-click on your target, by selecting the project file in Xcode's Project Navigator).
All your targets will be compiled with the same code. You just need to change the resources in Build Phases > Copy Bundle Resources to have different app created for each target. No need to look at Xcode's internal files.
You can even change the code in your source files by adding a preprocessor macro in your build options (something like FIRST_CLIENT=1) and then look for these definition in your file with #if FIRST_CLIENT.
I have a project set-up like this and it works pretty well :

Resources