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.shscript from our latest releaseOr using curl: curl -LO https://github.com/brightdigit/PackageDSL/releases/download/latest/package.sh 
- 
Make the script executable: 
chmod +x package.shThe 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.9Generate a minimized Package.swift for a specific package:
./package.sh ~/Projects/MyPackage --version 5.9 --minimizeYou 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()
  }
}For more details check out the documentation on:
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 Packagedirectory 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