Skip to content

Commit 1fbd8e9

Browse files
committed
Improve executable product diagnostics
Improve the diagnostics emitted when an executable product doesn’t have exactly one executable target by being more precise depending on if the number of executable targets is 0, and the user probably needs help on how to make a target executable, or if its more than 1, in that case we don’t need to provide that help.
1 parent d433df8 commit 1fbd8e9

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)