sirghi
(Sergiu Todirașcu)
1
We have a project that supports both iOS and tvOS. We have common code and code per platform. To make everything work we are using EXCLUDED_SOURCE_FILE_NAMES and INCLUDED_SOURCE_FILE_NAMES build settings for [sdk=appletv*] and [sdk=iphone*] respectively (4 combinations). Our development flow requires that we expose our code as a local swift package.
Looking at the Package syntax, I found the following drawbacks:
- We have the
sources and exclude properties on Target, but they can't be customized per sdk.
- We're using the build settings such that we first exclude files and then include some of them back. Afaik you can't do that on
Target.
- There are some conditional c, cxx, swift and linker settings inside
Target but the above build settings don't fall in either category.
I guess one workaround could be to have 2 separate targets for the tv and phone platforms.
But question still holds - is there a way to specify generic build settings inside Package.swift? And are the statements of the above drawbacks correct?
technogen
(Gor Gyolchanyan)
2
There are two approaches that I could recommend that would solve your problem, both of which involve forgetting about manual source file includes and excludes.
Solution 1
Use conditional compilation and file naming pattern to compose a multi-platform module that will work equally well regardless of build system limitations:
MyFeature~iOS.swift:
#if os(iOS)
/* ... */
#endif
MyFeature~tvOS.swift:
#if os(tvOS)
/* ... */
#endif
Pros:
- Easy to set up and scale up with support for more platforms.
- Very flexible, can have code that is common between some of the platforms, but not all.
Cons:
- Noticeable boilerplate.
- Large multi-purpose module.
Solution 2
Split the single module into 4 pieces, subdividing their responsibilities, and allowing each piece to be built only when necessary:
Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "my-library",
products: [
.library(
name: "MyLibrary",
targets: [ "MyLibrary" ]
)
],
targets: [
.target(
/* The umbrella module (client code only uses this) */
name: "MyLibrary",
dependencies [
.target(name: "_MyLibraryCore"),
.target(name: "_MyLibraryAspect_iOS", condition: .when(platforms: [.iOS])),
.target(name: "_MyLibraryAspect_tvOS", condition: .when(platforms: [.tvOS])),
]
),
.target(
/* The core module (platform-agnostic functionality) */
name: "_MyLibraryCore"
),
.target(
/* The iOS aspect module (iOS-specific functionality) */
name: "_MyLibraryAspect_iOS",
dependencies: [
.target("_MyLibraryCore")
]
),
.target(
/* The tvOS aspect module (tvOS-specific functionality) */
name: "_MyLibraryAspect_tvOS",
dependencies: [
.target(name: "_MyLibraryCore")
]
)
]
)
Sources/MyLibrary/MyLibrary.swift (the only file in the umbrella module):
@_exported import _MyLibraryCore
#if os(iOS)
@_exported import _MyLibraryAspect_iOS
#endif
#if os(tvOS)
@_exported import _MyLibraryAspect_tvOS
#endif
Pros:
- Smaller, more focused modules.
- No extra boilerplate.
Cons:
- Takes some effort to set up scale up with more platform support.
- Not flexible, every platform-specific chunk of code is isolated.
sirghi
(Sergiu Todirașcu)
3
Thank you @technogen for the detailed solutions. They seem promising. I'll try them out.
1 Like