From 14c82d251fc3dca7751d98cdb67f57f556e23f97 Mon Sep 17 00:00:00 2001 From: artemcm Date: Wed, 8 May 2024 17:14:07 -0700 Subject: [PATCH] Fallback on 'swift-frontend -scan-dependencies ...' fallback path in case libSwiftScan shared library cannot be loaded. Resolves rdar://127768140 --- .../ModuleDependencyScanning.swift | 50 ++++++++++------ .../ExplicitModuleBuildTests.swift | 59 +++++++++++++++++++ 2 files changed, 91 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift index 0e0c77560..b75400ba9 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift @@ -23,8 +23,14 @@ import class Foundation.JSONDecoder import var Foundation.EXIT_SUCCESS extension Diagnostic.Message { - static func warn_scanner_frontend_fallback() -> Diagnostic.Message { - .warning("Fallback to `swift-frontend` dependency scanner invocation") + static func warn_scan_dylib_not_found() -> Diagnostic.Message { + .warning("Unable to locate libSwiftScan. Fallback to `swift-frontend` dependency scanner invocation.") + } + static func warn_scan_dylib_load_failed(_ libPath: String) -> Diagnostic.Message { + .warning("In-process dependency scan query failed due to incompatible libSwiftScan (\(libPath)). Fallback to `swift-frontend` dependency scanner invocation. Specify '-nonlib-dependency-scanner' to silence this warning.") + } + static func error_caching_enabled_libswiftscan_load_failure(_ libPath: String) -> Diagnostic.Message { + .error("Swift Caching enabled - libSwiftScan load failed (\(libPath)).") } static func scanner_diagnostic_error(_ message: String) -> Diagnostic.Message { .error(message) @@ -156,27 +162,35 @@ public extension Driver { /// Returns false if the lib is available and ready to use private mutating func initSwiftScanLib() throws -> Bool { - // If `-nonlib-dependency-scanner` was specified or the libSwiftScan library cannot be found, + // `-nonlib-dependency-scanner` was specified + guard !parsedOptions.hasArgument(.driverScanDependenciesNonLib) else { + return true + } + + // If the libSwiftScan library cannot be found, // attempt to fallback to using `swift-frontend -scan-dependencies` invocations for dependency // scanning. - guard !parsedOptions.hasArgument(.driverScanDependenciesNonLib), - let scanLibPath = try toolchain.lookupSwiftScanLib(), + guard let scanLibPath = try toolchain.lookupSwiftScanLib(), fileSystem.exists(scanLibPath) else { - // This warning is mostly useful for debugging the driver, so let's hide it - // when libSwiftDriver is used, instead of a swift-driver executable. - if !integratedDriver { - diagnosticEngine.emit(.warn_scanner_frontend_fallback()) - } - + diagnosticEngine.emit(.warn_scan_dylib_not_found()) return true } - try interModuleDependencyOracle.verifyOrCreateScannerInstance(fileSystem: fileSystem, - swiftScanLibPath: scanLibPath) - if isCachingEnabled { - self.cas = try interModuleDependencyOracle.getOrCreateCAS(pluginPath: try getCASPluginPath(), - onDiskPath: try getOnDiskCASPath(), - pluginOptions: try getCASPluginOptions()) + do { + try interModuleDependencyOracle.verifyOrCreateScannerInstance(fileSystem: fileSystem, + swiftScanLibPath: scanLibPath) + if isCachingEnabled { + self.cas = try interModuleDependencyOracle.getOrCreateCAS(pluginPath: try getCASPluginPath(), + onDiskPath: try getOnDiskCASPath(), + pluginOptions: try getCASPluginOptions()) + } + } catch { + if isCachingEnabled { + diagnosticEngine.emit(.error_caching_enabled_libswiftscan_load_failure(scanLibPath.description)) + } else { + diagnosticEngine.emit(.warn_scan_dylib_load_failed(scanLibPath.description)) + } + return true } return false } @@ -257,7 +271,7 @@ public extension Driver { } } - mutating internal func performDependencyScan() throws -> InterModuleDependencyGraph { + mutating func performDependencyScan() throws -> InterModuleDependencyGraph { let scannerJob = try dependencyScanningJob() let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles) let dependencyGraph: InterModuleDependencyGraph diff --git a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift index 73e0719d3..8957e61aa 100644 --- a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift +++ b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift @@ -1517,6 +1517,65 @@ final class ExplicitModuleBuildTests: XCTestCase { } } + // Ensure dependency scanning succeeds via fallback `swift-frontend -scan-dependenceis` + // mechanism if libSwiftScan.dylib fails to load. + func testDependencyScanningFallback() throws { + let (stdlibPath, shimsPath, _, _) = try getDriverArtifactsForScanning() + + // Create a simple test case. + try withTemporaryDirectory { path in + let main = path.appending(component: "testDependencyScanningFallback.swift") + try localFileSystem.writeFileContents(main, bytes: "import C;") + + let dummyBrokenDylib = path.appending(component: "lib_InternalSwiftScan.dylib") + try localFileSystem.writeFileContents(dummyBrokenDylib, bytes: "n/a") + + var environment = ProcessEnv.vars + environment["SWIFT_DRIVER_SWIFTSCAN_LIB"] = dummyBrokenDylib.nativePathString(escaped: true) + + let cHeadersPath: AbsolutePath = + try testInputsPath.appending(component: "ExplicitModuleBuilds") + .appending(component: "CHeaders") + let swiftModuleInterfacesPath: AbsolutePath = + try testInputsPath.appending(component: "ExplicitModuleBuilds") + .appending(component: "Swift") + let sdkArgumentsForTesting: [String] = (try? Driver.sdkArgumentsForTesting()) ?? [] + let driverArgs: [String] = ["swiftc", + "-I", cHeadersPath.nativePathString(escaped: true), + "-I", swiftModuleInterfacesPath.nativePathString(escaped: true), + "-I", stdlibPath.nativePathString(escaped: true), + "-I", shimsPath.nativePathString(escaped: true), + "/tmp/Foo.o", + "-explicit-module-build", + "-working-directory", path.nativePathString(escaped: true), + "-disable-clang-target", + main.nativePathString(escaped: true)] + sdkArgumentsForTesting + do { + var driver = try Driver(args: driverArgs, env: environment) + let interModuleDependencyGraph = try driver.performDependencyScan() + XCTAssertTrue(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .warning && + $0.message.text == "In-process dependency scan query failed due to incompatible libSwiftScan (\(dummyBrokenDylib.nativePathString(escaped: true))). Fallback to `swift-frontend` dependency scanner invocation. Specify '-nonlib-dependency-scanner' to silence this warning."}) + XCTAssertTrue(interModuleDependencyGraph.mainModule.directDependencies?.contains(where: { $0.moduleName == "C" })) + } + + // Ensure no warning is emitted with '-nonlib-dependency-scanner' + do { + var driver = try Driver(args: driverArgs + ["-nonlib-dependency-scanner"], env: environment) + let _ = try driver.performDependencyScan() + XCTAssertFalse(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .warning && + $0.message.text == "In-process dependency scan query failed due to incompatible libSwiftScan (\(dummyBrokenDylib.nativePathString(escaped: true))). Fallback to `swift-frontend` dependency scanner invocation. Specify '-nonlib-dependency-scanner' to silence this warning."}) + } + + // Ensure error is emitted when caching is enabled + do { + var driver = try Driver(args: driverArgs + ["-cache-compile-job"], env: environment) + let _ = try driver.performDependencyScan() + XCTAssertFalse(driver.diagnosticEngine.diagnostics.contains { $0.behavior == .error && + $0.message.text == "Swift Caching enabled - libSwiftScan load failed (\(dummyBrokenDylib.nativePathString(escaped: true))."}) + } + } + } + func testParallelDependencyScanningDiagnostics() throws { let (stdlibPath, shimsPath, toolchain, _) = try getDriverArtifactsForScanning() // The dependency oracle wraps an instance of libSwiftScan and ensures thread safety across