|
| 1 | +# Package Creation |
| 2 | + |
| 3 | +* Proposal: [SE-0318](https://github.com/apple/swift-evolution/blob/main/proposal-templates/0318-package-creation.md) |
| 4 | +* Authors: [Miguel Perez](https://github.com/miggs597/) |
| 5 | +* Review Manager: [Tom Doron](https://github.com/tomerd) |
| 6 | +* Status: **Scheduled for review (Jun 8 - June 15 2021)** |
| 7 | +* Implementation: https://github.com/apple/swift-package-manager/pull/3514/ |
| 8 | + |
| 9 | +# Introduction |
| 10 | + |
| 11 | +In order to clearly separate the roles of transforming an existing directory of source files into a Swift package, from creating a new package from scratch we propose adding a new command `swift package create`. `swift package init` will continue to exist as is, but will be updated to focus on the former, while the new `swift package create` will focus on the latter. |
| 12 | + |
| 13 | + |
| 14 | +# Motivation |
| 15 | + |
| 16 | +Currently `swift package init` handle two distinct use cases: |
| 17 | + |
| 18 | +1. Transforming an existing directory with sources into a Swift package |
| 19 | +2. Creating a new package from scratch. |
| 20 | + |
| 21 | +This one-size-fits-all approach can be confusing and counter-productive, especially for users that are focused on the second use case. It assumes prior knowledge about the command behavior, and specifically about the need to create an empty directory upfront and naming the package after the directory name. |
| 22 | + |
| 23 | +We feel that separating the two concerns into separate commands, will allow SwiftPM to have better default behavior which is more aligned with the users expectations. |
| 24 | + |
| 25 | + |
| 26 | +## Current Behavior |
| 27 | + |
| 28 | +### Creating new package |
| 29 | + |
| 30 | +To create a new package users perform the following steps: |
| 31 | + |
| 32 | +```console |
| 33 | +$ mkdir MyLib |
| 34 | +$ cd MyLib |
| 35 | +$ swift package init |
| 36 | +``` |
| 37 | + |
| 38 | +Resulting in the following structure: |
| 39 | + |
| 40 | +```console |
| 41 | +. |
| 42 | +├── .gitignore |
| 43 | +├── Package.swift |
| 44 | +├── README.md |
| 45 | +├── Sources |
| 46 | +│ └── MyLib |
| 47 | +│ └── MyLib.swift |
| 48 | +└── Tests |
| 49 | + └── MyLibTests |
| 50 | + └── MyLibTests.swift |
| 51 | +``` |
| 52 | + |
| 53 | +Note that the user has to first make the `MyLib` directory and then navigate into it. |
| 54 | + |
| 55 | +By default, `swift package init` will use the directory name to define the package name, which can be changed by using the `--name` option. |
| 56 | + |
| 57 | +By default, `swift package init` will set the package type to a library , which can be changed by using the `—type` option. |
| 58 | + |
| 59 | + |
| 60 | +### Transforming an existing directory of sources into a package |
| 61 | + |
| 62 | +To transform an existing directory of sources into a package using `swift package init` users perform the following steps: |
| 63 | + |
| 64 | +```console |
| 65 | +$ cd MySources |
| 66 | +$ swift package init |
| 67 | +``` |
| 68 | + |
| 69 | +Resulting in the following structure: |
| 70 | + |
| 71 | +```console |
| 72 | +. |
| 73 | +├── .gitignore |
| 74 | +├── Package.swift |
| 75 | +├── README.md |
| 76 | +├── Sources |
| 77 | +│ └── MySources |
| 78 | +│ └── MySources.swift |
| 79 | +└── Tests |
| 80 | + └── MySourcesTests |
| 81 | + └── MySourcesTests.swift |
| 82 | +``` |
| 83 | + |
| 84 | +In this case, SwiftPM will only “fill the gaps”, or in other words only add `Source`, `Tests`, and the other files if they are missing. |
| 85 | + |
| 86 | +By default, `swift package init` will use the directory name to define the package name, which can be changed by using the` —name` option. |
| 87 | + |
| 88 | +By default, `swift package init` will set the package type to a library , which can be changed by using the `—type` option. |
| 89 | + |
| 90 | + |
| 91 | +## Problem Definition |
| 92 | + |
| 93 | +`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. |
| 94 | + |
| 95 | +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. |
| 96 | + |
| 97 | + |
| 98 | +# Proposed Solution |
| 99 | + |
| 100 | +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.` |
| 101 | + |
| 102 | + `swift package create` would be used to create a new package from scratch. |
| 103 | + |
| 104 | + `swift package init` would be used to transform pre-existing source directory to a package. |
| 105 | + |
| 106 | +Both commands will gain the capability to use a templating system such that the directory structure used is customizable by the end user. |
| 107 | + |
| 108 | + |
| 109 | +# Detailed Design |
| 110 | + |
| 111 | +## New command: `swift package create` |
| 112 | + |
| 113 | +Following, is the behavior of the new command: |
| 114 | + |
| 115 | +```console |
| 116 | +$ swift package create MyApp |
| 117 | +``` |
| 118 | + |
| 119 | +Will create a new package with the following directory structure: |
| 120 | + |
| 121 | +```console |
| 122 | +. |
| 123 | +├── Package.swift |
| 124 | +├── Sources |
| 125 | +│ └── MyApp |
| 126 | +│ └── MyApp.swift |
| 127 | +└── Tests |
| 128 | + └── MyAppTests |
| 129 | + └── MyAppTests.swift |
| 130 | +``` |
| 131 | + |
| 132 | +Note 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: |
| 133 | + |
| 134 | +```console |
| 135 | +$ cd MyApp |
| 136 | +$ swift run ## or omit cd, and use swift run --package-path MyApp |
| 137 | +[3/3] Linking MyApp |
| 138 | +Hello, world! |
| 139 | +``` |
| 140 | + |
| 141 | + |
| 142 | +### Customizing the package type |
| 143 | + |
| 144 | +The `--type` option is used to customize the type of package created. Available options include: `library`, `system-module`, or `executable`. For example |
| 145 | + |
| 146 | +``` |
| 147 | +$ swift package create MyLib --type library |
| 148 | +``` |
| 149 | + |
| 150 | +Will create a library package with the the following directory structure |
| 151 | + |
| 152 | +```console |
| 153 | +. |
| 154 | +├── Package.swift |
| 155 | +├── Sources |
| 156 | +│ └── MyLib |
| 157 | +│ └── MyLib.swift |
| 158 | +└── Tests |
| 159 | + └── MyLibTests |
| 160 | + └── MyLibTests.swift |
| 161 | +``` |
| 162 | + |
| 163 | +Or, an example of creating a `system-module` package. |
| 164 | + |
| 165 | +```console |
| 166 | +$ swift package create SysMod --type system-module |
| 167 | +``` |
| 168 | + |
| 169 | +Will create a library package with the the following directory structure |
| 170 | + |
| 171 | +```console |
| 172 | +. |
| 173 | +├── Package.swift |
| 174 | +└── module.modulemap |
| 175 | +``` |
| 176 | + |
| 177 | + |
| 178 | +## User defined templates |
| 179 | + |
| 180 | +By default, `swift package create` and `swift package init` uses the following directory structure: |
| 181 | + |
| 182 | +```console |
| 183 | +. |
| 184 | +├── Package.swift |
| 185 | +├── Sources |
| 186 | +│ └── <Module> |
| 187 | +│ └── <Module>.swift |
| 188 | +└── Tests |
| 189 | + └──<Module>Tests |
| 190 | + └── <Module>Tests.swift |
| 191 | +``` |
| 192 | + |
| 193 | +To 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”. |
| 194 | + |
| 195 | +Templates are defined by adding a directory to SwiftPM’s configuration directory, e.g. `~/.swiftpm/configuration/templates/new-package/<template-name>` |
| 196 | + |
| 197 | +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: |
| 198 | + |
| 199 | +1. Copy the template directory to the target location. |
| 200 | +2. Substitute string placeholders with values that are derived from the new package request or context. |
| 201 | +3. Strip git information from the template location. |
| 202 | + |
| 203 | + |
| 204 | +For example, given a `test` template located in `~/.swiftpm/configuration/templates/new-package/test` with the directory structure: |
| 205 | + |
| 206 | +```console |
| 207 | +. |
| 208 | +├── .git |
| 209 | +├── .gitignore |
| 210 | +├── Package.swift |
| 211 | +├── README.md |
| 212 | +├── LICENSE.md |
| 213 | +└── src |
| 214 | + └── MyApp.swift |
| 215 | +``` |
| 216 | + |
| 217 | +The following `Package.swift`: |
| 218 | + |
| 219 | +```swift |
| 220 | +import PackageDescription |
| 221 | + |
| 222 | +let package = Package( |
| 223 | + name: "___NAME___", |
| 224 | + dependencies: [ |
| 225 | + .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), |
| 226 | + .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"), |
| 227 | + ], |
| 228 | + targets: [ |
| 229 | + .executableTarget( |
| 230 | + name: "___NAME_AS_C99___", |
| 231 | + sources: "src", |
| 232 | + dependencies: [ |
| 233 | + .product(name: "NIO", pacakge: "swift-nio"), |
| 234 | + .product(name: "`Crypto`", pacakge: "swift-crypto") |
| 235 | + ] |
| 236 | + ), |
| 237 | + ] |
| 238 | +) |
| 239 | +``` |
| 240 | + |
| 241 | +And the following `README.md`: |
| 242 | + |
| 243 | +```markdown |
| 244 | +### ___NAME___ |
| 245 | + |
| 246 | +This is the ___NAME___ package! |
| 247 | +``` |
| 248 | + |
| 249 | +Running `swift package init --template test --name HelloWorld` |
| 250 | + |
| 251 | +Will result with the following directory structure: |
| 252 | + |
| 253 | +```console |
| 254 | +. |
| 255 | +├── Package.swift |
| 256 | +├── .gitignore |
| 257 | +├── README.md |
| 258 | +├── LICENSE.md |
| 259 | +└── src |
| 260 | + └── MyApp.swift |
| 261 | +``` |
| 262 | + |
| 263 | +The following `Package.swift`: |
| 264 | + |
| 265 | +```swift |
| 266 | +import PackageDescription |
| 267 | + |
| 268 | +let package = Package( |
| 269 | + name: "HelloWorld", |
| 270 | + dependencies: [ |
| 271 | + .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), |
| 272 | + .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"), |
| 273 | + ], |
| 274 | + targets: [ |
| 275 | + .executableTarget( |
| 276 | + name: "HelloWorld", |
| 277 | + sources: "src", |
| 278 | + dependencies: [ |
| 279 | + .product(name: "NIO", pacakge: "swift-nio"), |
| 280 | + .product(name: "Crypto", pacakge: "swift-crypto") |
| 281 | + ] |
| 282 | + ), |
| 283 | + ] |
| 284 | +) |
| 285 | +``` |
| 286 | + |
| 287 | +And the following `README.md`: |
| 288 | + |
| 289 | +```markdown |
| 290 | +### HelloWorld |
| 291 | + |
| 292 | +This is the HelloWorld package! |
| 293 | +``` |
| 294 | + |
| 295 | +When the `--name` option is omitted, the name of the target directory will be used as the package name. |
| 296 | + |
| 297 | +### Substitutions |
| 298 | + |
| 299 | +While transforming the template directory into a package, SwiftPM performs string substitutions on all text files, using the following metadata fields: |
| 300 | + |
| 301 | +1. `___NAME___`: The name provided by the user using the `--name` flag |
| 302 | +2. `___NAME_AS_C99___`: The name provided by the user using the `--name` flag, transformed to be C99 compliant |
| 303 | + |
| 304 | +Future iterations of this feature will include additional metadata fields that can be used in this context. |
| 305 | + |
| 306 | + |
| 307 | +### Defining the default template |
| 308 | + |
| 309 | +To customize the default template (i.e. when `swift package create` is invoked with the explicit `--template `argument), user define a template named “default”, i.e. `~/.swiftpm/configuration/templates/new-package/default` |
| 310 | + |
| 311 | + |
| 312 | +### Adding and updating templates |
| 313 | + |
| 314 | +Templates are designed to be shared as git repositories. The following commands will be added to SwiftPM to facilitate adding and updating templates: |
| 315 | + |
| 316 | +`swift package add-template <url> [--name <name>]` |
| 317 | + |
| 318 | +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. |
| 319 | + |
| 320 | +`swift package update-template <name>` |
| 321 | + |
| 322 | +Performs a `git update` on the template found at `~/.swiftpm/configuration/templates/new-package/<name>`. |
| 323 | + |
| 324 | + |
| 325 | +## Impact on SwiftPM |
| 326 | + |
| 327 | +When processing `swift package create` or `swift package init`, SwiftPM will do the following |
| 328 | + |
| 329 | +1. If a template is specified with `--template` option: 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. |
| 330 | +2. When no template is specified with `--template` option: |
| 331 | + 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 |
| 332 | + 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. |
| 333 | + |
| 334 | + |
| 335 | +## Changes to `swift package init` |
| 336 | + |
| 337 | +`swift package init` will be slightly updated to reflect it’s focus on transforming existing source directories to packages: |
| 338 | + |
| 339 | +1. `swift package init` will no longer add a `README.md`, and .`gitignore` files by default, reducing its impact on the existing sources directory. |
| 340 | +2. When `swift package init` is used in an empty directory, it will create a new package as it does today but emit a diagnostics message encouraging the user to use `swift package create` in the future, to help transition to the more appropriate command. |
| 341 | +3. `swift package init` will accept the new `--template` option and apply it as described above. |
| 342 | + |
| 343 | + |
| 344 | +# Security |
| 345 | + |
| 346 | +No impact. |
| 347 | + |
| 348 | + |
| 349 | +# Impact on existing packages |
| 350 | + |
| 351 | +No impact. |
| 352 | + |
| 353 | + |
| 354 | +# Alternatives considered |
| 355 | + |
| 356 | +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. |
| 357 | + |
| 358 | +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 configuraiton. This was not selected in order to provide a better user experience, and greater flexibility with respect to including other files in a template. |
| 359 | + |
| 360 | +# Future Iterations |
| 361 | + |
| 362 | +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. |
0 commit comments