Skip to content

Commit 6c99d13

Browse files
kcieplakowenv
authored andcommitted
Update logic to find 'linker' executable.
* Update the logic to not use the running platform but rather use the commonly named ld.<ALTERNATE_LINKER> filename. * Add a large comment with the various linker filenames for reference. * Change the tests to locate the given linker first as linkers may or may not be present on host. * Change the linker test properties to not throw but rather return an optional.
1 parent 025ee96 commit 6c99d13

File tree

3 files changed

+91
-55
lines changed

3 files changed

+91
-55
lines changed

Sources/SWBCore/Specs/Tools/LinkerTools.swift

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,17 +1274,44 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
12741274

12751275
override public func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? {
12761276
let alternateLinker = scope.evaluate(BuiltinMacros.ALTERNATE_LINKER)
1277-
12781277
// The ALTERNATE_LINKER is the 'name' of the linker not the executable name, clang will find the linker binary based on name passed via -fuse-ld, but we need to discover
1279-
// its properties by executing the actual binary. On unix based oses the linkers are installed as ld.<name> on windows its <name>.exe
1278+
// its properties by executing the actual binary. There is a common filename when the linker is not "ld" across all platforms using "ld.<ALTERNAME_LINKER>(.exe)"
1279+
// macOS (Xcode SDK)
1280+
// -----------------
1281+
// ld
1282+
// ld-classic
1283+
//
1284+
// macOS (Open Source)
1285+
// -----------
1286+
// ld.lld -> lld
1287+
// ld64.lld -> lld
1288+
// lld
1289+
// lld-link -> lld
1290+
//
1291+
// Linux
1292+
// ------
1293+
// /usr/bin/ld -> aarch64-linux-gnu-ld
1294+
// /usr/bin/ld.bfd -> aarch64-linux-gnu-ld.bfd
1295+
// /usr/bin/ld.gold -> aarch64-linux-gnu-ld.gold
1296+
// /usr/bin/ld.lld -> lld
1297+
// /usr/bin/ld64.lld -> lld
1298+
// /usr/bin/lld
1299+
// /usr/bin/lld-link -> lld
1300+
// /usr/bin/gold -> aarch64-linux-gnu-gold
1301+
//
1302+
// Windows
1303+
// -------
1304+
// ld.lld.exe
1305+
// ld64.lld.exe
1306+
// lld-link.exe
1307+
// lld.exe
1308+
// link.exe //In Visual Studio
1309+
//
1310+
// Note: On Linux you cannot invoke the llvm linker by the direct name for determining the version,
1311+
// you need to use ld.<ALTERNATE_LINKER>
12801312
var linkerPath = Path("ld")
1281-
if alternateLinker != "" {
1282-
linkerPath =
1283-
switch producer.hostOperatingSystem {
1284-
case .linux: Path("ld.\(alternateLinker)")
1285-
case .windows: Path("\(alternateLinker).exe")
1286-
default : Path("\(alternateLinker)")
1287-
}
1313+
if alternateLinker != "" && alternateLinker != "ld" {
1314+
linkerPath = Path(producer.hostOperatingSystem.imageFormat.executableName(basename: "ld.\(alternateLinker)"))
12881315
}
12891316
// Create the cache key. This is just the path to the linker we would invoke if we were invoking the linker directly.
12901317
guard let toolPath = producer.executableSearchPaths.lookup(linkerPath) else {

Sources/SWBTestSupport/CoreBasedTests.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,31 +258,32 @@ extension CoreBasedTests {
258258
}
259259

260260
// Linkers
261-
package var ldPath: Path {
261+
package var ldPath: Path? {
262262
get async throws {
263263
let (core, defaultToolchain) = try await coreAndToolchain()
264-
return try #require(defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld"), "couldn't find ld in default toolchain")
264+
return defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld")
265265
}
266266
}
267-
package var linkPath: Path {
267+
package var linkPath: Path? {
268268
get async throws {
269269
let (core, defaultToolchain) = try await coreAndToolchain()
270-
return try #require(defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "link"), "couldn't find link in default toolchain")
270+
if core.hostOperatingSystem != .windows {
271+
// Most unixes have a link executable, but that is not a linker
272+
return nil
273+
}
274+
return defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "link")
271275
}
272276
}
273-
package var lldPath: Path {
277+
package var lldPath: Path? {
274278
get async throws {
275279
let (core, defaultToolchain) = try await coreAndToolchain()
276-
return try #require(
277-
defaultToolchain.executableSearchPaths.findExecutable(
278-
operatingSystem: core.hostOperatingSystem,
279-
basename: core.hostOperatingSystem == .windows ? "lld-link" : "ld.lld"), "couldn't find ld.ldd in default toolchain")
280+
return defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.lld")
280281
}
281282
}
282-
package var goldPath: Path {
283+
package var goldPath: Path? {
283284
get async throws {
284285
let (core, defaultToolchain) = try await coreAndToolchain()
285-
return try #require(defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold"), "couldn't find ld.gold in default toolchain")
286+
return defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold")
286287
}
287288
}
288289
}

Tests/SWBBuildSystemTests/LinkerTests.swift

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ fileprivate struct LinkerTests: CoreBasedTests {
198198
type: .commandLineTool,
199199
buildPhases: [
200200
TestSourcesBuildPhase(["main.swift"]),
201-
TestFrameworksBuildPhase([TestBuildFile(.target("Library"))])
201+
TestFrameworksBuildPhase([TestBuildFile(.target("Library"))]),
202202
],
203203
dependencies: ["Library"]
204204
),
@@ -216,35 +216,45 @@ fileprivate struct LinkerTests: CoreBasedTests {
216216
let projectDir = tester.workspace.projects[0].sourceRoot
217217
try await tester.fs.writeFileContents(projectDir.join("main.swift")) { stream in
218218
stream <<< """
219-
import Library
219+
import Library
220220
221-
hello()
222-
"""
221+
hello()
222+
"""
223223
}
224224
try await tester.fs.writeFileContents(projectDir.join("library.swift")) { stream in
225225
stream <<< """
226-
public func hello() {
227-
print(\"Hello World\")
228-
}
229-
"""
226+
public func hello() {
227+
print(\"Hello World\")
228+
}
229+
"""
230+
}
231+
232+
// Try to find the installed linkers
233+
let ldLinkerPath = try await self.ldPath
234+
let lldLinkerPath = try await self.lldPath
235+
let goldLinkerPath = try await self.goldPath
236+
var linkLinkerPath = try await self.linkPath
237+
if runDestination == .windows {
238+
// Issue: Finding link.exe will fail until https://github.com/swiftlang/swift-build/pull/163 is merged. Clang will find it via PATH.
239+
linkLinkerPath = Path("link.exe")
230240
}
241+
let installedLinkerPaths = [lldLinkerPath, ldLinkerPath, goldLinkerPath, linkLinkerPath].compactMap { $0 }
231242

232243
// Default Linker
233-
var parameters = BuildParameters(configuration: "Debug")
234-
let defaultLinker = try await runDestination == .windows ? self.lldPath : self.ldPath
244+
var parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": ""])
235245
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
236246
results.checkTask(.matchRuleType("Ld")) { task in
237247
results.checkTaskOutput(task) { taskOutput in
238248
results.checkTaskOutput(task) { output in
239-
// Expect that the default linker is called by clang
240-
#expect(output.asString.contains(anyOf: defaultLinker.str))
249+
// Expect that one of the installed linkers is used, we are not sure which one.
250+
#expect(installedLinkerPaths.map { $0.str }.contains(where: output.asString.contains))
241251
}
242252
}
243253
}
244254
if runDestination == .windows {
245255
// Issue: Linker cannot find dependent library
246256
results.checkError(.contains("Linker command failed with exit code 1"))
247-
results.checkError(.contains("lld-link: error: could not open 'Library.lib'"))
257+
results.checkError(.contains("LNK1181: cannot open input file 'Library.lib'"))
248258
}
249259
results.checkNoDiagnostics()
250260
}
@@ -256,19 +266,17 @@ fileprivate struct LinkerTests: CoreBasedTests {
256266
results.checkError(.contains("Invalid linker name in argument '-fuse-ld=not-a-linker'"))
257267
results.checkError(.contains("invalid linker name in argument '-fuse-ld=not-a-linker'"))
258268
} else {
259-
//Windows 'clang' does not check the linker in passed in via -fuse-ld and simply tries to execute it verbatim.
269+
// Windows 'clang' does not check the linker in passed in via -fuse-ld and simply tries to execute it verbatim.
260270
results.checkError(.contains("Unable to execute command: program not executable"))
261271
results.checkError(.contains("unable to execute command: program not executable"))
262272
results.checkError(.contains("Linker command failed with exit code 1"))
263273
}
264274
results.checkNoDiagnostics()
265275
}
266276

267-
268277
// lld - llvm linker
269-
if runDestination != .macOS {
270-
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": runDestination == .windows ? "lld-link": "lld"])
271-
let linkerPath = try await self.lldPath
278+
if let lldLinkerPath {
279+
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": "lld"])
272280
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
273281
if runDestination == .windows {
274282
// Issue: Linker cannot find dependent library
@@ -277,46 +285,46 @@ fileprivate struct LinkerTests: CoreBasedTests {
277285
}
278286

279287
results.checkTask(.matchRuleType("Ld")) { task in
280-
task.checkCommandLineContains(["-fuse-ld=\(runDestination == .windows ? "lld-link" : "lld")"])
288+
task.checkCommandLineContains(["-fuse-ld=lld"])
281289
results.checkTaskOutput(task) { output in
282-
//Expect that the default linker is called by clang
283-
#expect(output.asString.contains(anyOf: linkerPath.str))
290+
// Expect that the default linker is called by clang
291+
if runDestination == .windows {
292+
// clang will choose to run lld-link rather than ld.lld.exe.
293+
// clang output will have escaped slashes in stdout.
294+
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(lldLinkerPath.dirname.join("lld-link").str))
295+
} else {
296+
#expect(output.asString.contains(lldLinkerPath.str))
297+
}
284298
}
285299
}
286300
results.checkNoDiagnostics()
287301
}
288302
}
289303

290304
// gold
291-
if runDestination == .linux {
305+
if let goldLinkerPath {
292306
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": "gold"])
293-
let linkerPath = try await self.goldPath
294307
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
295308
results.checkTask(.matchRuleType("Ld")) { task in
296-
task.checkCommandLineContains(["-fuse-ld=gold"])
309+
task.checkCommandLineContains(["-fuse-ld=gold"])
297310
results.checkTaskOutput(task) { output in
298-
//Expect that the default linker is called by clang
299-
#expect(output.asString.contains(anyOf: linkerPath.str))
311+
// Expect that the default linker is called by clang
312+
#expect(output.asString.contains(goldLinkerPath.str))
300313
}
301314
}
302315
results.checkNoDiagnostics()
303316
}
304317
}
305318

306319
// link.exe
307-
if runDestination == .windows {
320+
if let linkLinkerPath {
308321
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": "link"])
309-
// Issue: Finding link.exe will fail until https://github.com/swiftlang/swift-build/pull/163 is merged
310-
// clang will find it via PATH.
311-
// linkerPath = try await self.linkPath
312-
let linkerPath = Path("link.exe")
313-
314322
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
315323
results.checkTask(.matchRuleType("Ld")) { task in
316-
task.checkCommandLineContains(["-fuse-ld=link"])
324+
task.checkCommandLineContains(["-fuse-ld=link"])
317325
results.checkTaskOutput(task) { output in
318-
//Expect that the default linker is called by clang
319-
#expect(output.asString.contains(anyOf: linkerPath.str))
326+
// Expect that the default linker is called by clang
327+
#expect(output.asString.contains(linkLinkerPath.str))
320328
}
321329
}
322330
//Issue: Linker cannot find dependent library

0 commit comments

Comments
 (0)