Package Manager Extensible Build Tools

Pardon the confused post above. I'm still parsing this.

My first impression is that while it provides a pathway for extensibility, which is a win, I'm against this particular implementation:

  • the API and the data model feel very complex for what it is doing
  • I don't have a lot of comfort with the idea of putting in one partial-measure now (executable-only tools) and leaving the door open for more extensions (libraries) later. How much thought has gone into library-only tools?

I feel like the two-step approach that was being discussed in the SPM Static dependencies thread has a far smoother user experience.

My take on this proposal is generally negative. I'd favor this above NPM-style shell scripts, but not:

  • custom loaders that allow alternate handlers for differing file patterns
  • a two step process as described in the linked thread above
  • local dependencies allowing authors to define and share their own build/test libraries instead of using SwiftPM as a build tool for larger, non-Package projects
  • a clearer separation between SwiftPM the package spec and resolver, and SwiftTools, an interface for building Swift code -- which would help open the door to consistency and flexibility, while not making the package spec so complex

Edit: This is a complex problem and a complex domain, so I don't mean to sound dismissive when I express concern over the package spec's complexity. My concern is that locking into an API (and a general sub-system) with this much "concreteness" feels like it might cause a rigidity problem down the line -- so I'm offering a few solutions which feel like steps backwards and at least create a few potential lines of discussion around several potential approaches to this solution. But I admit that I'm biased towards preservation of a very clean package spec.

Part of me feels like the last bullet point above is worth expanding upon, is this the right thread for that? Limiting what packages can do w/r/t their own builds (and passing that build configuration on to the package consumer) feels like it gives flexibility while also providing a sane, usable default experience for the majority of packages. If Package.swift describes dependencies and, say, "build.swift" describes your app and how to build it, you could also expose an API for building external packages:

// build.swift -- SwiftTools version specified by Package spec
// or defaulted to 'system' version
import SwiftTools
// enable a ProtobufTools plugin, also specified in Package spec
import ProtobufTools

let MyLib = SwiftTools.library(name: "MyLib", dependencies: [
  .package("abc/xyz") 
  // where "abc" is a package and "xyz" is a product
  // with deps defined in respective Package.swift files
])

// local dependencies by reference
let MyApp = SwiftTools.executable(name: "MyApp", dependencies: [ MyLib ])

// defines `swift build MyLib`, `swift build MyApp`, `swift run MyApp`
SwiftTools.run([MyLib, MyApp])

// defines a task to compile protobuffer definitions
// ProtobufTools exposes options in pure Swift
// it's responsible for translating potential CLI calls, not SwiftPM
SwiftTools.task("build-protobuffers", ProtobufTools.build("Sources/MyApp/*.pb"))

// defines a task called run-my-app, `swift run run-my-app`
SwiftTools.task("run-my-app", MyApp.run)

Again just trying to think of ways to give some degree of flexibility to this system that keeps the package spec really simple. I know this is a wild departure from the thread at hand, but I'm concerned about lock-in to an API that might be better served by a slightly different approach, so at least it's worth discussing.

This model has a bit more boilerplate but the mental model is way simpler and leads to a lot more flexibility. You can basically define a whole middleware chain around builds and tests, expose Foundation, LibC, XCTest and other system libraries from SwiftTools, and most build complexity is opt-in.