Configuring bundle ID and app icon with xcconfigs and tuistSep 12, 2023
What are build settings?
Apart from all the Swift and/or Objective-C code that makes an iOS app function, there’s a separate area of concern that deals with the details of how that particular app is compiled, packaged, and prepared to be run on a device - that area of concern is encapsulated by Xcode build settings. What version of the Swift language to use? What compiler warnings to enable? Where should the compiler search for C/Obj-C header files? These are a few of the types of details you’d configure in build settings.
To change a build setting without any separate tooling on an iOS project, you’d dive into Xcode and change something directly within the IDE, which can be troublesome. Each time you change a build setting within the IDE, you’ll end up producing a diff in the cryptic
.pbxproj file or other related files. These files are prone to git conflicts in a team environment.
As a project grows, you may want to move from having a monolithic app target to having code split up into loosely coupled and separately compiled modules, which only increases the complexity of your build process. Ideally, we have some way of templating the build settings for each new module in order to make that process as repeatable and as automated as possible. Without some extra tooling, this is not an easy task.
I was introduced to tuist at a previous role and for the first time, dealing with build configurations on iOS projects felt a lot less scary.
The core idea is that you describe your project in code, and you dynamically generate those pesky
.xcworkspace files - which can now be added to a
.gitignore. Every detail of how your built product is configured is specified either through a nice Swift DSL, xcconfigs, or some other humanly grokkable format.
I wanted to write a quick post about one simple and practical tuist use case: you’d like to use a different bundle ID and app icon for your debug build and your release build. One reason why these two changes together are useful is because they’ll allow you to have a debug build and a release build installed simultaneously on your device, and be able to distinguish them from one another visually.
Assuming that you’ve already configured a basic project in tuist, you’d start by extracting existing build settings for all your app targets and projects
# Extract target build settings tuist migration settings-to-xcconfig -p Project.xcodeproj -t MyApp -x MyApp.xcconfig # Extract project build settings tuist migration settings-to-xcconfig -p Project.xcodeproj -x MyAppProject.xcconfig
Put these files somewhere that makes sense for your project - for me, it was
Then, I split up shared target build settings into one file named “Base.xcconfig”. “Debug.xcconfig” and “Release.xcconfig” will import the base file using the
In tuist, you’d specify the
xcconfig for the project within the Project initializer:
return Project( name: name, organizationName: "orgname", options: options, packages: packages, settings: Settings.settings( configurations: [ .debug(name: "Debug", xcconfig: .relativeToRoot("Modules/MyProject/Configs/Project.xcconfig")), .release(name: "Release", xcconfig: .relativeToRoot("Modules/MyProject/Configs/Project.xcconfig")), ], defaultSettings: .none ), targets: targets, schemes: [ makeDebugScheme(), makeReleaseScheme(), ] )
Similarly for your target (unrelated configuration omitted):
Target( name: name, platform: platform, product: .app, bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", settings: .settings( configurations: [ .debug(name: "Debug", xcconfig: "Configs/Debug.xcconfig"), .release(name: "Release", xcconfig: "Configs/Release.xcconfig") ], defaultSettings: .none ) )
$(PRODUCT_BUNDLE_IDENTIFIER)? This is a variable defined in your target’s xcconfig file - set this to the appropriate bundleID for that particular target. That should take care of configuring bundle IDs.
For separate app icons, first define a variable in
Then in your Debug.xcconfig:
And in release:
Notice that we don’t need to append a suffix here, since the release AppIcon is titled
And after another call to
tuist generate, we’re done.
Even on a small app,
tuist has been a big QoL increase for me - I don’t enjoy menu diving in an IDE and I’d much rather have my configuration as code. It’s one of those tools that I’ll now always seriously consider reaching for as an early technical decision when starting a new project.