Skip to content

Commit 64f3fcb

Browse files
authored
Merge pull request #129 from owenv/compat-libs
Link runtime compatibility libs on darwin if needed
2 parents 8718e2c + 81b5a59 commit 64f3fcb

File tree

3 files changed

+228
-78
lines changed

3 files changed

+228
-78
lines changed

Sources/SwiftDriver/Jobs/DarwinToolchain+LinkerSupport.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,9 @@ extension DarwinToolchain {
258258
to: &commandLine,
259259
parsedOptions: &parsedOptions,
260260
sdkPath: sdkPath,
261-
targetTriple: targetTriple
261+
targetTriple: targetTriple,
262+
linkerOutputType: linkerOutputType,
263+
fileSystem: fileSystem
262264
)
263265

264266
// These custom arguments should be right before the object file at the

Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift

Lines changed: 121 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ extension Toolchain {
2424
let resourceDirBase: AbsolutePath
2525
if let resourceDir = parsedOptions.getLastArgument(.resourceDir) {
2626
resourceDirBase = try AbsolutePath(validating: resourceDir.asSingle)
27-
} else if let sdk = parsedOptions.getLastArgument(.sdk),
27+
} else if !triple.isDarwin,
28+
let sdk = parsedOptions.getLastArgument(.sdk),
2829
let sdkPath = try? AbsolutePath(validating: sdk.asSingle) {
2930
resourceDirBase = sdkPath
3031
.appending(components: "usr", "lib",
@@ -120,87 +121,130 @@ extension Toolchain {
120121
// MARK: - Common argument routines
121122

122123
extension DarwinToolchain {
123-
func addArgsToLinkStdlib(
124-
to commandLine: inout [Job.ArgTemplate],
125-
parsedOptions: inout ParsedOptions,
126-
sdkPath: String?,
127-
targetTriple: Triple
128-
) throws {
124+
func getSwiftRuntimeCompatibilityVersion(for targetTriple: Triple) -> (Int, Int)? {
125+
// FIXME: Add arm64e to Triple.swift
126+
if targetTriple.archName == "arm64e" {
127+
return (5, 3)
128+
}
129129

130-
// Link compatibility libraries, if we're deploying back to OSes that
131-
// have an older Swift runtime.
132-
// let sharedResourceDirPath = try computeResourceDirPath(for: targetTriple,
133-
// isShared: true)
134-
// Optional<llvm::VersionTuple> runtimeCompatibilityVersion;
135-
//
136-
// if (context.Args.hasArg(options::OPT_runtime_compatibility_version)) {
137-
// auto value = context.Args.getLastArgValue(
138-
// options::OPT_runtime_compatibility_version);
139-
// if (value.equals("5.0")) {
140-
// runtimeCompatibilityVersion = llvm::VersionTuple(5, 0);
141-
// } else if (value.equals("none")) {
142-
// runtimeCompatibilityVersion = None;
143-
// } else {
144-
// // TODO: diagnose unknown runtime compatibility version?
145-
// }
146-
// } else if (job.getKind() == LinkKind::Executable) {
147-
// runtimeCompatibilityVersion
148-
// = getSwiftRuntimeCompatibilityVersionForTarget(getTriple());
149-
// }
150-
//
151-
// if (runtimeCompatibilityVersion) {
152-
// if (*runtimeCompatibilityVersion <= llvm::VersionTuple(5, 0)) {
153-
// // Swift 5.0 compatibility library
154-
// SmallString<128> BackDeployLib;
155-
// BackDeployLib.append(SharedResourceDirPath);
156-
// llvm::sys::path::append(BackDeployLib, "libswiftCompatibility50.a");
157-
//
158-
// if (llvm::sys::fs::exists(BackDeployLib)) {
159-
// Arguments.push_back("-force_load");
160-
// Arguments.push_back(context.Args.MakeArgString(BackDeployLib));
161-
// }
162-
// }
163-
// }
164-
//
165-
// if (job.getKind() == LinkKind::Executable) {
166-
// if (runtimeCompatibilityVersion)
167-
// if (*runtimeCompatibilityVersion <= llvm::VersionTuple(5, 0)) {
168-
// // Swift 5.0 dynamic replacement compatibility library.
169-
// SmallString<128> BackDeployLib;
170-
// BackDeployLib.append(SharedResourceDirPath);
171-
// llvm::sys::path::append(BackDeployLib,
172-
// "libswiftCompatibilityDynamicReplacements.a");
173-
//
174-
// if (llvm::sys::fs::exists(BackDeployLib)) {
175-
// Arguments.push_back("-force_load");
176-
// Arguments.push_back(context.Args.MakeArgString(BackDeployLib));
177-
// }
178-
// }
179-
// }
130+
if targetTriple.isMacOSX {
131+
let macOSVersion = targetTriple.version(for: .macOS)
132+
switch (macOSVersion.major, macOSVersion.minor, macOSVersion.micro) {
133+
case (10, ...14, _):
134+
return (5, 0)
135+
case (10, ...15, ...3):
136+
return (5, 1)
137+
case (10, ...15, _):
138+
return (5, 2)
139+
default:
140+
break
141+
}
142+
} else if targetTriple.isiOS { // includes tvOS
143+
let iOSVersion = targetTriple.version(for: .iOS(.device))
144+
switch (iOSVersion.major, iOSVersion.minor, iOSVersion.micro) {
145+
case (...12, _, _):
146+
return (5, 0)
147+
case (13, ...3, _):
148+
return (5, 1)
149+
case (13, _, _):
150+
return (5, 2)
151+
default:
152+
break
153+
}
154+
} else if targetTriple.isWatchOS {
155+
let watchOSVersion = targetTriple.version(for: .watchOS(.device))
156+
switch (watchOSVersion.major, watchOSVersion.minor, watchOSVersion.micro) {
157+
case (...5, _, _):
158+
return (5, 0)
159+
case (6, ...1, _):
160+
return (5, 1)
161+
case (6, _, _):
162+
return (5, 2)
163+
default:
164+
break
165+
}
166+
}
167+
return nil
168+
}
180169

181-
// Add the runtime library link path, which is platform-specific and found
182-
// relative to the compiler.
183-
let runtimePaths = try runtimeLibraryPaths(
184-
for: targetTriple,
185-
parsedOptions: &parsedOptions,
186-
sdkPath: sdkPath,
187-
isShared: true
188-
)
189-
for path in runtimePaths {
190-
commandLine.appendFlag(.L)
191-
commandLine.appendPath(path)
170+
func addArgsToLinkStdlib(
171+
to commandLine: inout [Job.ArgTemplate],
172+
parsedOptions: inout ParsedOptions,
173+
sdkPath: String?,
174+
targetTriple: Triple,
175+
linkerOutputType: LinkOutputType,
176+
fileSystem: FileSystem
177+
) throws {
178+
// Link compatibility libraries, if we're deploying back to OSes that
179+
// have an older Swift runtime.
180+
var runtimeCompatibilityVersion: (Int, Int)? = nil
181+
let resourceDirPath = try computeResourceDirPath(for: targetTriple,
182+
parsedOptions: &parsedOptions,
183+
isShared: true)
184+
if let version = parsedOptions.getLastArgument(.runtimeCompatibilityVersion)?.asSingle {
185+
switch version {
186+
case "5.0":
187+
runtimeCompatibilityVersion = (5, 0)
188+
case "5.1":
189+
runtimeCompatibilityVersion = (5, 1)
190+
case "none":
191+
runtimeCompatibilityVersion = nil
192+
default:
193+
// TODO: diagnose unknown runtime compatibility version?
194+
break
192195
}
196+
} else if linkerOutputType == .executable {
197+
runtimeCompatibilityVersion = getSwiftRuntimeCompatibilityVersion(for: targetTriple)
198+
}
193199

194-
let rpaths = StdlibRpathRule(
195-
parsedOptions: &parsedOptions,
196-
targetTriple: targetTriple
197-
)
198-
for path in rpaths.paths(runtimeLibraryPaths: runtimePaths) {
199-
commandLine.appendFlag("-rpath")
200-
commandLine.appendPath(path)
200+
func addArgsForBackDeployLib(_ libName: String) {
201+
let backDeployLibPath = resourceDirPath.appending(component: libName)
202+
if fileSystem.exists(backDeployLibPath) {
203+
commandLine.append(.flag("-force_load"))
204+
commandLine.appendPath(backDeployLibPath)
201205
}
202206
}
203207

208+
if let compatibilityVersion = runtimeCompatibilityVersion {
209+
if compatibilityVersion <= (5, 0) {
210+
// Swift 5.0 compatibility library
211+
addArgsForBackDeployLib("libswiftCompatibility50.a")
212+
}
213+
214+
if compatibilityVersion <= (5, 1) {
215+
// Swift 5.1 compatibility library
216+
addArgsForBackDeployLib("libswiftCompatibility51.a")
217+
}
218+
219+
if linkerOutputType == .executable && compatibilityVersion <= (5, 0) {
220+
// Swift 5.0 dynamic replacement compatibility library.
221+
addArgsForBackDeployLib("libswiftCompatibilityDynamicReplacements.a")
222+
}
223+
}
224+
225+
// Add the runtime library link path, which is platform-specific and found
226+
// relative to the compiler.
227+
let runtimePaths = try runtimeLibraryPaths(
228+
for: targetTriple,
229+
parsedOptions: &parsedOptions,
230+
sdkPath: sdkPath,
231+
isShared: true
232+
)
233+
for path in runtimePaths {
234+
commandLine.appendFlag(.L)
235+
commandLine.appendPath(path)
236+
}
237+
238+
let rpaths = StdlibRpathRule(
239+
parsedOptions: &parsedOptions,
240+
targetTriple: targetTriple
241+
)
242+
for path in rpaths.paths(runtimeLibraryPaths: runtimePaths) {
243+
commandLine.appendFlag("-rpath")
244+
commandLine.appendPath(path)
245+
}
246+
}
247+
204248
/// Represents the rpaths we need to add in order to find the
205249
/// desired standard library at runtime.
206250
fileprivate enum StdlibRpathRule {
@@ -221,7 +265,7 @@ extension DarwinToolchain {
221265
positive: .toolchainStdlibRpath,
222266
negative: .noToolchainStdlibRpath,
223267
default: false
224-
) {
268+
) {
225269
// If the user has explicitly asked for a toolchain stdlib, we should
226270
// provide one using -rpath. This used to be the default behaviour but it
227271
// was considered annoying in at least the SwiftPM scenario (see
@@ -232,7 +276,7 @@ extension DarwinToolchain {
232276
self = .toolchain
233277
}
234278
else if targetTriple.supports(.swiftInTheOS) ||
235-
parsedOptions.hasArgument(.noStdlibRpath) {
279+
parsedOptions.hasArgument(.noStdlibRpath) {
236280
// If targeting an OS with Swift in /usr/lib/swift, the LC_ID_DYLIB
237281
// install_name the stdlib will be an absolute path like
238282
// /usr/lib/swift/libswiftCore.dylib, and we do not need to provide an rpath

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,110 @@ final class SwiftDriverTests: XCTestCase {
903903
#endif
904904
}
905905

906+
func testCompatibilityLibs() throws {
907+
var env = ProcessEnv.vars
908+
env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1"
909+
try withTemporaryDirectory { path in
910+
let path5_0Mac = path.appending(components: "macosx", "libswiftCompatibility50.a")
911+
let path5_1Mac = path.appending(components: "macosx", "libswiftCompatibility51.a")
912+
let pathDynamicReplacementsMac = path.appending(components: "macosx", "libswiftCompatibilityDynamicReplacements.a")
913+
let path5_0iOS = path.appending(components: "iphoneos", "libswiftCompatibility50.a")
914+
let path5_1iOS = path.appending(components: "iphoneos", "libswiftCompatibility51.a")
915+
let pathDynamicReplacementsiOS = path.appending(components: "iphoneos", "libswiftCompatibilityDynamicReplacements.a")
916+
917+
for compatibilityLibPath in [path5_0Mac, path5_1Mac,
918+
pathDynamicReplacementsMac, path5_0iOS,
919+
path5_1iOS, pathDynamicReplacementsiOS] {
920+
try localFileSystem.writeFileContents(compatibilityLibPath) { $0 <<< "Empty" }
921+
}
922+
let commonArgs = ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-resource-dir", path.pathString]
923+
924+
do {
925+
var driver = try Driver(args: commonArgs + ["-target", "x86_64-apple-macosx10.14"], env: env)
926+
let plannedJobs = try driver.planBuild()
927+
928+
XCTAssertEqual(3, plannedJobs.count)
929+
let linkJob = plannedJobs[2]
930+
XCTAssertEqual(linkJob.kind, .link)
931+
let cmd = linkJob.commandLine
932+
933+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0Mac))]))
934+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1Mac))]))
935+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsMac))]))
936+
}
937+
938+
do {
939+
var driver = try Driver(args: commonArgs + ["-target", "x86_64-apple-macosx10.15.1"], env: env)
940+
let plannedJobs = try driver.planBuild()
941+
942+
XCTAssertEqual(3, plannedJobs.count)
943+
let linkJob = plannedJobs[2]
944+
XCTAssertEqual(linkJob.kind, .link)
945+
let cmd = linkJob.commandLine
946+
947+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0Mac))]))
948+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1Mac))]))
949+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsMac))]))
950+
}
951+
952+
do {
953+
var driver = try Driver(args: commonArgs + ["-target", "x86_64-apple-macosx10.15.4"], env: env)
954+
let plannedJobs = try driver.planBuild()
955+
956+
XCTAssertEqual(3, plannedJobs.count)
957+
let linkJob = plannedJobs[2]
958+
XCTAssertEqual(linkJob.kind, .link)
959+
let cmd = linkJob.commandLine
960+
961+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0Mac))]))
962+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1Mac))]))
963+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsMac))]))
964+
}
965+
966+
do {
967+
var driver = try Driver(args: commonArgs + ["-target", "x86_64-apple-macosx10.15.4", "-runtime-compatibility-version", "5.0"], env: env)
968+
let plannedJobs = try driver.planBuild()
969+
970+
XCTAssertEqual(3, plannedJobs.count)
971+
let linkJob = plannedJobs[2]
972+
XCTAssertEqual(linkJob.kind, .link)
973+
let cmd = linkJob.commandLine
974+
975+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0Mac))]))
976+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1Mac))]))
977+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsMac))]))
978+
}
979+
980+
do {
981+
var driver = try Driver(args: commonArgs + ["-target", "arm64-apple-ios13.0"], env: env)
982+
let plannedJobs = try driver.planBuild()
983+
984+
XCTAssertEqual(3, plannedJobs.count)
985+
let linkJob = plannedJobs[2]
986+
XCTAssertEqual(linkJob.kind, .link)
987+
let cmd = linkJob.commandLine
988+
989+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0iOS))]))
990+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1iOS))]))
991+
XCTAssertFalse(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsiOS))]))
992+
}
993+
994+
do {
995+
var driver = try Driver(args: commonArgs + ["-target", "arm64-apple-ios12.0"], env: env)
996+
let plannedJobs = try driver.planBuild()
997+
998+
XCTAssertEqual(3, plannedJobs.count)
999+
let linkJob = plannedJobs[2]
1000+
XCTAssertEqual(linkJob.kind, .link)
1001+
let cmd = linkJob.commandLine
1002+
1003+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_0iOS))]))
1004+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(path5_1iOS))]))
1005+
XCTAssertTrue(cmd.contains(subsequence: [.flag("-force_load"), .path(.absolute(pathDynamicReplacementsiOS))]))
1006+
}
1007+
}
1008+
}
1009+
9061010
func testSanitizerArgs() throws {
9071011
// FIXME: This doesn't work on Linux.
9081012
#if os(macOS)

0 commit comments

Comments
 (0)