Hi all,
I wanted to share a quick pitch on adding some swift package subcommands for making mechanical edits to the Package.swift manifest. I've got this maybe 70% implemented over at https://github.com/apple/swift-package-manager/pull/3034 building on some earlier work by @Aciid, and I think it's at the point now where it makes sense to start discussing the CLI interface and people's use cases. As always, feedback and questions are welcome! Without further ado...
Package Editor Commands
Introduction
Because Swift package manifests are written in Swift using the PackageDescription API, it is difficult to automate common tasks like adding a new product, target, or dependency. This proposal introduces new swift package subcommands to perform some common editing tasks which can streamline users' workflows and enable new higher-level tools.
Motivation
There are a number of reasons someone might want to edit a package using a CLI or library interface instead of editing a manifest by hand:
- In some situations, it's less error-prone than editing the manifest manually. Package authors could provide a one line command in their README to easily integrate the latest version.
- Because more of the process is automated, users would no longer need to remember details of the package layout convention, like which folders they need to create when adding a new library target.
- Using libSwiftPM, IDEs could offer to update the manifest automatically when the user tries to import a missing dependency or create a new target.
- Future features like package collections and package registries could make dependency integration easier for users browsing for packages.
Additionally, many other package managers offer similar features:
- npm's
npm installcommand for adding dependencies to itspackage.json - Tools like cargo-edit for editing the
cargo.tomlformat used by Rust - Elm's
elm installcommand for adding dependencies toelm.json
Proposed solution
This proposal introduces three new swift package subcommands: add-product, add-target, and add-dependency, which edit the manifest of the current package. Together, these encompass many of the most common editing operations performed by users when working on a package.
Detailed design
New Commands
The following subcommands will be added to swift package:
swift package add-product <name> [--type <type>] [--targets <targets>]
name: The name of the new product.
type: executable, library, static-library, or dynamic-library. If unspecified, this will default to library.
targets: A comma separated list of target names to to add to the new product.
swift package add-target <name> [--type <type>] [--no-test-target] [--dependencies <dependencies>]
name: The name of the new target.
type: library, executable or test. If unspecified, this will default to library. The distinction between library and executable targets doesn't impact the manifest currently, but will determine whether or not a main.swift file is created.
--no-test-target: By default, a test target is added for each regular target unless this flag is present.
dependencies: A comma separated list of target dependency names.
In addition to editing the manifest, the add-target command will create the appropriate Sources and Tests subdirectories for new targets.
swift package add-dependency <url> [--exact <version>] [--revision <revision>] [--branch <branch>] [--from <version>] [--up-to-next-minor-from <version>]
url: The URL of the new package dependency. This may also be the path to a local package.
Only one of the following options may appear to specify a package dependency requirement:
--exact : Specifies a .exact(<version>) requirement in the manifest.
--revision : Specifies a .revision(<revision>) requirement in the manifest.
--branch : Specifies a .branch(<branch>) requirement in the manifest.
--from : Specifies a .upToNextMajor(<version>) requirement in the manifest.
--up-to-next-minor-from : Specifies a .upToNextMinor(<version>) requirement in the manifest.
If no requirement is specified, the command will default to a .upToNextMajor requirement on the latest version of the package.
Note: These new commands will be restricted to only operate on package manifests having a swift-tools-version of 5.2 or later. This decision was made to reduce the complexity of the feature, as there were a number of major changes to the PackageDescription module in Swift 5.2. It is expected that in the future, support for editing manifests with older tools versions will be maintained on a best-effort basis.
Examples
Given an initially empty package:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "MyPackage"
)
The following commands can be used to add dependencies, targets, and products:
swift package add-dependency https://github.com/apple/swift-argument-parser
swift package add-target MyLibrary
swift package add-target MyExecutable --no-test-target --dependencies MyLibrary,ArgumentParser
swift package add-product MyLibrary --targets MyLibrary
Resulting in this manifest:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyLibrary",
targets: [
"MyLibrary",
]),
],
dependencies: [
.package(name: "swift-argument-parser", url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "0.3.1")),
],
targets: [
.target(
name: "MyLibrary",
dependencies: []),
.testTarget(
name: "MyLibraryTests",
dependencies: [
"MyLibrary",
]),
.target(
name: "MyExecutable",
dependencies: [
"MyLibrary",
.product(name: "ArgumentParser", package: "https://github.com/apple/swift-argument-parser")
]),
]
)
Security
This proposal has minimal impact on the security of the package manager. Packages added using the add-dependency subcommand will be fetched and their manifests will be loaded, but this is no different than if the user manually edited the manifest to include them.
Impact on Existing Packages
Since this proposal only includes new editing commands, it has no impact on the semantics of existing packages.
Alternatives considered
The only alternative seriously considered was not including this functionality at all. It's worth noting that maintaining this functionality over time and adapting it to changes in the PackageDescription API will require a nontrivial effort. However, the benefits of including the functionality are substantial enought that it seems like a worthwhile tradeoff.
Future directions
Support for Deleting Products/Targets/Dependencies and Renaming Products/Targets
This functionality was considered, but ultimately removed from the scope of the initial proposal. Most of these editing operations appear to be fairly uncommon, so it seems better to wait and see how the new commands are used in practice before rolling out more.
Add a swift package upgrade Command
A hypothetical swift package upgrade command could automatically update the version specifiers of package dependencies to newer versions, similar to npm upgrade. This is another manifest editing operation very commonly provided by other package managers.