Skip to content

Commit 15360c5

Browse files
committed
Allow sources anywhere in ./Sources when only one target is present
After getting some feedback about the added `path` argument for executable packages generated with `swift package init` (#6144), this change allows a target's sources to occupy the entire sources directory when there is only one target in the package. All package types can benefit from this. When there is more than one target in a package, the existing requirements for target sources still apply. This change should be compatible with existing layouts as well. If there is only a single target in a package, then sources can of course continue to exist in `./Sources/<target>`. Amend the `executable` template's generated manifest to not include the `path` argument anymore. rdar://106829666
1 parent 57d829a commit 15360c5

File tree

4 files changed

+148
-21
lines changed

4 files changed

+148
-21
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ Swift Next
2020
Swift 5.9
2121
-----------
2222

23+
* [#6294]
24+
25+
When a package contains a single target, sources may be distributed anywhere within the `./Sources` directory. If sources are placed in a subdirectory under `./Sources/<target>`, or there is more than one target, the existing expectation for sources apply.
26+
2327
* [#6114]
2428

2529
Added a new `allowNetworkConnections(scope:reason:)` for giving a command plugin permissions to access the network. Permissions can be scoped to Unix domain sockets in general or specifically for Docker, as well as local or remote IP connections which can be limited by port. For non-interactive use cases, there is also a `--allow-network-connections` commandline flag to allow network connections for a particular scope.

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,14 @@ public final class PackageBuilder {
526526
return path
527527
}
528528

529+
// If there is only one target defined, it may be allowed to occupy the
530+
// entire predefined target directory.
531+
if self.manifest.toolsVersion >= .v5_9 {
532+
if self.manifest.targets.count == 1 {
533+
return predefinedTargetDirectory.path
534+
}
535+
}
536+
529537
// Otherwise, if the path "exists" then the case in manifest differs from the case on the file system.
530538
if fileSystem.isDirectory(path) {
531539
self.observabilityScope.emit(.targetNameHasIncorrectCase(target: target.name))

Sources/Workspace/InitPackage.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,7 @@ public final class InitPackage {
257257
if packageType == .executable {
258258
param += """
259259
.executableTarget(
260-
name: "\(pkgname)",
261-
path: "Sources"),
260+
name: "\(pkgname)")
262261
]
263262
"""
264263
} else if packageType == .tool {

Tests/PackageLoadingTests/PackageBuilderTests.swift

Lines changed: 135 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -998,23 +998,154 @@ class PackageBuilderTests: XCTestCase {
998998
}
999999
}
10001000

1001-
func testManifestTargetDeclErrors() throws {
1001+
/// Starting with tools version 5.9, packages are permitted to place
1002+
/// sources anywhere in ./Sources when a package has a single target.
1003+
func testRelaxedSourceLocationSingleTarget() throws {
10021004
do {
1003-
// Reference a target which doesn't exist.
1005+
// Single target: Sources are expected in ./Sources.
10041006
let fs = InMemoryFileSystem(emptyFiles:
1005-
"/Foo.swift")
1007+
"/Sources/Foo.swift")
10061008

10071009
let manifest = Manifest.createRootManifest(
10081010
displayName: "pkg",
1011+
toolsVersion: .v5_9,
1012+
targets: [
1013+
try TargetDescription(name: "Random"),
1014+
]
1015+
)
1016+
PackageBuilderTester(manifest, in: fs) { package, diagnostics in
1017+
package.checkModule("Random") { result in
1018+
XCTAssertEqual("/Sources", result.target.path)
1019+
}
1020+
}
1021+
}
1022+
1023+
do {
1024+
// Single target: Sources are expected in ./Sources.
1025+
// In this case, there is a stray source file at the top-level, and no sources
1026+
// under ./Sources, so the target Random has no sources.
1027+
// This results in a *warning* that there are no sources for the target.
1028+
let fs = InMemoryFileSystem(emptyFiles:
1029+
"/Stray.swift")
1030+
1031+
let manifest = Manifest.createRootManifest(
1032+
displayName: "pkg",
1033+
toolsVersion: .v5_9,
10091034
targets: [
10101035
try TargetDescription(name: "Random"),
10111036
]
10121037
)
10131038
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1014-
diagnostics.check(diagnostic: .contains("Source files for target Random should be located under 'Sources/Random'"), severity: .error)
1039+
diagnostics.check(diagnostic: .contains("Source files for target Random should be located under /Sources"), severity: .warning)
1040+
}
1041+
}
1042+
1043+
do {
1044+
// Single target: Sources are expected in ./Sources. In this case,
1045+
// there is a stray source file at the top-level which is ignored.
1046+
let fs = InMemoryFileSystem(emptyFiles:
1047+
"/Stray.swift",
1048+
"/Sources/Random.swift")
1049+
1050+
let manifest = Manifest.createRootManifest(
1051+
displayName: "pkg",
1052+
toolsVersion: .v5_9,
1053+
targets: [
1054+
try TargetDescription(name: "Random"),
1055+
]
1056+
)
1057+
PackageBuilderTester(manifest, in: fs) { package, diagnostics in
1058+
package.checkModule("Random")
10151059
}
10161060
}
10171061

1062+
do {
1063+
// Single target: Sources can be expected in ./Sources/<target>.
1064+
// If that directory exists, stray sources inside ./Sources will
1065+
// not be included in the target.
1066+
let fs = InMemoryFileSystem(emptyFiles:
1067+
"/Sources/Stray.swift",
1068+
"/Sources/MyTarget/Foo.swift"
1069+
)
1070+
let manifest = Manifest.createRootManifest(
1071+
displayName: "pkg",
1072+
toolsVersion: .v5_9,
1073+
targets: [
1074+
try TargetDescription(name: "MyTarget"),
1075+
]
1076+
)
1077+
PackageBuilderTester(manifest, in: fs) { package, diagnostics in
1078+
package.checkModule("MyTarget") { result in
1079+
result.checkSources(paths: "Foo.swift")
1080+
}
1081+
}
1082+
}
1083+
1084+
do {
1085+
// Multiple targets: Sources are expected in their respective subdirectories
1086+
// under Sources
1087+
let fs = InMemoryFileSystem(emptyFiles:
1088+
"/Foo.swift")
1089+
1090+
let manifest = Manifest.createRootManifest(
1091+
displayName: "pkg",
1092+
toolsVersion: .v5_9,
1093+
targets: [
1094+
try TargetDescription(name: "TargetA"),
1095+
try TargetDescription(name: "TargetB"),
1096+
]
1097+
)
1098+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1099+
diagnostics.check(diagnostic: .contains("Source files for target TargetA should be located under 'Sources/TargetA'" ), severity: .error)
1100+
}
1101+
}
1102+
}
1103+
1104+
func testStrictSourceLocation() throws {
1105+
do {
1106+
for fs in [
1107+
InMemoryFileSystem(emptyFiles:
1108+
"/Sources/Foo.swift"),
1109+
InMemoryFileSystem(emptyFiles:
1110+
"/Stray.swift"),
1111+
InMemoryFileSystem(emptyFiles:
1112+
"/Stray.swift",
1113+
"/Sources/Random.swift"),
1114+
] {
1115+
let manifest = Manifest.createRootManifest(
1116+
displayName: "pkg",
1117+
targets: [
1118+
try TargetDescription(name: "Random"),
1119+
]
1120+
)
1121+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1122+
diagnostics.check(diagnostic: .contains("Source files for target Random should be located under 'Sources/Random'"), severity: .error)
1123+
}
1124+
}
1125+
}
1126+
1127+
do {
1128+
// Multiple targets: Sources are expected in their respective subdirectories
1129+
// under Sources
1130+
let fs = InMemoryFileSystem(emptyFiles:
1131+
"/Foo.swift")
1132+
1133+
let manifest = Manifest.createRootManifest(
1134+
displayName: "pkg",
1135+
toolsVersion: .v5_9,
1136+
targets: [
1137+
try TargetDescription(name: "TargetA"),
1138+
try TargetDescription(name: "TargetB"),
1139+
]
1140+
)
1141+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1142+
diagnostics.check(diagnostic: .contains("Source files for target TargetA should be located under 'Sources/TargetA'" ), severity: .error)
1143+
}
1144+
}
1145+
1146+
}
1147+
1148+
func testManifestTargetDeclErrors() throws {
10181149
do {
10191150
let fs = InMemoryFileSystem(emptyFiles:
10201151
"/src/pkg/Foo.swift")
@@ -1060,21 +1191,6 @@ class PackageBuilderTests: XCTestCase {
10601191
}
10611192
}
10621193

1063-
do {
1064-
let fs = InMemoryFileSystem(emptyFiles:
1065-
"/Source/pkg/Foo.swift")
1066-
// Reference invalid target.
1067-
let manifest = Manifest.createRootManifest(
1068-
displayName: "pkg",
1069-
targets: [
1070-
try TargetDescription(name: "foo"),
1071-
]
1072-
)
1073-
PackageBuilderTester(manifest, in: fs) { _, diagnotics in
1074-
diagnotics.check(diagnostic: .contains("Source files for target foo should be located under 'Sources/foo'"), severity: .error)
1075-
}
1076-
}
1077-
10781194
do {
10791195
let fs = InMemoryFileSystem()
10801196
// Binary target.

0 commit comments

Comments
 (0)