Skip to content

Commit eec7a3f

Browse files
authored
Merge pull request #2644 from hartbit/executable-target-diagnostics
Improve executable product diagnostics
2 parents 075fb99 + 1fbd8e9 commit eec7a3f

File tree

3 files changed

+74
-13
lines changed

3 files changed

+74
-13
lines changed

Sources/PackageLoading/Diagnostics.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,22 @@ extension Diagnostic.Message {
5858
.error("system library product \(product) shouldn't have a type and contain only one target")
5959
}
6060

61-
static func invalidExecutableProductDecl(_ product: String) -> Diagnostic.Message {
62-
.error("executable product '\(product)' should have exactly one executable target")
61+
static func executableProductTargetNotExecutable(product: String, target: String) -> Diagnostic.Message {
62+
.error("""
63+
executable product '\(product)' expects target '\(target)' to be executable; an executable target requires \
64+
a 'main.swift' file
65+
""")
66+
}
67+
68+
static func executableProductWithoutExecutableTarget(product: String) -> Diagnostic.Message {
69+
.error("""
70+
executable product '\(product)' should have one executable target; an executable target requires a \
71+
'main.swift' file
72+
""")
73+
}
74+
75+
static func executableProductWithMoreThanOneExecutableTarget(product: String) -> Diagnostic.Message {
76+
.error("executable product '\(product)' should not have more than one executable target")
6377
}
6478

6579
static var noLibraryTargetsForREPL: Diagnostic.Message {

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,12 +1075,7 @@ public final class PackageBuilder {
10751075
case .library, .test:
10761076
break
10771077
case .executable:
1078-
let executableTargets = targets.filter({ $0.type == .executable })
1079-
if executableTargets.count != 1 {
1080-
diagnostics.emit(
1081-
.invalidExecutableProductDecl(product.name),
1082-
location: diagnosticLocation()
1083-
)
1078+
guard validateExecutableProduct(product, with: targets) else {
10841079
continue
10851080
}
10861081
}
@@ -1109,6 +1104,33 @@ public final class PackageBuilder {
11091104
return products.map({ $0.item })
11101105
}
11111106

1107+
private func validateExecutableProduct(_ product: ProductDescription, with targets: [Target]) -> Bool {
1108+
let executableTargetCount = targets.filter { $0.type == .executable }.count
1109+
guard executableTargetCount == 1 else {
1110+
if executableTargetCount == 0 {
1111+
if let target = targets.spm_only {
1112+
diagnostics.emit(
1113+
.executableProductTargetNotExecutable(product: product.name, target: target.name),
1114+
location: diagnosticLocation()
1115+
)
1116+
} else {
1117+
diagnostics.emit(
1118+
.executableProductWithoutExecutableTarget(product: product.name),
1119+
location: diagnosticLocation()
1120+
)
1121+
}
1122+
} else {
1123+
diagnostics.emit(
1124+
.executableProductWithMoreThanOneExecutableTarget(product: product.name),
1125+
location: diagnosticLocation()
1126+
)
1127+
}
1128+
1129+
return false
1130+
}
1131+
1132+
return true
1133+
}
11121134
}
11131135

11141136
/// We create this structure after scanning the filesystem for potential targets.

Tests/PackageLoadingTests/PackageBuilderTests.swift

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,22 +1452,47 @@ class PackageBuilderTests: XCTestCase {
14521452

14531453
func testBadExecutableProductDecl() {
14541454
let fs = InMemoryFileSystem(emptyFiles:
1455-
"/Sources/foo/foo.swift"
1455+
"/Sources/foo1/main.swift",
1456+
"/Sources/foo2/main.swift",
1457+
"/Sources/FooLib1/lib.swift",
1458+
"/Sources/FooLib2/lib.swift"
14561459
)
14571460

14581461
let manifest = Manifest.createV4Manifest(
14591462
name: "MyPackage",
14601463
products: [
1461-
ProductDescription(name: "foo", type: .executable, targets: ["foo"]),
1464+
ProductDescription(name: "foo1", type: .executable, targets: ["FooLib1"]),
1465+
ProductDescription(name: "foo2", type: .executable, targets: ["FooLib1", "FooLib2"]),
1466+
ProductDescription(name: "foo3", type: .executable, targets: ["foo1", "foo2"]),
14621467
],
14631468
targets: [
1464-
TargetDescription(name: "foo"),
1469+
TargetDescription(name: "foo1"),
1470+
TargetDescription(name: "foo2"),
1471+
TargetDescription(name: "FooLib1"),
1472+
TargetDescription(name: "FooLib2"),
14651473
]
14661474
)
14671475
PackageBuilderTester(manifest, in: fs) { package, diagnostics in
1468-
package.checkModule("foo") { _ in }
1476+
package.checkModule("foo1") { _ in }
1477+
package.checkModule("foo2") { _ in }
1478+
package.checkModule("FooLib1") { _ in }
1479+
package.checkModule("FooLib2") { _ in }
1480+
diagnostics.check(
1481+
diagnostic: """
1482+
executable product 'foo1' expects target 'FooLib1' to be executable; an executable target requires \
1483+
a 'main.swift' file
1484+
""",
1485+
behavior: .error,
1486+
location: "'MyPackage' /")
1487+
diagnostics.check(
1488+
diagnostic: """
1489+
executable product 'foo2' should have one executable target; an executable target requires a \
1490+
'main.swift' file
1491+
""",
1492+
behavior: .error,
1493+
location: "'MyPackage' /")
14691494
diagnostics.check(
1470-
diagnostic: "executable product \'foo\' should have exactly one executable target",
1495+
diagnostic: "executable product 'foo3' should not have more than one executable target",
14711496
behavior: .error,
14721497
location: "'MyPackage' /")
14731498
}

0 commit comments

Comments
 (0)