Skip to content

Commit 1d3ca7a

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` (swiftlang#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 1d3ca7a

File tree

3 files changed

+123
-21
lines changed

3 files changed

+123
-21
lines changed

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 >= ToolsVersion(version: .init(5, 9, 0)) {
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: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -998,23 +998,133 @@ 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")
1008+
1009+
let manifest = Manifest.createRootManifest(
1010+
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")
10061030

10071031
let manifest = Manifest.createRootManifest(
10081032
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+
// Multiple targets: Sources are expected in their respective subdirectories
1064+
// under Sources
1065+
let fs = InMemoryFileSystem(emptyFiles:
1066+
"/Foo.swift")
1067+
1068+
let manifest = Manifest.createRootManifest(
1069+
displayName: "pkg",
1070+
toolsVersion: .v5_9,
1071+
targets: [
1072+
try TargetDescription(name: "TargetA"),
1073+
try TargetDescription(name: "TargetB"),
1074+
]
1075+
)
1076+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1077+
diagnostics.check(diagnostic: .contains("Source files for target TargetA should be located under 'Sources/TargetA'" ), severity: .error)
1078+
}
1079+
}
1080+
}
1081+
1082+
func testStrictSourceLocation() throws {
1083+
do {
1084+
for fs in [
1085+
InMemoryFileSystem(emptyFiles:
1086+
"/Sources/Foo.swift"),
1087+
InMemoryFileSystem(emptyFiles:
1088+
"/Stray.swift"),
1089+
InMemoryFileSystem(emptyFiles:
1090+
"/Stray.swift",
1091+
"/Sources/Random.swift"),
1092+
] {
1093+
let manifest = Manifest.createRootManifest(
1094+
displayName: "pkg",
1095+
targets: [
1096+
try TargetDescription(name: "Random"),
1097+
]
1098+
)
1099+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1100+
diagnostics.check(diagnostic: .contains("Source files for target Random should be located under 'Sources/Random'"), severity: .error)
1101+
}
1102+
}
1103+
}
1104+
1105+
do {
1106+
// Multiple targets: Sources are expected in their respective subdirectories
1107+
// under Sources
1108+
let fs = InMemoryFileSystem(emptyFiles:
1109+
"/Foo.swift")
1110+
1111+
let manifest = Manifest.createRootManifest(
1112+
displayName: "pkg",
1113+
toolsVersion: .v5_9,
1114+
targets: [
1115+
try TargetDescription(name: "TargetA"),
1116+
try TargetDescription(name: "TargetB"),
1117+
]
1118+
)
1119+
PackageBuilderTester(manifest, in: fs) { _, diagnostics in
1120+
diagnostics.check(diagnostic: .contains("Source files for target TargetA should be located under 'Sources/TargetA'" ), severity: .error)
1121+
}
1122+
}
1123+
1124+
}
1125+
1126+
func testManifestTargetDeclErrors() throws {
1127+
10181128
do {
10191129
let fs = InMemoryFileSystem(emptyFiles:
10201130
"/src/pkg/Foo.swift")
@@ -1060,21 +1170,6 @@ class PackageBuilderTests: XCTestCase {
10601170
}
10611171
}
10621172

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-
10781173
do {
10791174
let fs = InMemoryFileSystem()
10801175
// Binary target.

0 commit comments

Comments
 (0)