Simplify the management of your Package.swift file with a type-safe, modular DSL:
import PackageDescription
let package = Package(
name: "MyApp",
entries: {
AppTarget()
NetworkingModule()
DatabaseModule()
},
dependencies: {
Alamofire()
SQLite()
},
testTargets: {
AppTests()
NetworkingTests()
},
swiftSettings: {
InternalImportsByDefault()
}
)
.supportedPlatforms {
WWDC2023()
}
.defaultLocalization(.english)
- Modular Organization: Split your package definition across multiple files for better maintainability
- Type Safety: Leverage Swift's type system to catch configuration errors at compile time
- Better Discoverability: Clear directory structure makes it easy to find and modify package components
- Reduced Complexity: Simplified syntax for defining products, targets, and dependencies
- Easy Maintenance: Update individual components without touching the entire Package.swift file
Check out BushelKit, which uses PackageDSL to manage its complex package structure with multiple products and dependencies. Its Package/Sources
directory demonstrates how to organize:
- Multiple product targets
- Nested dependencies
- Platform-specific code
- Test targets
- Documentation targets
Package
└── Sources
├── Dependencies
│ ├── ArgumentParser.swift
│ ├── DocC.swift
│ ├── RadiantKit
│ │ ├── RadiantDocs.swift
│ │ ├── RadiantPaging.swift
│ │ └── RadiantProgress.swift
│ └── RadiantKit.swift
├── Index.swift
├── Platforms
│ └── WWDC2023.swift
├── Products
│ ├── BushelCommand.swift
│ ├── BushelDocs.swift
│ └── ... more products ...
├── Targets
│ ├── BushelArgs.swift
│ └── ... more targets ...
└── Tests
├── BushelFactoryTests.swift
└── ... more tests ...
Create a minimal package structure:
MyPackage/
└── Package/
└── Sources/
├── Products/
│ └── AppTarget.swift
├── Dependencies/
│ └── Alamofire.swift
└── Index.swift
Package/Sources/Products/AppTarget.swift
:
struct AppTarget: Product, Target {
var dependencies: any Dependencies {
Alamofire()
CoreModule()
}
}
Package/Sources/Dependencies/Alamofire.swift
:
struct Alamofire: PackageDependency {
var dependency: Package.Dependency {
.package(
url: "https://github.com/Alamofire/Alamofire.git",
from: "5.8.0"
)
}
}
Package/Sources/Index.swift
:
let package = Package(
entries: {
AppTarget()
}
)
./package.sh . --version 5.9
-
Download the
package.sh
script from our latest releaseOr using curl:
curl -LO https://github.com/brightdigit/PackageDSL/releases/latest/download/package.sh
-
Make the script executable:
chmod +x package.sh
The script accepts the following arguments:
./package.sh [PACKAGE_DIR] [OPTIONS]
PACKAGE_DIR
: Path to your package directory (required)
--version <version>
: Specify Swift tools version (default: 6.0)--minimize
: Minimize the output by removing comments and extra whitespace
Generate Package.swift for the current directory using Swift 5.9:
./package.sh . --version 5.9
Generate a minimized Package.swift for a specific package:
./package.sh ~/Projects/MyPackage --version 5.9 --minimize
You can create a test target with the TestTarget
protocol:
struct BushelCoreTests: TestTarget {
var dependencies: any Dependencies {
BushelCore()
}
}
Then add it to your list of test targets using the testTarget
argument:
let package = Package {
BushelCommand()
BushelLibraryApp()
BushelMachineApp()
BushelSettingsApp()
BushelApp()
}
testTargets: {
BushelCoreTests() // right here
}
Swift settings can be added to all the targets using swiftSettings
argument on the Package
constructor or to a specific target by implementing using the swiftSettings
property:
struct BushelFactory: Target {
var dependencies: any Dependencies {
BushelCore()
BushelMachine()
BushelLibrary()
BushelLogging()
BushelData()
}
var swiftSettings: [SwiftSetting] {
InternalImportsByDefault()
}
}
To add resources simply implement the resources
property on the Target
:
struct BushelLocalization: Target {
var resources : [Resource] {
.process("Styling/Colors/Colors.xcassets")
.process("Styling/Fonts/FontsFiles")
.process("Images/Images.xcassets")
.process("Images/whiteLoading.json")
.process("Images/primaryLoading.json")
}
}
Right now there are two modifier methods to do this. defaultLocalization
which takes in a LanguageTag
and supportedPlatforms
which can take in a list of platforms or a PlatformSet
.
A PlatformSet
is useful if use a to define a set of platforms for a specific year such as:
struct WWDC2023: PlatformSet {
var body: any SupportedPlatforms {
.macOS(.v14)
.iOS(.v17)
.watchOS(.v10)
.tvOS(.v17)
}
}
Rather then define your platforms as:
let package = Package {
...
}
.supportedPlatforms {
.macOS(.v14)
.iOS(.v17)
.watchOS(.v10)
.tvOS(.v17)
}
Each target can implement the platforms
property:
struct MacOnlyTarget: Target {
var platforms: [SupportedPlatform] {
SupportedPlatform.macOS(.v13)
}
}
The DSL is used only at development time to generate your Package.swift
. There's no runtime impact on your package or its users.
- Create the
Package
directory structure - Move each product, target, and dependency into separate files
- Run the generator script to create your new
Package.swift
- Compare the generated file with your original to ensure everything transferred correctly
- joshdholtz for inspiration with DeckUI