Skip to content

Simplify the management of your Package.swift file with a type-safe, modular DSL

License

Notifications You must be signed in to change notification settings

brightdigit/PackageDSL

Repository files navigation

PackageDSL

SwiftPM DocC

GitHub Repo stars GitHub GitHub issues GitHub Workflow Status

Twitter Follow Mastodon Follow YouTube Channel Subscribers

GitHub Downloads (specific asset, latest release)

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)

Why PackageDSL?

  • 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

Table of Contents

Real World Example

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 ...

Getting Started

1. Basic Setup

Create a minimal package structure:

MyPackage/
└── Package/
    └── Sources/
        ├── Products/
        │   └── AppTarget.swift
        ├── Dependencies/
        │   └── Alamofire.swift
        └── Index.swift

2. Define Your Components

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()
  }
)

3. Generate Package.swift

./package.sh . --version 5.9

Installation

  1. Download the package.sh script from our latest release

    Or using curl:

    curl -LO https://github.com/brightdigit/PackageDSL/releases/latest/download/package.sh
  2. Make the script executable:

chmod +x package.sh

Usage

The script accepts the following arguments:

./package.sh [PACKAGE_DIR] [OPTIONS]

Arguments

  • PACKAGE_DIR: Path to your package directory (required)

Options

  • --version <version>: Specify Swift tools version (default: 6.0)
  • --minimize: Minimize the output by removing comments and extra whitespace

Examples

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

FAQ

How about test targets?

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
}

How about custom Swift settings?

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()
  }
}

How about adding resources?

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)
}

How do I specify target-specific platforms?

Each target can implement the platforms property:

struct MacOnlyTarget: Target {
  var platforms: [SupportedPlatform] {
    SupportedPlatform.macOS(.v13)
  }
}

What's the performance impact?

The DSL is used only at development time to generate your Package.swift. There's no runtime impact on your package or its users.

How do I migrate an existing package?

  1. Create the Package directory structure
  2. Move each product, target, and dependency into separate files
  3. Run the generator script to create your new Package.swift
  4. Compare the generated file with your original to ensure everything transferred correctly

Thanks