Sharing code across Package manifests

We have a codebase with many local Swift Packages in a single repository. In order to better keep dependency URLs and versions synced across all of the Package.swift files, it would be beneficial if we could do something like this:

// global file
extension Package.Dependency {
  static let nuke = .package(name: "Nuke", url: ..., .upToNextMinor(...))
  static let apollo = ...
}
// Package.swift A
let package = Package(..., dependencies: [.nuke, .apollo])
// Package.swift B
let package = Package(..., dependencies: [.apollo])

is this possible in any way?

1 Like

Hey @davepaul0 , did you find any way to do this?

@mukesh-mt I did not.

I'm not working on the same project anymore, but in hindsight, I probably could have used a code gen tool like Sorcery ( or just scripted some sed commands!) to get our package definitions in a single place, but in a large team with various levels of experience, I'm not sure it'd be worth the additional level of obscurity. :man_shrugging:

I am also interested in any ideas on how to share common package code.

Our current use case is this: We'd like to gradually adopt Swift feature flags across all our packages on our road to Swift 6. Right now, each package manifest has this extension:

extension [SwiftSettings] {
  static var upcomingFeatures: Self {
    [
      .enableUpcomingFeature("Feature1"),
      .enableUpcomingFeature("Feature2"),
    ]
  }
}

…so we can at least unify these across targets within one package:

  …
  .target(
    name: "MyTarget,
    swiftSettings: .upcomingFeatures
  )
  …

But it would be much nicer to define this in one global place and use it across packages (which are all in the same repository and also Xcode project/workspace).

A very similar wish would be to have the same platforms value across all our packages, and ideally even tools version, although that's probably even tricker, given it's a special kind of comment instead of Swift code.

1 Like

fwiw, my experience lead me to just copy the common code.

I believe it will be years before SPM supports shared code in Package.swift, so for non-trivial packages I end up copying common code to the end of the file and writing scripts to synchronize the copies (essentially an #include). It's ugly but I find that I'm constantly adding features to the common code, making me glad I made the jump.

The structure I use is something like

import PackageDescription

let package = P.makePackage()

/// Keep declarations used in package outside of global scope
private enum P {
  static func makePackage() -> Package {
    ... // use PackageSpec here
 }
}

// MARK: - tag for copied common code...
private struct PackageSpec {
  ...
}

Features I'm using:

  • SwiftSettings, esp for 6
  • factories for different target and dependency flavors
  • switch remote dependencies to local
  • share common package authors (me, apple, vapor...)
  • platform sets (latest, last 2 years, etc.)
  • easy declarations for multi-product packages, dependency lists, version ranges
  • flags, e.g., linker support for info.plist
  • support for SPM versions (<5.9, 5.9..<6.0, 6.0+)
  • above all: no more duplicate string literals :)

I decided not to do code generation because the upgrade/migration time varies a lot; forcing all to migrate in lock-step was a productivity drag.

1 Like