|
| 1 | +# Package Manager Custom Target Layouts |
| 2 | + |
| 3 | +* Proposal: [SE-0162](0162-package-manager-custom-target-layouts.md) |
| 4 | +* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r) |
| 5 | +* Review Manager: [Rick Ballard](https://github.com/rballard) |
| 6 | +* Status: **Active review (April 4...April 10, 2017)** |
| 7 | +* Bug: [SR-29](https://bugs.swift.org/browse/SR-29) |
| 8 | + |
| 9 | +## Introduction |
| 10 | + |
| 11 | +This proposal enhances the `Package.swift` manifest APIs to support custom |
| 12 | +target layouts, and removes a convention which allowed omission of targets from |
| 13 | +the manifest. |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +The Package Manager uses a convention system to infer targets structure from |
| 18 | +disk layout. This works well for most packages, which can easily adopt the |
| 19 | +conventions, and frees users from needing to update their `Package.swift` file |
| 20 | +every time they add or remove sources. Adopting the conventions is more |
| 21 | +difficult for some packages, however – especially existing C libraries or large |
| 22 | +projects, which would be difficult to reorganize. We intend to give users a way |
| 23 | +to make such projects into packages without needing to conform to our |
| 24 | +conventions. |
| 25 | + |
| 26 | +The current convention rules make it very convenient to add new targets and |
| 27 | +source files by inferring them automatically from disk, but they also can be |
| 28 | +confusing, overly-implicit, and difficult to debug; for example, if the user |
| 29 | +does not follow the conventions correctly which determine their targets, they |
| 30 | +may wind up with targets they don't expect, or not having targets they did |
| 31 | +expect, and either way their clients can't easily see which targets are available |
| 32 | +by looking at the `Package.swift` manifest. We want to retain convenience where it |
| 33 | +really matters, such as easy addition of new source files, but require explicit |
| 34 | +declarations where being explicit adds significant value. We also want to make |
| 35 | +sure that the implicit conventions we keep are straightforward and easy to |
| 36 | +remember. |
| 37 | + |
| 38 | +## Proposed solution |
| 39 | + |
| 40 | +* We propose to stop inferring targets from disk. They must be explicitly declared |
| 41 | + in the manifest file. The inference was not very useful, as targets eventually |
| 42 | + need to be declared in order to use common features such as product and target |
| 43 | + dependencies, or build settings (which are planned for Swift 4). Explicit |
| 44 | + target declarations make a package easier to understand by clients, and allow us |
| 45 | + to provide good diagnostics when the layout on disk does not match the |
| 46 | + declarations. |
| 47 | + |
| 48 | +* We propose to remove the requirement that name of a test target must have |
| 49 | + suffix "Tests". Instead, test targets will be explicitly declared as such |
| 50 | + in the manifest file. |
| 51 | + |
| 52 | +* We propose a list of pre-defined search paths for declared targets. |
| 53 | + |
| 54 | + When a target does not declare an explicit path, these directories will be used |
| 55 | + to search for the target. The name of the directory must match the name of |
| 56 | + the target. The search will be done in order and will be case-sensitive. |
| 57 | + |
| 58 | + Regular targets: package root, Sources, Source, src, srcs. |
| 59 | + Test targets: Tests, package root, Sources, Source, src, srcs. |
| 60 | + |
| 61 | + It is an error if a target is found in more than one of these paths. In |
| 62 | + such cases, the path should be explicitly declared using the path property |
| 63 | + proposed below. |
| 64 | + |
| 65 | +* We propose to add a factory method `testTarget` to the `Target` class, to define |
| 66 | + test targets. |
| 67 | + |
| 68 | + ```swift |
| 69 | + .testTarget(name: "FooTests", dependencies: ["Foo"]) |
| 70 | + ``` |
| 71 | + |
| 72 | +* We propose to add three properties to the `Target` class: `path`, `sources` and |
| 73 | + `exclude`. |
| 74 | + |
| 75 | + * `path`: This property defines the path to the top-level directory containing the |
| 76 | + target's sources, relative to the package root. It is not legal for this path |
| 77 | + to escape the package root, i.e., values like "../Foo", "/Foo" are invalid. The |
| 78 | + default value of this property will be `nil`, which means the target will be |
| 79 | + searched for in the pre-defined paths. The empty string ("") or dot (".") implies |
| 80 | + that the target's sources are directly inside the package root. |
| 81 | + |
| 82 | + * `sources`: This property defines the source files to be included in the |
| 83 | + target. The default value of this property will be nil, which means all |
| 84 | + valid source files found in the target's path will be included. This can |
| 85 | + contain directories and individual source files. Directories will be |
| 86 | + searched recursively for valid source files. Paths specified are relative |
| 87 | + to the target path. |
| 88 | + |
| 89 | + Each source file will be represented by String type. In future, we will |
| 90 | + consider upgrading this to its own type to allow per-file build settings. |
| 91 | + The new type would conform to `CustomStringConvertible`, so existing |
| 92 | + declarations would continue to work (except where the strings were |
| 93 | + constructed programatically). |
| 94 | + |
| 95 | + * `exclude`: This property can be used to exclude certain files and |
| 96 | + directories from being picked up as sources. Exclude paths are relative |
| 97 | + to the target path. This property has more precedence than `sources` |
| 98 | + property. |
| 99 | + |
| 100 | + _Note: We plan to support globbing in future, but to keep this proposal short |
| 101 | + we are not proposing it right now._ |
| 102 | + |
| 103 | +* It is an error if the paths of two targets overlap (unless resolved with `exclude`). |
| 104 | + |
| 105 | + ```swift |
| 106 | + // This is an error: |
| 107 | + .target(name: "Bar", path: "Sources/Bar"), |
| 108 | + .testTarget(name: "BarTests", dependencies: ["Bar"], path: "Sources/Bar/Tests"), |
| 109 | + |
| 110 | + // This works: |
| 111 | + .target(name: "Bar", path: "Sources/Bar", exclude: ["Tests"]), |
| 112 | + .testTarget(name: "BarTests", dependencies: ["Bar"], path: "Sources/Bar/Tests"), |
| 113 | + ``` |
| 114 | + |
| 115 | +* For C family library targets, we propose to add a `publicHeadersPath` |
| 116 | + property. |
| 117 | + |
| 118 | + This property defines the path to the directory containing public headers of |
| 119 | + a C target. This path is relative to the target path and default value of |
| 120 | + this property is `include`. This mechanism should be further improved |
| 121 | + in the future, but there are several behaviors, such as modulemap generation, |
| 122 | + which currently depend of having only one public headers directory. We will address |
| 123 | + those issues separately in a future proposal. |
| 124 | + |
| 125 | + _All existing rules related to custom and automatic modulemap remain intact._ |
| 126 | + |
| 127 | +* Remove exclude from `Package` class. |
| 128 | + |
| 129 | + This property is no longer required because of the above proposed |
| 130 | + per-target exclude property. |
| 131 | + |
| 132 | +* The templates provided by the `swift package init` subcommand will be updated |
| 133 | + according to the above rules, so that users do not need to manually |
| 134 | + add their first target to the manifest. |
| 135 | + |
| 136 | +## Examples: |
| 137 | + |
| 138 | +* Dummy manifest containing all Swift code. |
| 139 | + |
| 140 | +```swift |
| 141 | +let package = Package( |
| 142 | + name: "SwiftyJSON", |
| 143 | + targets: [ |
| 144 | + .target( |
| 145 | + name: "Utility", |
| 146 | + path: "Sources/BasicCode" |
| 147 | + ), |
| 148 | + |
| 149 | + .target( |
| 150 | + name: "SwiftyJSON", |
| 151 | + dependencies: ["Utility"], |
| 152 | + path: "SJ", |
| 153 | + sources: ["SwiftyJSON.swift"] |
| 154 | + ), |
| 155 | + |
| 156 | + .testTarget( |
| 157 | + name: "AllTests", |
| 158 | + dependencies: ["Utility", "SwiftyJSON"], |
| 159 | + path: "Tests", |
| 160 | + exclude: ["Fixtures"] |
| 161 | + ), |
| 162 | + ] |
| 163 | +) |
| 164 | +``` |
| 165 | + |
| 166 | +* LibYAML |
| 167 | + |
| 168 | +```swift |
| 169 | +let packages = Package( |
| 170 | + name: "LibYAML", |
| 171 | + targets: [ |
| 172 | + .target( |
| 173 | + name: "libyaml", |
| 174 | + sources: ["src"] |
| 175 | + ) |
| 176 | + ] |
| 177 | +) |
| 178 | +``` |
| 179 | + |
| 180 | +* Node.js http-parser |
| 181 | + |
| 182 | +```swift |
| 183 | +let packages = Package( |
| 184 | + name: "http-parser", |
| 185 | + targets: [ |
| 186 | + .target( |
| 187 | + name: "http-parser", |
| 188 | + publicHeaders: ".", |
| 189 | + sources: ["http_parser.c"] |
| 190 | + ) |
| 191 | + ] |
| 192 | +) |
| 193 | +``` |
| 194 | + |
| 195 | +* swift-build-tool |
| 196 | + |
| 197 | +```swift |
| 198 | +let packages = Package( |
| 199 | + name: "llbuild", |
| 200 | + targets: [ |
| 201 | + .target( |
| 202 | + name: "swift-build-tool", |
| 203 | + path: ".", |
| 204 | + sources: [ |
| 205 | + "lib/Basic", |
| 206 | + "lib/llvm/Support", |
| 207 | + "lib/Core", |
| 208 | + "lib/BuildSystem", |
| 209 | + "products/swift-build-tool/swift-build-tool.cpp", |
| 210 | + ] |
| 211 | + ) |
| 212 | + ] |
| 213 | +) |
| 214 | +``` |
| 215 | + |
| 216 | +## Impact on existing code |
| 217 | + |
| 218 | +These enhancements will be added to the version 4 manifest API, which will |
| 219 | +release with Swift 4. There will be no impact on packages using the version 3 |
| 220 | +manifest API. When packages update their minimum tools version to 4.0, they |
| 221 | +will need to update the manifest according to the changes in this proposal. |
| 222 | + |
| 223 | +There are two flat layouts supported in Swift 3: |
| 224 | + |
| 225 | +1. Source files directly in the package root. |
| 226 | +2. Source files directly inside a `Sources/` directory. |
| 227 | + |
| 228 | +If packages want to continue using either of these flat layouts, they will need |
| 229 | +to explicitly set a target path to the flat directory; otherwise, a directory |
| 230 | +named after the target is expected. For example, if a package `Foo` has |
| 231 | +following layout: |
| 232 | + |
| 233 | +``` |
| 234 | +Package.swift |
| 235 | +Sources/main.swift |
| 236 | +Sources/foo.swift |
| 237 | +``` |
| 238 | + |
| 239 | +The updated manifest will look like this: |
| 240 | + |
| 241 | +```swift |
| 242 | +// swift-tools-version:4.0 |
| 243 | +import PackageDescription |
| 244 | + |
| 245 | +let package = Package( |
| 246 | + name: "Foo", |
| 247 | + targets: [ |
| 248 | + .target(name: "Foo", path: "Sources"), |
| 249 | + ] |
| 250 | +) |
| 251 | +``` |
| 252 | + |
| 253 | +## Alternatives considered |
| 254 | + |
| 255 | +We considered making a more minimal change which disabled the flat layouts |
| 256 | +by default, and provided a top-level property to allow opting back in to them. |
| 257 | +This would allow us to discourage these layouts – which we would like |
| 258 | +to do before the package ecosystem grows – without needing to add a fully |
| 259 | +customizable API. However, we think the fuller API we've proposed here is fairly |
| 260 | +straightforward and provides the ability to make a number of existing projects |
| 261 | +into packages, so we think this is worth doing at this time. |
0 commit comments