Swift Package Manager DSL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// ✂️ Copy everything below this into other Package.swift files | |
/// to create the DSL capabilities described at | |
/// https://dan.works/hyper-modularization/ | |
/// ------------------------------------------------------------ | |
// MARK: - 🪄 Package Helpers | |
extension String { | |
var dependency: Target.Dependency { | |
Target.Dependency.target(name: self) | |
} | |
var snapshotTests: String { "\(self)SnapshotTests" } | |
var tests: String { "\(self)Tests" } | |
} | |
struct Module { | |
enum ProductType { | |
case library(Product.Library.LibraryType? = nil) | |
} | |
typealias Builder = (inout Self) -> Void | |
static func builder(withDefaults defaults: Module) -> (Builder?) -> Module { | |
{ block in | |
var module = Self( | |
name: "TO BE REPLACED", | |
defaultWith: defaults.defaultWith, | |
swiftSettings: defaults.swiftSettings, | |
plugins: defaults.plugins | |
) | |
block?(&module) | |
return module.merged(with: defaults) | |
} | |
} | |
var name: String | |
var group: String? | |
var dependsOn: [String] | |
let defaultWith: [Target.Dependency] | |
var with: [Target.Dependency] | |
var createProduct: ProductType? | |
var createTarget: Bool | |
var createUnitTests: Bool | |
var unitTestsDependsOn: [String] | |
var unitTestsWith: [Target.Dependency] | |
var createSnapshotTests: Bool | |
var snapshotTestsDependsOn: [String] | |
var resources: [Resource]? | |
var swiftSettings: [SwiftSetting] | |
var plugins: [Target.PluginUsage] | |
var dependencies: [Target.Dependency] { | |
defaultWith + with + dependsOn.map { $0.dependency } | |
} | |
var productTargets: [String] { | |
createTarget ? [name] : dependsOn | |
} | |
init( | |
name: String, | |
group: String? = nil, | |
dependsOn: [String] = [], | |
defaultWith: [Target.Dependency] = [], | |
with: [Target.Dependency] = [], | |
createProduct: ProductType? = nil, | |
createTarget: Bool = true, | |
createUnitTests: Bool = true, | |
unitTestsDependsOn: [String] = [], | |
unitTestsWith: [Target.Dependency] = [], | |
createSnapshotTests: Bool = false, | |
snapshotTestsDependsOn: [String] = [], | |
resources: [Resource]? = nil, | |
swiftSettings: [SwiftSetting] = [], | |
plugins: [Target.PluginUsage] = [] | |
) { | |
self.name = name | |
self.group = group | |
self.dependsOn = dependsOn | |
self.defaultWith = defaultWith | |
self.with = with | |
self.createProduct = createProduct | |
self.createTarget = createTarget | |
self.createUnitTests = createUnitTests | |
self.unitTestsDependsOn = unitTestsDependsOn | |
self.unitTestsWith = unitTestsWith | |
self.createSnapshotTests = createSnapshotTests | |
self.snapshotTestsDependsOn = snapshotTestsDependsOn | |
self.resources = resources | |
self.swiftSettings = swiftSettings | |
self.plugins = plugins | |
} | |
private func merged(with other: Self) -> Self { | |
var copy = self | |
copy.dependsOn = Set(dependsOn).union(other.dependsOn).sorted() | |
copy.unitTestsDependsOn = Set(unitTestsDependsOn).union(other.unitTestsDependsOn).sorted() | |
copy.snapshotTestsDependsOn = Set(snapshotTestsDependsOn).union(other.snapshotTestsDependsOn).sorted() | |
return copy | |
} | |
func group(by newGroup: String) -> Self { | |
var copy = self | |
if let group { | |
copy.group = "\(newGroup)/\(group)" | |
} else { | |
copy.group = newGroup | |
} | |
return copy | |
} | |
} | |
extension Package { | |
func add(module: Module) { | |
// Check should create a product | |
if case let .library(type) = module.createProduct { | |
products.append( | |
.library( | |
name: module.name, | |
type: type, | |
targets: module.productTargets | |
) | |
) | |
} | |
// Check should create a target | |
if module.createTarget { | |
let path = "\(module.group ?? "")/Sources/\(module.name)" | |
targets.append( | |
.target( | |
name: module.name, | |
dependencies: module.dependencies, | |
path: path, | |
resources: module.resources, | |
swiftSettings: module.swiftSettings, | |
plugins: module.plugins | |
) | |
) | |
} | |
// Check should add unit tests | |
if module.createUnitTests { | |
let path = "\(module.group ?? "")/Tests/\(module.name.tests)" | |
targets.append( | |
.testTarget( | |
name: module.name.tests, | |
dependencies: [module.name.dependency] + module.unitTestsDependsOn.map { $0.dependency } + | |
module.unitTestsWith + [.customDump], | |
path: path | |
) | |
) | |
} | |
// Check should add snapshot tests | |
if module.createSnapshotTests { | |
let path = "\(module.group ?? "")/Tests/\(module.name.snapshotTests)" | |
targets.append( | |
.testTarget( | |
name: module.name.snapshotTests, | |
dependencies: [module.name.dependency] + | |
module.snapshotTestsDependsOn.map { $0.dependency } + [ | |
.customDump | |
], | |
path: path | |
) | |
) | |
} | |
} | |
} | |
protocol ModuleGroupConvertible { | |
func makeGroup() -> [Module] | |
} | |
@resultBuilder | |
struct GroupBuilder { | |
static func buildBlock() -> [Module] { [] } | |
static func buildBlock(_ modules: ModuleGroupConvertible...) -> [Module] { | |
modules.flatMap { $0.makeGroup() } | |
} | |
} | |
extension Module: ModuleGroupConvertible { | |
func makeGroup() -> [Module] { [self] } | |
} | |
struct ModuleGroup: ModuleGroupConvertible { | |
var name: String | |
var modules: [Module] | |
init(_ name: String, @GroupBuilder builder: () -> [Module]) { | |
self.name = name | |
self.modules = builder() | |
} | |
func makeGroup() -> [Module] { | |
modules.map { $0.group(by: name) } | |
} | |
} | |
infix operator <> | |
extension String { | |
/// Adds the string as a module to the package, allowing for inline | |
/// customization with no defaults. Handy for base modules like Utilities | |
/// which are then used inside the builders/defaults. | |
static func <> (lhs: String, rhs: Module.Builder) -> Module { | |
var module = Module(name: lhs) | |
rhs(&module) | |
return module | |
} | |
} | |
infix operator <+ | |
extension String { | |
/// Creates a new Module using the lhs as the name | |
static func <+ (lhs: String, rhs: Module) -> Module { | |
var module = rhs | |
module.name = lhs | |
return module | |
} | |
} | |
infix operator <<& | |
extension String { | |
/// Nest modules into groups | |
static func <<& (groupName: String, @ModuleBuilder builder: () -> [Module]) -> ModuleGroup { | |
ModuleGroup(groupName, builder: builder) | |
} | |
} | |
infix operator <& | |
extension String { | |
/// Add groups of modules together under a logical directory | |
static func <& (groupName: String, @ModuleBuilder builder: () -> [Module]) { | |
let modules = ModuleGroup(groupName, builder: builder).makeGroup() | |
for module in modules { | |
package.add(module: module) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment