Resolves https://github.com/tuist/tuist/issues/160
Short description 📝
Supporting more than one configuration is pretty common amongst projects, so this is something we should naturally support. The discussion around the approach can be found on #160.
I really wanted to capture a declarative way to describe configuration so that you really do not care about the underlying implementation. You say what you want and that's what you get.
There were some challenges to deal with:
- Every project in the workspace has to have the same build configurations.
- Dependencies probably won't define the same configurations as the root project, they may only care about
Debug
and Release
.
- Dependencies may care about the configurations defined by the root project but want to provide overrides specific to them.
- Targets may specify configuration overrides in the form of more build settings, or more xcconfig files.
I managed to solve 1, 2, 3 and part of 4.
In this pull request I do not implement the requirement for adding additional xcconfig overrides at the target level. If this is something you think is worth implementation please open an issue and quote this pull request.
Solution 📦
The root project is important. The root project defines the base set of configurations which will be propagated throughout the rest of the loaded projects.
Each project has the ability to specify an xcconfig file as an override for each configuration.
If your module does not specify the configurations that the root project does by name then it will fall-back and find the configuration which matches the build configuration type (e.g. Debug or Release) and failing that it will not specify anything.
An example root project might look something like the following:
import ProjectDescription
let project = Project(
name: "MyApp",
settings: Settings(configurations: [
.debug(name: "Integration", xcconfig: "Configs/App/Integration.xcconfig"),
.debug(name: "Private Production", xcconfig: "Configs/App/PrivateProduction.xcconfig"),
.debug(name: "E02", xcconfig: "Configs/App/E02.xcconfig"),
.debug(name: "F02", xcconfig: "Configs/App/F02.xcconfig"),
.release(name: "Automation", xcconfig: "Configs/App/Automation.xcconfig"),
.release(name: "Production", xcconfig: "Configs/App/Production.xcconfig"),
]),
targets: [
Target(
name: "App",
platform: .iOS,
product: .app,
bundleId: "com.oliver.app",
infoPlist: "Supporting/Info.plist",
sources: "Sources/**",
dependencies: [
.project(target: "LayoutView", path: "Modules/LayoutView"),
.project(target: "SortedCollection", path: "Modules/SortedCollection"),
]
),
Target(
name: "AppTests",
platform: .iOS,
product: .unitTests,
bundleId: "com.oliver.app-tests",
infoPlist: "Supporting/Tests.plist",
sources: "Tests/**",
dependencies: [
.target(name: "App"),
]
)
]
)
An example module project might look something like the following:
import ProjectDescription
let project = Project(
name: "SortedCollection",
settings: Settings(
base: [ "ProjectBase": "YES" ],
configurations: [
.debug(name: "Debug", settings: [ "Project": "Debug" ], xcconfig: "Configs/Debug.xcconfig"),
.release(name: "Release", settings: [ "Project": "Release" ], xcconfig: "Configs/Release.xcconfig"),
]
),
targets: [
Target(
name: "SortedCollection",
platform: Platform.iOS,
product: Product.framework,
bundleId: "com.bskyb.SortedCollection",
infoPlist: "Info.plist",
sources: "Sources/**",
settings: TargetSettings(
base: [ "ModuleBase": "YES" ],
buildSettings: [
"Debug" : [ "Target": "Debug" ],
"Release" : [ "Target": "Release" ],
]
)
),
Target(
name: "SortedCollectionTests",
platform: Platform.iOS,
product: Product.unitTests,
bundleId: "com.bskyb.SortedCollectionTests",
infoPlist: "Tests.plist",
sources: "Tests/**",
dependencies: [
TargetDependency.target(name: "SortedCollection"),
],
settings: TargetSettings(
base: [ "TestBase": "YES" ],
buildSettings: [
"Debug" : [ "Target": "Debug" ],
"Release" : [ "Target": "Release" ],
]
)
),
]
)
In the above instance, if tuist generate
is run on the root project then SortedCollection
will inherit the build configurations Integration
, Private Production
... and for each of those configurations it will apply the appropriate settings from it's own configuration. e.g. Integration
will be assigned the Debug
configuration.
This is currently a work in progress, so any feedback on the implementation and design is greatly appreciated.
We need to determine if the pattern described above is worth going for, or if there are any alternatives worth searching.