SE-0318: Package Creation
* Proposal: [SE-0318](0318-package-creation.md) * Author: [Miguel Perez](https://github.com/miggs597) * Review Manager: [Tom Doron](https://github.com/tomerd) * Status: **Returned for revision** * Implementation: [apple/swift-package-manager#3514](https://github.com/apple/swift-package-manager/pull/3514) * Discussion: [Pitch](https://forums.swift.org/t/pitch-new-command-for-package-creation-and-package-templates/47874/18), [Review](https://forums.swift.org/t/se-0318-package-creation/)
Current Behavior
Creating new package
To create a new package users perform the following steps:
$ mkdir MyLib
$ cd MyLib
$ swift package initResulting in the following structure:
.
├── .gitignore
├── Package.swift
├── README.md
├── Sources
│ └── MyLib
│ └── MyLib.swift
└── Tests
└── MyLibTests
└── MyLibTests.swiftNote that the user has to first make the MyLib directory and then navigate into it.
By default, swift package init will use the directory name to define the package name, which can be changed by using the --name option.
By default, swift package init will set the package type to a library , which can be changed by using the —type option.
Transforming an existing directory of sources into a package
To transform an existing directory of sources into a package using swift package init users perform the following steps:
$ cd MySources
$ swift package initResulting in the following structure:
.
├── .gitignore
├── Package.swift
├── README.md
├── Sources
│ └── MySources
│ └── MySources.swift
└── Tests
└── MySourcesTests
└── MySourcesTests.swiftIn this case, SwiftPM will only “fill the gaps”, or in other words only add Source, Tests, and the other files if they are missing.
By default, swift package init will use the directory name to define the package name, which can be changed by using the —name option.
By default, swift package init will set the package type to a library , which can be changed by using the —type option.
Problem Definition
swift package init is a utility to get started quickly, and is especially important to new users. The current behavior as described above can often achieve the opposite given its ambiguity and reliance on prior knowledge. Specifically, the default behavior of swift package init is geared towards transforming existing source directory to packages, while most new users are interested in creating new programs from scratch so they can experiment with the language.
A secondary issue is that swift package init uses a directory structure template which cannot be customized by the users. Given that SwiftPM is fairly flexible about the package’s directory structure, allowing users to define their own directory structure templates could be a good improvement for those that prefer a different default directory structure.
Proposed Solution
The identified problems could be solved by the introduction of a new command swift package create. This new command would live alongside of swift package init.
swift package create would be used to create a new package from scratch.
swift package init would be used to transform pre-existing source directory to a package.
Both commands will gain the capability to use a templating system such that the directory structure used is customizable by the end user.
Detailed Design
New command: `swift package create`
Following, is the behavior of the new command:
$ swift package create MyAppWill create a new package with the following directory structure:
.
├── Package.swift
├── Sources
│ └── MyApp
│ └── MyApp.swift
└── Tests
└── MyAppTests
└── MyAppTests.swiftNote that swift package create makes an executable package by default, which is important for new users trying to get their first Swift program up and running. Such users can immediately run the new package:
$ cd MyApp
$ swift run ## or omit cd, and use swift run --package-path MyApp
[3/3] Linking MyApp
Hello, world!Customizing the package type
The --type option is used to customize the type of package created. Available options include: library, system-module, or executable. For example
$ swift package create MyLib --type libraryWill create a library package with the the following directory structure
.
├── Package.swift
├── Sources
│ └── MyLib
│ └── MyLib.swift
└── Tests
└── MyLibTests
└── MyLibTests.swiftOr, an example of creating a system-module package.
$ swift package create SysMod --type system-moduleWill create a library package with the the following directory structure
.
├── Package.swift
└── module.modulemapUser defined templates
By default, swift package create and swift package init uses the following directory structure:
.
├── Package.swift
├── Sources
│ └── <Module>
│ └── <Module>.swift
└── Tests
└──<Module>Tests
└── <Module>Tests.swiftTo support use cases in which individuals or teams prefer a different directory structure that they can use consistently in their projects, the proposal introduces a new configuration option named “template”.
Templates are defined by adding a directory to SwiftPM’s configuration directory, e.g. ~/.swiftpm/configuration/templates/new-package/<template-name>
The template is a Swift package directory that SwiftPM copies and performs transformations on to create the new package. SwiftPM performs the following steps when creating a package from a template:
- Copy the template directory to the target location.
- Substitute string placeholders with values that are derived from the new package request or context.
- Strip git information from the template location.
For example, given a test template located in ~/.swiftpm/configuration/templates/new-package/test with the directory structure:
.
├── .git
├── .gitignore
├── Package.swift
├── README.md
├── LICENSE.md
└── src
└── MyApp.swiftThe following Package.swift:
import PackageDescription
let package = Package(
name: "___NAME___",
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "___NAME_AS_C99___",
sources: "src",
dependencies: [
.product(name: "NIO", package: "swift-nio"),
.product(name: "Crypto", package: "swift-crypto")
]
),
]
)And the following README.md:
### ___NAME___
This is the ___NAME___ package!Running swift package create HelloWorld --template test
Will result with the following directory structure:
.
├── Package.swift
├── .gitignore
├── README.md
├── LICENSE.md
└── src
└── MyApp.swiftThe following Package.swift:
import PackageDescription
let package = Package(
name: "HelloWorld",
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "HelloWorld",
sources: "src",
dependencies: [
.product(name: "NIO", package: "swift-nio"),
.product(name: "Crypto", package: "swift-crypto")
]
),
]
)And the following README.md:
### HelloWorld
This is the HelloWorld package!When running swift package init with the --name option omitted, the name of the target directory will be used as the package name.
Substitutions
While transforming the template directory into a package, SwiftPM performs string substitutions on all text files, using the following metadata fields:
NAME: The name provided by the user using the--nameflagNAME_AS_C99: The name provided by the user using the--nameflag, transformed to be C99 compliant
Future iterations of this feature will include additional metadata fields that can be used in this context.
Defining the default template
To customize the default template (i.e. when swift package create or swift package init is invoked without the explicit --template argument), users may define a template named “default”, i.e. ~/.swiftpm/configuration/templates/new-package/default
Adding and updating templates
Templates are designed to be shared as git repositories. The following commands will be added to SwiftPM to facilitate adding and updating templates:
swift package add-template <url> [--name <name>]
Performs git clone of the provided URL into ~/.swiftpm/configuration/templates/new-package/, making the template available to use immediately. The optional --name option can be used to set a different name from the one automatically given via the git clone operation.
swift package update-template <name>
Performs a git update on the template found at ~/.swiftpm/configuration/templates/new-package/<name>.
Impact on SwiftPM
When processing swift package create or swift package init, SwiftPM will do the following
- If a template is specified with
--templateoption: try to load the template and use it as described above, exiting with an error if such template was not found or ran into parsing errors. - When no template is specified with
--templateoption:
1. Check if a default template is defined in ~/.swiftpm/configuration/templates/new-package/default. If one is defined, use it as described in #1 above 2. If no default template is defined, construct a default PackageTemplate based on the --type option when provided, or the default type when such is not.
Changes to `swift package init`
swift package init will be slightly updated to reflect it’s focus on transforming existing source directories to packages:
swift package initwill no longer add aREADME.md, and .gitignorefiles by default, reducing its impact on the existing sources directory.- When
swift package initis used in an empty directory, it will create a new package as it does today but emit a diagnostics message encouraging the user to useswift package createin the future, to help transition to the more appropriate command. swift package initwill accept the new--templateoption and apply it as described above.
Security
No impact.
Impact on existing packages
No impact.
Alternatives considered
The main alternative is to modify the behavior of swift package init such that it better caters to the creation of new packages from scratch. The advantage of this alternative is that it maintains the API surface area. The disadvantages are that any changes to make it better for package creation are likely to make it confusing for transforming existing sources to package. More importantly, changes to the existing command may cause impact on users that have automation tied to the current behavior.
For templates, the main alternative is to use a data file (e.g. JSON) that describes how the package should be constructed. This would hone in the implementation as it defines a finite set of capabilities driven by configuration. This was not selected in order to provide a better user experience, and greater flexibility with respect to including other files in a template.
Future Iterations
In order to provide greater flexibility than what copying a Swift package directory can provide, a future version of SwiftPM could allow packages to be created in a procedural manner. SwiftPM could introduce new APIs that provide a toolbox of functionality for creating and configuration various aspects of packages, and could invoke Swift scripts that create new packages using those APIs. Such scripts could make decisions about what content to create based on input options or other external conditions. These APIs would also function when creating a Swift package from scratch, and or transforming existing sources into a Swift Package.