Skip to content

Commit 3c04356

Browse files
committed
[swiftpm] Handle symlinks in the workspace path
Previously we would fail to get settings if the path didn't match exactly. For now, we try first with the given path, then fallback to the realpath (and we always realpath the workspace).
1 parent 4dcc4e7 commit 3c04356

File tree

2 files changed

+141
-15
lines changed

2 files changed

+141
-15
lines changed

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public final class SwiftPMWorkspace {
6767
throw Error.noManifest(workspacePath: workspacePath)
6868
}
6969

70-
self.packageRoot = packageRoot
70+
self.packageRoot = resolveSymlinks(packageRoot)
7171

7272
guard let destinationToolchainBinDir = toolchainRegistry.default?.path?.appending(components: "usr", "bin") else {
7373
throw Error.cannotDetermineHostToolchain
@@ -199,7 +199,7 @@ extension SwiftPMWorkspace: BuildSystem {
199199
return nil
200200
}
201201

202-
if let td = self.fileToTarget[path] {
202+
if let td = targetDescription(for: path) {
203203
return settings(for: path, language, td)
204204
}
205205

@@ -213,6 +213,21 @@ extension SwiftPMWorkspace: BuildSystem {
213213

214214
return nil
215215
}
216+
217+
/// Returns the resolved target description for the given file, if one is known.
218+
func targetDescription(for file: AbsolutePath) -> TargetBuildDescription? {
219+
if let td = fileToTarget[file] {
220+
return td
221+
}
222+
223+
let realpath = resolveSymlinks(file)
224+
if realpath != file, let td = fileToTarget[realpath] {
225+
fileToTarget[file] = td
226+
return td
227+
}
228+
229+
return nil
230+
}
216231
}
217232

218233
extension SwiftPMWorkspace {
@@ -239,26 +254,44 @@ extension SwiftPMWorkspace {
239254

240255
/// Retrieve settings for a package manifest (Package.swift).
241256
func settings(forPackageManifest path: AbsolutePath) -> FileBuildSettings? {
242-
for package in packageGraph.packages where path == package.manifest.path {
257+
func impl(_ path: AbsolutePath) -> FileBuildSettings? {
258+
for package in packageGraph.packages where path == package.manifest.path {
243259
let compilerArgs = workspace.interpreterFlags(for: package.path) + [path.pathString]
244260
return FileBuildSettings(
245261
preferredToolchain: nil,
246262
compilerArguments: compilerArgs
247263
)
264+
}
265+
return nil
248266
}
249-
return nil
267+
268+
if let result = impl(path) {
269+
return result
270+
}
271+
272+
let canonicalPath = resolveSymlinks(path)
273+
return canonicalPath == path ? nil : impl(canonicalPath)
250274
}
251275

252276
/// Retrieve settings for a given header file.
253277
public func settings(forHeader path: AbsolutePath, _ language: Language) -> FileBuildSettings? {
254-
var dir = path.parentDirectory
255-
while !dir.isRoot {
256-
if let td = sourceDirToTarget[dir] {
257-
return settings(for: path, language, td)
278+
func impl(_ path: AbsolutePath) -> FileBuildSettings? {
279+
var dir = path.parentDirectory
280+
while !dir.isRoot {
281+
if let td = sourceDirToTarget[dir] {
282+
return settings(for: path, language, td)
283+
}
284+
dir = dir.parentDirectory
258285
}
259-
dir = dir.parentDirectory
286+
return nil
260287
}
261-
return nil
288+
289+
if let result = impl(path) {
290+
return result
291+
}
292+
293+
let canonicalPath = resolveSymlinks(path)
294+
return canonicalPath == path ? nil : impl(canonicalPath)
262295
}
263296

264297
/// Retrieve settings for the given swift file, which is part of a known target build description.

Tests/SKSwiftPMWorkspaceTests/SwiftPMWorkspaceTests.swift

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ final class SwiftPMWorkspaceTests: XCTestCase {
9090
targets: [.target(name: "lib", dependencies: [])])
9191
"""
9292
])
93-
let packageRoot = tempDir.path.appending(component: "pkg")
93+
let packageRoot = resolveSymlinks(tempDir.path.appending(component: "pkg"))
9494
let tr = ToolchainRegistry.shared
9595
let ws = try! SwiftPMWorkspace(
9696
workspacePath: packageRoot,
@@ -183,7 +183,7 @@ final class SwiftPMWorkspaceTests: XCTestCase {
183183
fileSystem: fs,
184184
buildSetup: TestSourceKitServer.buildSetup)
185185

186-
let source = packageRoot.appending(component: "Package.swift")
186+
let source = resolveSymlinks(packageRoot.appending(component: "Package.swift"))
187187
let arguments = ws.settings(for: source.asURL, .swift)!.compilerArguments
188188

189189
check("-swift-version", "4.2", arguments: arguments)
@@ -204,7 +204,7 @@ final class SwiftPMWorkspaceTests: XCTestCase {
204204
targets: [.target(name: "lib", dependencies: [])])
205205
"""
206206
])
207-
let packageRoot = tempDir.path.appending(component: "pkg")
207+
let packageRoot = resolveSymlinks(tempDir.path.appending(component: "pkg"))
208208
let tr = ToolchainRegistry.shared
209209
let ws = try! SwiftPMWorkspace(
210210
workspacePath: packageRoot,
@@ -243,7 +243,7 @@ final class SwiftPMWorkspaceTests: XCTestCase {
243243
])
244244
"""
245245
])
246-
let packageRoot = tempDir.path.appending(component: "pkg")
246+
let packageRoot = resolveSymlinks(tempDir.path.appending(component: "pkg"))
247247
let tr = ToolchainRegistry.shared
248248
let ws = try! SwiftPMWorkspace(
249249
workspacePath: packageRoot,
@@ -314,7 +314,7 @@ final class SwiftPMWorkspaceTests: XCTestCase {
314314
cxxLanguageStandard: .cxx14)
315315
"""
316316
])
317-
let packageRoot = tempDir.path.appending(component: "pkg")
317+
let packageRoot = resolveSymlinks(tempDir.path.appending(component: "pkg"))
318318
let tr = ToolchainRegistry.shared
319319
let ws = try! SwiftPMWorkspace(
320320
workspacePath: packageRoot,
@@ -393,6 +393,99 @@ final class SwiftPMWorkspaceTests: XCTestCase {
393393
check("-target", Triple.hostTriple.tripleString, arguments: arguments)
394394
#endif
395395
}
396+
397+
func testSymlinkInWorkspaceSwift() {
398+
// FIXME: should be possible to use InMemoryFileSystem.
399+
let fs = localFileSystem
400+
let tempDir = try! TemporaryDirectory(removeTreeOnDeinit: true)
401+
402+
try! fs.createFiles(root: tempDir.path, files: [
403+
"pkg_real/Sources/lib/a.swift": "",
404+
"pkg_real/Package.swift": """
405+
// swift-tools-version:4.2
406+
import PackageDescription
407+
let package = Package(name: "a", products: [], dependencies: [],
408+
targets: [.target(name: "lib", dependencies: [])])
409+
"""
410+
])
411+
let packageRoot = tempDir.path.appending(component: "pkg")
412+
413+
try! FileManager.default.createSymbolicLink(
414+
atPath: packageRoot.pathString,
415+
withDestinationPath: "pkg_real")
416+
417+
let tr = ToolchainRegistry.shared
418+
let ws = try! SwiftPMWorkspace(
419+
workspacePath: packageRoot,
420+
toolchainRegistry: tr,
421+
fileSystem: fs,
422+
buildSetup: TestSourceKitServer.buildSetup)
423+
424+
let aswift1 = packageRoot.appending(components: "Sources", "lib", "a.swift")
425+
let aswift2 = tempDir.path
426+
.appending(component: "pkg_real")
427+
.appending(components: "Sources", "lib", "a.swift")
428+
let manifest = packageRoot.appending(components: "Package.swift")
429+
430+
let arguments1 = ws.settings(for: aswift1.asURL, .swift)?.compilerArguments
431+
let arguments2 = ws.settings(for: aswift2.asURL, .swift)?.compilerArguments
432+
XCTAssertNotNil(arguments1)
433+
XCTAssertNotNil(arguments2)
434+
XCTAssertEqual(arguments1, arguments2)
435+
436+
checkNot(aswift1.pathString, arguments: arguments1 ?? [])
437+
check(resolveSymlinks(aswift1).pathString, arguments: arguments1 ?? [])
438+
439+
let argsManifest = ws.settings(for: manifest.asURL, .swift)?.compilerArguments
440+
XCTAssertNotNil(argsManifest)
441+
442+
checkNot(manifest.pathString, arguments: argsManifest ?? [])
443+
check(resolveSymlinks(manifest).pathString, arguments: argsManifest ?? [])
444+
}
445+
446+
func testSymlinkInWorkspaceCXX() {
447+
// FIXME: should be possible to use InMemoryFileSystem.
448+
let fs = localFileSystem
449+
let tempDir = try! TemporaryDirectory(removeTreeOnDeinit: true)
450+
try! fs.createFiles(root: tempDir.path, files: [
451+
"pkg_real/Sources/lib/a.cpp": "",
452+
"pkg_real/Sources/lib/b.cpp": "",
453+
"pkg_real/Sources/lib/include/a.h": "",
454+
"pkg_real/Package.swift": """
455+
// swift-tools-version:4.2
456+
import PackageDescription
457+
let package = Package(name: "a", products: [], dependencies: [],
458+
targets: [.target(name: "lib", dependencies: [])],
459+
cxxLanguageStandard: .cxx14)
460+
"""
461+
])
462+
463+
let packageRoot = tempDir.path.appending(component: "pkg")
464+
465+
try! FileManager.default.createSymbolicLink(
466+
atPath: packageRoot.pathString,
467+
withDestinationPath: "pkg_real")
468+
469+
let tr = ToolchainRegistry.shared
470+
let ws = try! SwiftPMWorkspace(
471+
workspacePath: packageRoot,
472+
toolchainRegistry: tr,
473+
fileSystem: fs,
474+
buildSetup: TestSourceKitServer.buildSetup)
475+
476+
let acxx = packageRoot.appending(components: "Sources", "lib", "a.cpp")
477+
let ah = packageRoot.appending(components: "Sources", "lib", "include", "a.h")
478+
479+
let argsCxx = ws.settings(for: acxx.asURL, .cpp)?.compilerArguments
480+
XCTAssertNotNil(argsCxx)
481+
check(acxx.pathString, arguments: argsCxx ?? [])
482+
checkNot(resolveSymlinks(acxx).pathString, arguments: argsCxx ?? [])
483+
484+
let argsH = ws.settings(for: ah.asURL, .cpp)?.compilerArguments
485+
XCTAssertNotNil(argsH)
486+
checkNot(ah.pathString, arguments: argsH ?? [])
487+
check(resolveSymlinks(ah).pathString, arguments: argsH ?? [])
488+
}
396489
}
397490

398491
private func checkNot(

0 commit comments

Comments
 (0)