diff --git a/Sources/SWBAndroidPlatform/Plugin.swift b/Sources/SWBAndroidPlatform/Plugin.swift index a50f97d5..56615c30 100644 --- a/Sources/SWBAndroidPlatform/Plugin.swift +++ b/Sources/SWBAndroidPlatform/Plugin.swift @@ -83,9 +83,6 @@ struct AndroidSDKRegistryExtension: SDKRegistryExtension { let defaultProperties: [String: PropertyListItem] = [ "SDK_STAT_CACHE_ENABLE": "NO", - // Workaround to avoid `-add_ast_path` on Linux, apparently this needs to perform some "swift modulewrap" step instead. - "GCC_GENERATE_DEBUGGING_SYMBOLS": .plString("NO"), - // Workaround to avoid `-dependency_info` on Linux. "LD_DEPENDENCY_INFO_FILE": .plString(""), diff --git a/Sources/SWBCore/SDKRegistry.swift b/Sources/SWBCore/SDKRegistry.swift index e7abe3d1..17dd57ee 100644 --- a/Sources/SWBCore/SDKRegistry.swift +++ b/Sources/SWBCore/SDKRegistry.swift @@ -749,9 +749,6 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send switch operatingSystem { case .linux: defaultProperties = [ - // Workaround to avoid `-add_ast_path` on Linux, apparently this needs to perform some "swift modulewrap" step instead. - "GCC_GENERATE_DEBUGGING_SYMBOLS": .plString("NO"), - // Workaround to avoid `-dependency_info` on Linux. "LD_DEPENDENCY_INFO_FILE": .plString(""), diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 27244ed8..6a6c8d5a 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -976,6 +976,7 @@ public final class BuiltinMacros { public static let SUPPORTS_TEXT_BASED_API = BuiltinMacros.declareBooleanMacro("SUPPORTS_TEXT_BASED_API") public static let SWIFT_AUTOLINK_EXTRACT_OUTPUT_PATH = BuiltinMacros.declarePathMacro("SWIFT_AUTOLINK_EXTRACT_OUTPUT_PATH") public static let PLATFORM_REQUIRES_SWIFT_AUTOLINK_EXTRACT = BuiltinMacros.declareBooleanMacro("PLATFORM_REQUIRES_SWIFT_AUTOLINK_EXTRACT") + public static let PLATFORM_REQUIRES_SWIFT_MODULEWRAP = BuiltinMacros.declareBooleanMacro("PLATFORM_REQUIRES_SWIFT_MODULEWRAP") public static let SWIFT_ABI_CHECKER_BASELINE_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_BASELINE_DIR") public static let SWIFT_ABI_CHECKER_EXCEPTIONS_FILE = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_EXCEPTIONS_FILE") public static let SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR") @@ -2128,6 +2129,7 @@ public final class BuiltinMacros { SUPPORTS_TEXT_BASED_API, SWIFT_AUTOLINK_EXTRACT_OUTPUT_PATH, PLATFORM_REQUIRES_SWIFT_AUTOLINK_EXTRACT, + PLATFORM_REQUIRES_SWIFT_MODULEWRAP, SWIFT_ABI_CHECKER_BASELINE_DIR, SWIFT_ABI_CHECKER_EXCEPTIONS_FILE, SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR, diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index 56ce7ab9..36efd74b 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -2535,6 +2535,7 @@ private class SettingsBuilder { sdkTable.push(BuiltinMacros.DYNAMIC_LIBRARY_EXTENSION, literal: imageFormat.dynamicLibraryExtension) sdkTable.push(BuiltinMacros.PLATFORM_REQUIRES_SWIFT_AUTOLINK_EXTRACT, literal: imageFormat.requiresSwiftAutolinkExtract) + sdkTable.push(BuiltinMacros.PLATFORM_REQUIRES_SWIFT_MODULEWRAP, literal: imageFormat.requiresSwiftModulewrap) } // Add additional SDK default settings. diff --git a/Sources/SWBCore/Specs/Tools/LinkerTools.swift b/Sources/SWBCore/Specs/Tools/LinkerTools.swift index 30a67df4..28daf721 100644 --- a/Sources/SWBCore/Specs/Tools/LinkerTools.swift +++ b/Sources/SWBCore/Specs/Tools/LinkerTools.swift @@ -592,7 +592,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // If we are linking Swift and build for debugging, pass the right .swiftmodule file for the current architecture to the // linker. This is needed so that debugging these modules works correctly. Note that `swiftModulePaths` will be empty for // anything but static archives and object files, because dynamic libraries and frameworks do not require this. - if isLinkUsingSwift && cbc.scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) { + if isLinkUsingSwift && cbc.scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) && !cbc.scope.evaluate(BuiltinMacros.PLATFORM_REQUIRES_SWIFT_MODULEWRAP) { for library in libraries { if let swiftModulePath = library.swiftModulePaths[cbc.scope.evaluate(BuiltinMacros.CURRENT_ARCH)] { commandLine += ["-Xlinker", "-add_ast_path", "-Xlinker", swiftModulePath.str] diff --git a/Sources/SWBCore/Specs/Tools/SwiftCompiler.swift b/Sources/SWBCore/Specs/Tools/SwiftCompiler.swift index 56dc2243..53b1480c 100644 --- a/Sources/SWBCore/Specs/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/Specs/Tools/SwiftCompiler.swift @@ -2127,6 +2127,11 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi return (inputs, outputs) }() + if cbc.scope.evaluate(BuiltinMacros.PLATFORM_REQUIRES_SWIFT_MODULEWRAP) && cbc.scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) { + let moduleWrapOutput = Path(moduleFilePath.withoutSuffix + ".o") + moduleOutputPaths.append(moduleWrapOutput) + } + // Add const metadata outputs to extra compilation outputs if await supportConstSupplementaryMetadata(cbc, delegate, compilationMode: compilationMode) { // If using whole module optimization then we use the -master.swiftconstvalues file from the sole compilation task. @@ -2963,7 +2968,7 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi // be a source-less target which just contains object files in it's framework phase. let currentPlatformFilter = PlatformFilter(scope) let containsSources = (producer.configuredTarget?.target as? StandardTarget)?.sourcesBuildPhase?.buildFiles.filter { currentPlatformFilter.matches($0.platformFilters) }.isEmpty == false - if containsSources && inputFileTypes.contains(where: { $0.conformsTo(identifier: "sourcecode.swift") }) && scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) { + if containsSources && inputFileTypes.contains(where: { $0.conformsTo(identifier: "sourcecode.swift") }) && scope.evaluate(BuiltinMacros.GCC_GENERATE_DEBUGGING_SYMBOLS) && !scope.evaluate(BuiltinMacros.PLATFORM_REQUIRES_SWIFT_MODULEWRAP) { let moduleName = scope.evaluate(BuiltinMacros.SWIFT_MODULE_NAME) let moduleFileDir = scope.evaluate(BuiltinMacros.PER_ARCH_MODULE_FILE_DIR) let moduleFilePath = moduleFileDir.join(moduleName + ".swiftmodule") diff --git a/Sources/SWBUtil/ProcessInfo.swift b/Sources/SWBUtil/ProcessInfo.swift index 0d9cf865..e1bfcecd 100644 --- a/Sources/SWBUtil/ProcessInfo.swift +++ b/Sources/SWBUtil/ProcessInfo.swift @@ -200,6 +200,15 @@ extension ImageFormat { return false } } + + public var requiresSwiftModulewrap: Bool { + switch self { + case .macho: + return false + default: + return true + } + } } extension FixedWidthInteger { diff --git a/Sources/SWBWebAssemblyPlatform/Plugin.swift b/Sources/SWBWebAssemblyPlatform/Plugin.swift index 27dd4b0e..d2f17a28 100644 --- a/Sources/SWBWebAssemblyPlatform/Plugin.swift +++ b/Sources/SWBWebAssemblyPlatform/Plugin.swift @@ -61,9 +61,6 @@ struct WebAssemblySDKRegistryExtension: SDKRegistryExtension { let defaultProperties: [String: PropertyListItem] = [ "SDK_STAT_CACHE_ENABLE": "NO", - // Workaround to avoid `-add_ast_path` on WebAssembly, apparently this needs to perform some "swift modulewrap" step instead. - "GCC_GENERATE_DEBUGGING_SYMBOLS": .plString("NO"), - "GENERATE_TEXT_BASED_STUBS": "NO", "GENERATE_INTERMEDIATE_TEXT_BASED_STUBS": "NO", diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index de9fa7e5..635268c0 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -222,6 +222,180 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } + @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + func debuggableCommandLineTool() async throws { + try await withTemporaryDirectory { (tmpDir: Path) in + let testProject = try await TestProject( + "TestProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("main.swift"), + TestFile("dynamic.swift"), + TestFile("static.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "ARCHS": "$(ARCHS_STANDARD)", + "CODE_SIGNING_ALLOWED": ProcessInfo.processInfo.hostOperatingSystem() == .macOS ? "YES" : "NO", + "CODE_SIGN_IDENTITY": "-", + "CODE_SIGN_ENTITLEMENTS": "Entitlements.plist", + "DEFINES_MODULE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "$(HOST_PLATFORM)", + "SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)", + "SWIFT_VERSION": swiftVersion, + "GCC_GENERATE_DEBUGGING_SYMBOLS": "YES", + ]) + ], + targets: [ + TestStandardTarget( + "tool", + type: .commandLineTool, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "LD_RUNPATH_SEARCH_PATHS": "@loader_path/", + ]) + ], + buildPhases: [ + TestSourcesBuildPhase(["main.swift"]), + TestFrameworksBuildPhase([ + TestBuildFile(.target("dynamiclib")), + TestBuildFile(.target("staticlib")), + ]) + ], + dependencies: [ + "dynamiclib", + "staticlib", + ] + ), + TestStandardTarget( + "dynamiclib", + type: .dynamicLibrary, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "DYLIB_INSTALL_NAME_BASE": "$ORIGIN", + "DYLIB_INSTALL_NAME_BASE[sdk=macosx*]": "@rpath", + + // FIXME: Find a way to make these default + "EXECUTABLE_PREFIX": "lib", + "EXECUTABLE_PREFIX[sdk=windows*]": "", + ]) + ], + buildPhases: [ + TestSourcesBuildPhase(["dynamic.swift"]), + ] + ), + TestStandardTarget( + "staticlib", + type: .staticLibrary, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + // FIXME: Find a way to make these default + "EXECUTABLE_PREFIX": "lib", + "EXECUTABLE_PREFIX[sdk=windows*]": "", + ]) + ], + buildPhases: [ + TestSourcesBuildPhase(["static.swift"]), + ] + ), + ]) + let core = try await getCore() + let tester = try await BuildOperationTester(core, testProject, simulated: false) + + let projectDir = tester.workspace.projects[0].sourceRoot + + try await tester.fs.writeFileContents(projectDir.join("main.swift")) { stream in + stream <<< "import dynamiclib\n" + stream <<< "import staticlib\n" + stream <<< "dynamicLib()\n" + stream <<< "dynamicLib()\n" + stream <<< "staticLib()\n" + stream <<< "print(\"Hello world\")\n" + } + + try await tester.fs.writeFileContents(projectDir.join("dynamic.swift")) { stream in + stream <<< "public func dynamicLib() { }" + } + + try await tester.fs.writeFileContents(projectDir.join("static.swift")) { stream in + stream <<< "public func staticLib() { }" + } + + try await tester.fs.writePlist(projectDir.join("Entitlements.plist"), .plDict([:])) + + let provisioningInputs = [ + "dynamiclib": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:])), + "staticlib": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:])), + "tool": ProvisioningTaskInputs(identityHash: "-", signedEntitlements: .plDict([:]), simulatedEntitlements: .plDict([:])) + ] + + let destination: RunDestinationInfo = .host + try await tester.checkBuild(runDestination: destination, persistent: true, signableTargets: Set(provisioningInputs.keys), signableTargetInputs: provisioningInputs) { results in + results.checkNoErrors() + if core.hostOperatingSystem.imageFormat.requiresSwiftModulewrap { + try results.checkTask(.matchTargetName("tool"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in + let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) + let contents = try tester.fs.read(auxFileAction.context.input).asString + let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } + #expect(files.count == 2) + #expect(files[0].hasSuffix("tool.o")) + #expect(files[1].hasSuffix("main.o")) + } + let toolWrap = try #require(results.getTask(.matchTargetName("tool"), .matchRuleType("SwiftModuleWrap"))) + try results.checkTask(.matchTargetName("tool"), .matchRuleType("Ld")) { task in + try results.checkTaskFollows(task, toolWrap) + } + + try results.checkTask(.matchTargetName("dynamiclib"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in + let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) + let contents = try tester.fs.read(auxFileAction.context.input).asString + let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } + #expect(files.count == 2) + #expect(files[0].hasSuffix("dynamiclib.o")) + #expect(files[1].hasSuffix("dynamic.o")) + } + let dylibWrap = try #require(results.getTask(.matchTargetName("dynamiclib"), .matchRuleType("SwiftModuleWrap"))) + try results.checkTask(.matchTargetName("dynamiclib"), .matchRuleType("Ld")) { task in + try results.checkTaskFollows(task, dylibWrap) + } + + try results.checkTask(.matchTargetName("staticlib"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in + let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) + let contents = try tester.fs.read(auxFileAction.context.input).asString + let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } + #expect(files.count == 2) + #expect(files[0].hasSuffix("staticlib.o")) + #expect(files[1].hasSuffix("static.o")) + } + let staticWrap = try #require(results.getTask(.matchTargetName("staticlib"), .matchRuleType("SwiftModuleWrap"))) + try results.checkTask(.matchTargetName("staticlib"), .matchRuleType("Libtool")) { task in + try results.checkTaskFollows(task, staticWrap) + } + } + + let toolchain = try #require(try await getCore().toolchainRegistry.defaultToolchain) + let environment: [String: String] + if destination.platform == "linux" { + environment = ["LD_LIBRARY_PATH": toolchain.path.join("usr/lib/swift/linux").str] + } else { + environment = [:] + } + + let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: projectDir.join("build").join("Debug\(destination.builtProductsDirSuffix)").join(core.hostOperatingSystem.imageFormat.executableName(basename: "tool")).str), arguments: [], environment: environment) + #expect(executionResult.exitStatus == .exit(0)) + if core.hostOperatingSystem == .windows { + #expect(String(decoding: executionResult.stdout, as: UTF8.self) == "Hello world\r\n") + } else { + #expect(String(decoding: executionResult.stdout, as: UTF8.self) == "Hello world\n") + } + #expect(String(decoding: executionResult.stderr, as: UTF8.self) == "") + } + } + } + /// Check that environment variables are propagated from the user environment correctly. @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireSystemPackages(apt: "yacc", yum: "byacc")) func userEnvironment() async throws {