Skip to content

Instantly share code, notes, and snippets.

@leogdion
Created July 21, 2023 20:30
Show Gist options
  • Save leogdion/ad5ca07da41fe470b0f9cb721c22507c to your computer and use it in GitHub Desktop.
Save leogdion/ad5ca07da41fe470b0f9cb721c22507c to your computer and use it in GitHub Desktop.
Swift DSL for Package.swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
// add this to beginning of your Package.swift
import PackageDescription
// MARK: SupportedPlatform
protocol SupportedPlatformable {
var platforms: [SupportedPlatform] { get }
}
protocol SupportedPlatformBody: SupportedPlatformable {
@SupportedPlatformBuilder
var body: [SupportedPlatform] { get }
}
extension SupportedPlatform: SupportedPlatformable {
var platforms: [SupportedPlatform] {
[self]
}
}
extension SupportedPlatformBody {
var platforms: [SupportedPlatform] {
self.body
}
}
@resultBuilder
enum SupportedPlatformBuilder {
static func buildPartialBlock(first: SupportedPlatformable) -> [SupportedPlatform] {
first.platforms
}
static func buildPartialBlock(accumulated: [SupportedPlatform], next: SupportedPlatformable) -> [SupportedPlatform] {
accumulated + next.platforms
}
}
// MARK: Products
protocol Productable {
var products: [Product] { get }
}
enum ProductType {
case library
case executable
}
protocol ProductSpecs: Productable {
var name: String { get }
var targetDependencies: [String] { get }
var type: ProductType { get }
}
extension ProductSpecs {
var name: String {
"\(Self.self)"
}
var targetDependencies: [String] {
[self.name]
}
var type: ProductType {
.library
}
var products: [Product] {
switch self.type {
case .executable:
[Product.executable(name: name, targets: targetDependencies)]
case .library:
[Product.library(name: name, targets: targetDependencies)]
}
}
}
protocol ProductBody: Productable {
@ProductBuilder
var body: [Product] { get }
}
extension Product: Productable {
var products: [Product] {
[self]
}
}
extension ProductBody {
var products: [Product] {
self.body
}
}
@resultBuilder
enum ProductBuilder {
static func buildPartialBlock(first: Productable) -> [Product] {
first.products
}
static func buildPartialBlock(accumulated: [Product], next: Productable) -> [Product] {
accumulated + next.products
}
}
// MARK: Dependencies
protocol PackageDependency {
var packageDependencies: [Package.Dependency] { get }
}
extension Package.Dependency: PackageDependency {
var packageDependencies: [Package.Dependency] {
[self]
}
}
extension PackageDependency {
static func package(url: String, from version: PackageDescription.Version) -> PackageDependency {
Package.Dependency.package(url: url, from: version)
}
}
protocol PackageDependencySpec: PackageDependency {
var dependency: Package.Dependency { get }
}
extension PackageDependencySpec {
var packageDependencies: [Package.Dependency] {
[self.dependency]
}
}
@resultBuilder
enum PackageDependencyBuilder {
static func package(url: String, from version: PackageDescription.Version) -> PackageDependency {
Package.Dependency.package(url: url, from: version)
}
static func buildPartialBlock(first: PackageDependency) -> [Package.Dependency] {
first.packageDependencies
}
static func buildPartialBlock(
accumulated: [Package.Dependency],
next: PackageDependency
) -> [Package.Dependency] {
accumulated + next.packageDependencies
}
}
// MARK: TargetDependencies
protocol TargetDependency {
var targetDependency: [Target.Dependency] { get }
}
extension Target.Dependency: TargetDependency {
var targetDependency: [Target.Dependency] {
[self]
}
}
extension String {
var packageName : String? {
self.split(separator: "/").last?.split(separator: ".").first.map(String.init)
}
}
extension PackageDependencySpec where Self: TargetDependency {
var productName: String {
"\(Self.self)"
}
var targetDependency: [Target.Dependency] {
switch self.dependency.kind {
case let .sourceControl(name: name, location: location, requirement: requirement):
let packageName = name ?? location.packageName
return [
Target.Dependency.product(name: productName, package: packageName)
]
@unknown default:
return [
Target.Dependency.byName(name: productName)
]
}
}
}
@resultBuilder
enum TargetDependencyBuilder {
static func buildPartialBlock(first: TargetDependency) -> [Target.Dependency] {
first.targetDependency
}
static func buildPartialBlock(accumulated: [Target.Dependency], next: TargetDependency) -> [Target.Dependency] {
accumulated + next.targetDependency
}
}
// MARK: Targets
protocol Targetable: TargetDependency {
var targets: [Target] { get }
}
extension Targetable {
var targetDependency: [Target.Dependency] {
targets.map { target in
.target(name: target.name)
}
}
}
protocol TargetSpecs: Targetable {
var targetName: String { get }
@TargetDependencyBuilder
var dependencies: [Target.Dependency] { get }
}
extension TargetSpecs {
var targetName: String {
"\(Self.self)"
}
var dependencies: [Target.Dependency] {
[]
}
var targets: [Target] {
[.target(name: self.targetName, dependencies: self.dependencies)]
}
}
protocol TargetBody: Targetable {
@TargetBuilder
var body: [Target] { get }
}
extension Target: Targetable {
var targets: [Target] {
[self]
}
}
extension TargetBody {
var targets: [Target] {
self.body
}
}
@resultBuilder
enum TargetBuilder {
static func buildPartialBlock(first: Targetable) -> [Target] {
first.targets
}
static func buildPartialBlock(accumulated: [Target], next: Targetable) -> [Target] {
accumulated + next.targets
}
}
// MARK: Package
extension LanguageTag {
static let english: LanguageTag = "en"
}
extension Package {
convenience init(
name: String,
defaultLocalization: LanguageTag,
@SupportedPlatformBuilder platforms: @escaping () -> [SupportedPlatform],
@ProductBuilder products: () -> [Product],
@PackageDependencyBuilder dependencies: () -> [Package.Dependency],
@TargetBuilder targets: () -> [Target]
) {
self.init(
name: name,
defaultLocalization: defaultLocalization,
platforms: platforms(),
products: products(),
dependencies: dependencies(),
targets: targets()
)
}
}
// example code from Bushel:
// MARK: - Bushel
//// MARK: Supported Platforms
//
//struct WWDC2023: SupportedPlatformBody {
// var body: [SupportedPlatform] {
// SupportedPlatform.macOS(.v14)
// SupportedPlatform.iOS(.v17)
// SupportedPlatform.watchOS(.v10)
// SupportedPlatform.tvOS(.v17)
// }
//}
//
//// MARK: Dependencies
//
//struct FelinePine: PackageDependencySpec, TargetDependency {
// var dependency: PackageDescription.Package.Dependency {
// .package(url: "https://github.com/brightdigit/FelinePine.git", from: "0.1.0-alpha.3")
// }
//}
//
//struct ArgumentParser: PackageDependencySpec, TargetDependency {
// var dependency: PackageDescription.Package.Dependency {
// .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0")
// }
//}
//
//struct BushelCore: TargetSpecs {}
//
//struct BushelLocalization: TargetSpecs {}
//
//struct BushelUT: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelCore()
// }
//}
//
//struct BushelLogging: TargetSpecs {
// var dependencies: [Target.Dependency] {
// FelinePine()
// }
//}
//
//struct BushelDataCore: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLogging()
// }
//}
//
//struct BushelLibraryData: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibrary()
// BushelLogging()
// BushelDataCore()
// }
//}
//
//struct BushelLibrary: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLogging()
// BushelCore()
// }
//}
//
//struct BushelLibraryViews: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibrary()
// BushelLibraryData()
// BushelLogging()
// BushelUT()
// BushelViewsCore()
// }
//}
//
//struct BushelMachine: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelCore()
// BushelLogging()
// }
//}
//
//struct BushelMachineData: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelDataCore()
// BushelMachine()
// BushelLogging()
// }
//}
//
//struct BushelMachineViews: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelMachineData()
// BushelLogging()
// BushelUT()
// BushelLocalization()
// BushelViewsCore()
// }
//}
//
//struct BushelViewsCore: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLogging()
// BushelUT()
// }
//}
//
//struct BushelViews: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibraryViews()
// BushelMachineViews()
// BushelSettingsViews()
// }
//}
//
//struct BushelData: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibraryData()
// BushelMachineData()
// }
//}
//
//struct BushelVirtualization: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibraryMacOS()
// BushelMachineMacOS()
// }
//}
//
//
//struct BushelSettingsViews: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelData()
// BushelLocalization()
// }
//}
//
//struct BushelLibraryMacOS: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibrary()
// BushelMacOSCore()
// }
//}
//
//struct BushelMachineMacOS: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelMachine()
// BushelMacOSCore()
// }
//}
//
//struct BushelMacOSCore: TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelCore()
// }
//}
//
//struct BushelArgs: TargetSpecs {
// var dependencies: [Target.Dependency] {
// ArgumentParser()
// }
//}
//
//struct BushelCommand: TargetSpecs, ProductSpecs {
// var targetName: String {
// "bushel"
// }
//
// var dependencies: [Target.Dependency] {
// BushelArgs()
// }
//
// var type: ProductType {
// .executable
// }
//}
//
//struct BushelLibraryApp: ProductSpecs, TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelLibraryViews()
// BushelLibraryMacOS()
// }
//}
//
//
//struct BushelSettingsApp: ProductSpecs, TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelSettingsViews()
// }
//}
//
//
//struct BushelMachineApp: TargetSpecs, ProductSpecs {
// var dependencies: [Target.Dependency] {
// BushelMachineViews()
// BushelMachineMacOS()
// }
//}
//
//
//struct BushelApp: ProductSpecs, TargetSpecs {
// var dependencies: [Target.Dependency] {
// BushelViews()
// BushelVirtualization()
// BushelMachine()
// BushelLibrary()
// BushelData()
// }
//}
//
//
//let package = Package(
// name: "BushelKit",
// defaultLocalization: .english
//) {
// WWDC2023()
//} products: {
// BushelLibraryApp()
// BushelMachineApp()
// BushelSettingsApp()
// BushelApp()
//} dependencies: {
// FelinePine()
// ArgumentParser()
//} targets: {
// BushelCore()
// BushelUT()
// BushelLocalization()
// BushelLogging()
//
// BushelArgs()
// BushelCommand()
//
// BushelDataCore()
// BushelViewsCore()
//
// BushelMacOSCore()
// BushelLibraryMacOS()
// BushelMachineMacOS()
// BushelVirtualization()
//
// BushelLibrary()
// BushelLibraryData()
// BushelLibraryViews()
// BushelLibraryApp()
//
// BushelMachine()
// BushelMachineData()
// BushelMachineViews()
// BushelMachineApp()
//
// BushelSettingsViews()
// BushelSettingsApp()
//
// BushelData()
// BushelViews()
//
// BushelApp()
//}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment