Skip to content

Commit 83076be

Browse files
committed
Merge branch 'custom-targets-layout'
2 parents fac0db0 + 5b873be commit 83076be

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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

Comments
 (0)