Skip to content

Commit 8516092

Browse files
committed
[PackageLoading] Diagnose broken symlink sources
<rdar://problem/34346476> swiftpm ignores missing source symlinks
1 parent 0fa92b1 commit 8516092

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

Sources/PackageLoading/Diagnostics.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,17 @@ public enum PackageBuilderDiagnostics {
125125

126126
let product: String
127127
}
128+
129+
struct BorkenSymlinkDiagnostic: DiagnosticData {
130+
static let id = DiagnosticID(
131+
type: BorkenSymlinkDiagnostic.self,
132+
name: "org.swift.diags.pkg-builder.borken-symlink",
133+
defaultBehavior: .warning,
134+
description: {
135+
$0 <<< "ignoring broken symlink" <<< { $0.path }
136+
}
137+
)
138+
139+
let path: String
140+
}
128141
}

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ public final class PackageBuilder {
238238
)
239239
}
240240

241+
private func diagnosticLocation() -> DiagnosticLocation {
242+
return PackageLocation.Local(name: manifest.name, packagePath: packagePath)
243+
}
244+
241245
/// Computes the special directory where targets are present or should be placed in future.
242246
private func findTargetSpecialDirs(_ targets: [Target]) -> (targetDir: String, testTargetDir: String) {
243247
let predefinedDirs = findPredefinedTargetDirectory()
@@ -277,8 +281,19 @@ public final class PackageBuilder {
277281
// Ignore linux main.
278282
if basename == SwiftTarget.linuxMainBasename { return false }
279283

280-
// Ignore symlinks to non-files.
281-
if !fileSystem.isFile(path) { return false }
284+
// Ignore paths which are not valid files.
285+
if !fileSystem.isFile(path) {
286+
287+
// Diagnose broken symlinks.
288+
if fileSystem.isSymlink(path) {
289+
diagnostics.emit(
290+
data: PackageBuilderDiagnostics.BorkenSymlinkDiagnostic(path: path.asString),
291+
location: diagnosticLocation()
292+
)
293+
}
294+
295+
return false
296+
}
282297

283298
// Ignore manifest files.
284299
if path.parentDirectory == packagePath {
@@ -336,7 +351,7 @@ public final class PackageBuilder {
336351
if !targets.isEmpty {
337352
diagnostics.emit(
338353
data: PackageBuilderDiagnostics.SystemPackageDeclaresTargetsDiagnostic(targets: targets.map({ $0.name })),
339-
location: PackageLocation.Local(name: manifest.name, packagePath: packagePath)
354+
location: diagnosticLocation()
340355
)
341356
}
342357

@@ -346,7 +361,7 @@ public final class PackageBuilder {
346361
case .v4_2:
347362
diagnostics.emit(
348363
data: PackageBuilderDiagnostics.SystemPackageDeprecatedDiagnostic(),
349-
location: PackageLocation.Local(name: manifest.name, packagePath: packagePath)
364+
location: diagnosticLocation()
350365
)
351366
}
352367

@@ -786,7 +801,7 @@ public final class PackageBuilder {
786801
if !inserted {
787802
diagnostics.emit(
788803
data: PackageBuilderDiagnostics.DuplicateProduct(product: product),
789-
location: PackageLocation.Local(name: manifest.name, packagePath: packagePath)
804+
location: diagnosticLocation()
790805
)
791806
}
792807
}
@@ -877,7 +892,7 @@ public final class PackageBuilder {
877892
if product.type != .library(.automatic) || targets.count != 1 {
878893
diagnostics.emit(
879894
data: PackageBuilderDiagnostics.SystemPackageProductValidationDiagnostic(product: product.name),
880-
location: PackageLocation.Local(name: manifest.name, packagePath: packagePath)
895+
location: diagnosticLocation()
881896
)
882897
continue
883898
}
@@ -892,7 +907,7 @@ public final class PackageBuilder {
892907
if executableTargets.count != 1 {
893908
diagnostics.emit(
894909
data: PackageBuilderDiagnostics.InvalidExecutableProductDecl(product: product.name),
895-
location: PackageLocation.Local(name: manifest.name, packagePath: packagePath)
910+
location: diagnosticLocation()
896911
)
897912
continue
898913
}

Tests/PackageLoadingTests/PackageBuilderTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import XCTest
12+
import TestSupport
1213

1314
import Basic
1415
import PackageModel
@@ -54,6 +55,35 @@ class PackageBuilderTests: XCTestCase {
5455
}
5556
}
5657

58+
func testBrokenSymlink() throws {
59+
mktmpdir { path in
60+
let fs = localFileSystem
61+
62+
let sources = path.appending(components: "Sources", "foo")
63+
try fs.createDirectory(sources, recursive: true)
64+
try fs.writeFileContents(sources.appending(components: "foo.swift"), bytes: "")
65+
66+
// Create a stray symlink in sources.
67+
let linkDestPath = path.appending(components: "link.swift")
68+
let linkPath = sources.appending(components: "link.swift")
69+
try fs.writeFileContents(linkDestPath, bytes: "")
70+
try createSymlink(linkPath, pointingAt: linkDestPath)
71+
try fs.removeFileTree(linkDestPath)
72+
73+
let manifest = Manifest.createV4Manifest(
74+
name: "pkg",
75+
targets: [
76+
TargetDescription(name: "foo"),
77+
]
78+
)
79+
80+
PackageBuilderTester(manifest, path: path, in: fs) { result in
81+
result.checkDiagnostic("ignoring broken symlink \(linkPath.asString)")
82+
result.checkModule("foo")
83+
}
84+
}
85+
}
86+
5787
func testCInTests() throws {
5888
let fs = InMemoryFileSystem(emptyFiles:
5989
"/Sources/MyPackage/main.swift",

Tests/PackageLoadingTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extension ModuleMapGeneration {
1414
extension PackageBuilderTests {
1515
static let __allTests = [
1616
("testBadExecutableProductDecl", testBadExecutableProductDecl),
17+
("testBrokenSymlink", testBrokenSymlink),
1718
("testCInTests", testCInTests),
1819
("testCompatibleSwiftVersions", testCompatibleSwiftVersions),
1920
("testCustomTargetDependencies", testCustomTargetDependencies),

0 commit comments

Comments
 (0)