diff --git a/Sources/SWBApplePlatform/Plugin.swift b/Sources/SWBApplePlatform/Plugin.swift index 6be46f8e..2288a849 100644 --- a/Sources/SWBApplePlatform/Plugin.swift +++ b/Sources/SWBApplePlatform/Plugin.swift @@ -31,8 +31,8 @@ import SWBTaskConstruction } struct AppleDeveloperDirectoryExtension: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { - try await hostOperatingSystem == .macOS ? Xcode.getActiveDeveloperDirectoryPath() : nil + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { + try await hostOperatingSystem == .macOS ? .xcode(Xcode.getActiveDeveloperDirectoryPath()) : nil } } diff --git a/Sources/SWBCore/Core.swift b/Sources/SWBCore/Core.swift index 4864e81c..238a2e92 100644 --- a/Sources/SWBCore/Core.swift +++ b/Sources/SWBCore/Core.swift @@ -70,14 +70,9 @@ public final class Core: Sendable { delegate.error("Could not determine path to developer directory because no extensions provided a fallback value") return nil case 1: - let path = values[0] - if path.str.hasSuffix(".app/Contents/Developer") { - resolvedDeveloperPath = .xcode(path) - } else { - resolvedDeveloperPath = .fallback(values[0]) - } + resolvedDeveloperPath = values[0] default: - delegate.error("Could not determine path to developer directory because multiple extensions provided conflicting fallback values: \(values.sorted().map { $0.str }.joined(separator: ", "))") + delegate.error("Could not determine path to developer directory because multiple extensions provided conflicting fallback values: \(values.map { $0.path.str }.sorted().joined(separator: ", "))") return nil } } @@ -181,12 +176,9 @@ public final class Core: Sendable { // A path to the root of a Swift toolchain, optionally paired with the developer path of an installed Xcode case swiftToolchain(Path, xcodeDeveloperPath: Path?) - // A fallback resolved path. - case fallback(Path) - public var path: Path { switch self { - case .xcode(let path), .swiftToolchain(let path, xcodeDeveloperPath: _), .fallback(let path): + case .xcode(let path), .swiftToolchain(let path, xcodeDeveloperPath: _): return path } } @@ -259,7 +251,7 @@ public final class Core: Sendable { self.xcodeProductBuildVersion = ProductBuildVersion(major: 99, train: "T", build: 999) self.xcodeProductBuildVersionString = xcodeProductBuildVersion.description } - case .swiftToolchain, .fallback: + case .swiftToolchain: // FIXME: Eliminate this requirment for Swift toolchains self.xcodeVersion = Version(99, 99, 99) self.xcodeProductBuildVersion = ProductBuildVersion(major: 99, train: "T", build: 999) @@ -277,12 +269,14 @@ public final class Core: Sendable { case .xcode(let path): toolchainPaths.append((path.join("Toolchains"), strict: path.str.hasSuffix(".app/Contents/Developer"))) case .swiftToolchain(let path, xcodeDeveloperPath: let xcodeDeveloperPath): - toolchainPaths.append((path, strict: true)) + if hostOperatingSystem == .windows { + toolchainPaths.append((path.join("Toolchains"), strict: true)) + } else { + toolchainPaths.append((path, strict: true)) + } if let xcodeDeveloperPath { toolchainPaths.append((xcodeDeveloperPath.join("Toolchains"), strict: xcodeDeveloperPath.str.hasSuffix(".app/Contents/Developer"))) } - case .fallback(let path): - toolchainPaths.append((path.join("Toolchains"), strict: false)) } // FIXME: We should support building the toolchain locally (for `inferiorProductsPath`). @@ -418,7 +412,7 @@ public final class Core: Sendable { let pluginPath = path.join("usr/lib/libToolchainCASPlugin.dylib") let plugin = try? ToolchainCASPlugin(dylib: pluginPath) casPlugin = plugin - case .swiftToolchain, .fallback: + case .swiftToolchain: // Unimplemented break } @@ -454,8 +448,6 @@ public final class Core: Sendable { } else { searchPaths = [] } - case .fallback: - searchPaths = [] } } if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") { diff --git a/Sources/SWBCore/Extensions/DeveloperDirectoryExtension.swift b/Sources/SWBCore/Extensions/DeveloperDirectoryExtension.swift index 9dc0db37..3e69177c 100644 --- a/Sources/SWBCore/Extensions/DeveloperDirectoryExtension.swift +++ b/Sources/SWBCore/Extensions/DeveloperDirectoryExtension.swift @@ -21,5 +21,5 @@ public struct DeveloperDirectoryExtensionPoint: ExtensionPoint { } public protocol DeveloperDirectoryExtension: Sendable { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? } diff --git a/Sources/SWBCore/MacroConfigFileLoader.swift b/Sources/SWBCore/MacroConfigFileLoader.swift index 224e5e9f..2d5e4248 100644 --- a/Sources/SWBCore/MacroConfigFileLoader.swift +++ b/Sources/SWBCore/MacroConfigFileLoader.swift @@ -133,7 +133,7 @@ final class MacroConfigFileLoader: Sendable { // FIXME: Move this to its proper home, and support the other special cases Xcode has (PLATFORM_DIR and SDK_DIR). This should move to using a generic facility, e.g., source trees: Add search paths for .xcconfig macros to match what Xcode has if path.str.hasPrefix("") { switch developerPath { - case .xcode(let developerPath), .swiftToolchain(let developerPath, _), .fallback(let developerPath): + case .xcode(let developerPath), .swiftToolchain(let developerPath, _): path = Path(path.str.replacingOccurrences(of: "", with: developerPath.str)) } } diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index bf7cc068..1f67c0ea 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -1009,6 +1009,7 @@ public final class BuiltinMacros { public static let SWIFT_ENABLE_BARE_SLASH_REGEX = BuiltinMacros.declareBooleanMacro("SWIFT_ENABLE_BARE_SLASH_REGEX") public static let SWIFT_ENABLE_EMIT_CONST_VALUES = BuiltinMacros.declareBooleanMacro("SWIFT_ENABLE_EMIT_CONST_VALUES") public static let SWIFT_ENABLE_OPAQUE_TYPE_ERASURE = BuiltinMacros.declareBooleanMacro("SWIFT_ENABLE_OPAQUE_TYPE_ERASURE") + public static let SWIFT_ENABLE_LANGUAGE_FEATURE_ENABLEMENT_DIAGNOSTICS = BuiltinMacros.declareBooleanMacro("SWIFT_ENABLE_LANGUAGE_FEATURE_ENABLEMENT_DIAGNOSTICS") public static let SWIFT_EMIT_CONST_VALUE_PROTOCOLS = BuiltinMacros.declareStringListMacro("SWIFT_EMIT_CONST_VALUE_PROTOCOLS") public static let SWIFT_GENERATE_ADDITIONAL_LINKER_ARGS = BuiltinMacros.declareBooleanMacro("SWIFT_GENERATE_ADDITIONAL_LINKER_ARGS") public static let SWIFT_USE_INTEGRATED_DRIVER = BuiltinMacros.declareBooleanMacro("SWIFT_USE_INTEGRATED_DRIVER") @@ -2180,6 +2181,7 @@ public final class BuiltinMacros { SWIFT_ENABLE_BARE_SLASH_REGEX, SWIFT_ENABLE_EMIT_CONST_VALUES, SWIFT_ENABLE_OPAQUE_TYPE_ERASURE, + SWIFT_ENABLE_LANGUAGE_FEATURE_ENABLEMENT_DIAGNOSTICS, SWIFT_EMIT_CONST_VALUE_PROTOCOLS, SWIFT_GENERATE_ADDITIONAL_LINKER_ARGS, SWIFT_ENABLE_TESTABILITY, diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index eb3b72cc..bc9b7e62 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -1026,7 +1026,7 @@ extension WorkspaceContext { // Add the standard search paths. switch core.developerPath { - case .xcode(let path), .fallback(let path): + case .xcode(let path): paths.append(path.join("usr").join("bin")) paths.append(path.join("usr").join("local").join("bin")) case .swiftToolchain(let path, let xcodeDeveloperPath): diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift index 85995789..6a495ad6 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift @@ -1443,6 +1443,9 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi } private func diagnoseFeatureEnablement(_ cbc: CommandBuildContext, _ languageFeatureEnablementInfo: SwiftBlocklists.LanguageFeatureEnablementInfo, _ delegate: any TaskGenerationDelegate) { + guard cbc.scope.evaluate(BuiltinMacros.SWIFT_ENABLE_LANGUAGE_FEATURE_ENABLEMENT_DIAGNOSTICS) else { + return + } let moduleName = cbc.scope.evaluate(BuiltinMacros.SWIFT_MODULE_NAME) let otherFlags = cbc.scope.evaluate(BuiltinMacros.OTHER_SWIFT_FLAGS) var otherFlagsFeatures: [String] = [] diff --git a/Sources/SWBGenericUnixPlatform/Plugin.swift b/Sources/SWBGenericUnixPlatform/Plugin.swift index e4679961..128fde75 100644 --- a/Sources/SWBGenericUnixPlatform/Plugin.swift +++ b/Sources/SWBGenericUnixPlatform/Plugin.swift @@ -23,13 +23,13 @@ import Foundation } struct GenericUnixDeveloperDirectoryExtension: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { if hostOperatingSystem == .windows || hostOperatingSystem == .macOS { // Handled by the Windows and Apple plugins return nil } - return .root + return .swiftToolchain(.root, xcodeDeveloperPath: nil) } } diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift index 5dc3f792..fa1b5686 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/CustomTaskProducer.swift @@ -34,28 +34,43 @@ final class CustomTaskProducer: PhasedTaskProducer, TaskProducer { let workingDirectory = customTask.workingDirectory.map { Path(context.settings.globalScope.evaluate($0)).normalize() } ?? context.defaultWorkingDirectory let inputPaths = customTask.inputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() } let inputs = inputPaths.map { delegate.createNode($0) } - var outputs: [any PlannedNode] = customTask.outputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() }.map { delegate.createNode($0) } - + let outputPaths = customTask.outputFilePaths.map { Path(context.settings.globalScope.evaluate($0)).normalize() } + var outputs: [any PlannedNode] = outputPaths.map { delegate.createNode($0) } + + let md5Context = InsecureHashContext() + for arg in commandLine { + md5Context.add(string: arg) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + for (key, value) in environment.bindingsDictionary { + md5Context.add(string: key) + md5Context.add(number: 0) + md5Context.add(string: value) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + md5Context.add(string: workingDirectory.str) + md5Context.add(number: 1) + for input in inputPaths { + md5Context.add(string: input.str) + md5Context.add(number: 0) + } + md5Context.add(number: 1) + for output in outputPaths { + md5Context.add(string: output.str) + md5Context.add(number: 0) + } + let taskSignature = md5Context.signature.asString + if outputs.isEmpty { // If there are no outputs, create a virtual output that can be wired up to gates - let md5Context = InsecureHashContext() - for arg in commandLine { - md5Context.add(string: arg) - } - for (key, value) in environment.bindingsDictionary { - md5Context.add(string: key) - md5Context.add(string: value) - } - md5Context.add(string: workingDirectory.str) - for input in inputPaths { - md5Context.add(string: input.str) - } - outputs.append(delegate.createVirtualNode("CustomTask-\(md5Context.signature.asString)")) + outputs.append(delegate.createVirtualNode("CustomTask-\(taskSignature)")) } delegate.createTask( type: CustomTaskTypeDescription.only, - ruleInfo: ["CustomTask", context.settings.globalScope.evaluate(customTask.executionDescription)], + ruleInfo: ["CustomTask", context.settings.globalScope.evaluate(customTask.executionDescription), taskSignature], commandLine: commandLine, environment: environment, workingDirectory: workingDirectory, diff --git a/Sources/SWBTestSupport/CoreTestSupport.swift b/Sources/SWBTestSupport/CoreTestSupport.swift index 356345fa..e95da70f 100644 --- a/Sources/SWBTestSupport/CoreTestSupport.swift +++ b/Sources/SWBTestSupport/CoreTestSupport.swift @@ -38,7 +38,7 @@ extension Core { if hostOperatingSystem == .macOS { developerPath = .xcode(try await Xcode.getActiveDeveloperDirectoryPath()) } else { - developerPath = .fallback(Path.root) + developerPath = .swiftToolchain(.root, xcodeDeveloperPath: nil) } let delegate = TestingCoreDelegate() return await (try Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: PluginManager(skipLoadingPluginIdentifiers: []), developerPath: developerPath, resourceSearchPaths: [], inferiorProductsPath: nil, additionalContentPaths: [], environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess), delegate.diagnostics) diff --git a/Sources/SWBTestSupport/DummyCommandProducer.swift b/Sources/SWBTestSupport/DummyCommandProducer.swift index de335df2..021ee8fd 100644 --- a/Sources/SWBTestSupport/DummyCommandProducer.swift +++ b/Sources/SWBTestSupport/DummyCommandProducer.swift @@ -60,7 +60,7 @@ package struct MockCommandProducer: CommandProducer, Sendable { paths.append(path) } switch core.developerPath { - case .xcode(let path), .fallback(let path): + case .xcode(let path): paths.append(path.join("usr").join("bin")) paths.append(path.join("usr").join("local").join("bin")) case .swiftToolchain(let path, xcodeDeveloperPath: let xcodeDeveloperPath): diff --git a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec index 7bdda1e0..cccdc078 100644 --- a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec @@ -1353,6 +1353,21 @@ }; }, + // Hidden clang importer options to control C++ behavior + // in the clang importer, not visible in build settings. + { + Name = "SWIFT_CLANG_CXX_STANDARD_LIBRARY_HARDENING"; + Type = String; + DefaultValue = "$(CLANG_CXX_STANDARD_LIBRARY_HARDENING)"; + CommandLineArgs = { + "none" = ("-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE"); + "fast" = ("-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST"); + "extensive" = ("-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"); + "debug" = ("-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG"); + "<>" = (); + }; + }, + { Name = "SWIFT_OVERLOAD_PREBUILT_MODULE_CACHE_PATH"; Type = Path; diff --git a/Sources/SWBWindowsPlatform/Plugin.swift b/Sources/SWBWindowsPlatform/Plugin.swift index 34037862..9a2c7e40 100644 --- a/Sources/SWBWindowsPlatform/Plugin.swift +++ b/Sources/SWBWindowsPlatform/Plugin.swift @@ -53,14 +53,14 @@ public final class WindowsPlugin: Sendable { } struct WindowsDeveloperDirectoryExtension: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { guard hostOperatingSystem == .windows else { return nil } guard let userProgramFiles = URL.userProgramFiles, let swiftPath = try? userProgramFiles.appending(component: "Swift").filePath else { throw StubError.error("Could not determine path to user program files") } - return swiftPath + return .swiftToolchain(swiftPath, xcodeDeveloperPath: nil) } } @@ -94,15 +94,7 @@ struct WindowsPlatformExtension: PlatformInfoExtension { return [] } - let platformsPath: Path - switch context.developerPath { - case .xcode(let path): - platformsPath = path.join("Platforms") - case .swiftToolchain(let path, _): - platformsPath = path.join("Platforms") - case .fallback(let path): - platformsPath = path.join("Platforms") - } + let platformsPath = context.developerPath.path.join("Platforms") return try context.fs.listdir(platformsPath).compactMap { version in let versionedPlatformsPath = platformsPath.join(version) guard context.fs.isDirectory(versionedPlatformsPath) else { diff --git a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift index 7d7df89f..3398c990 100644 --- a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift +++ b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift @@ -4821,6 +4821,7 @@ fileprivate struct SwiftDriverTests: CoreBasedTests { TestBuildConfiguration( "Debug", buildSettings: [ + "SWIFT_ENABLE_LANGUAGE_FEATURE_ENABLEMENT_DIAGNOSTICS": "YES", "PRODUCT_NAME": "$(TARGET_NAME)", "SWIFT_VERSION": "5", "BUILD_VARIANTS": "normal", diff --git a/Tests/SWBCoreTests/CoreTests.swift b/Tests/SWBCoreTests/CoreTests.swift index b19b7a33..36fbd821 100644 --- a/Tests/SWBCoreTests/CoreTests.swift +++ b/Tests/SWBCoreTests/CoreTests.swift @@ -320,6 +320,7 @@ import SWBServiceCore // Validate that the core fails if there are loading errors. try await withTemporaryDirectory { tmpDirPath in let fakePlatformPath = tmpDirPath.join("Platforms/Fake.platform") + try localFS.createDirectory(tmpDirPath.join("Toolchains"), recursive: true) try localFS.createDirectory(fakePlatformPath, recursive: true) try await localFS.writePlist(fakePlatformPath.join("Info.plist"), .plDict([ "Description": .plString("Fake"), @@ -334,7 +335,7 @@ import SWBServiceCore let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: []) await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint()) await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self) - let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: .fallback(tmpDirPath), buildServiceModTime: Date(), connectionMode: .inProcess) + let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: .swiftToolchain(tmpDirPath, xcodeDeveloperPath: nil), buildServiceModTime: Date(), connectionMode: .inProcess) #expect(core == nil) let results = CoreDelegateResults(delegate.diagnostics) @@ -346,69 +347,75 @@ import SWBServiceCore @Test(.skipIfEnvironmentVariableSet(key: .externalToolchainsDir)) func externalToolchainsDir() async throws { try await withTemporaryDirectory { tmpDir in + try localFS.createDirectory(tmpDir.join("Toolchains")) + let originalToolchain = try await toolchainPathsCount() - try await testExternalToolchainPath(withSetEnv: nil, expecting: [], originalToolchain) - try await testExternalToolchainPath(withSetEnv: tmpDir.join("tmp/Foobar/MyDir").str, expecting: [tmpDir.join("tmp/Foobar/MyDir").str], originalToolchain) - try await testExternalToolchainPath(withSetEnv: nil, expecting: [], originalToolchain) - try await testExternalToolchainPath(withSetEnv: [tmpDir.join("tmp/MetalToolchain1.0").str, tmpDir.join("tmp/MetalToolchain2.0").str, tmpDir.join("tmp/MetalToolchain3.0").str].joined(separator: String(Path.pathEnvironmentSeparator)), expecting: [ + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: nil, expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: tmpDir.join("tmp/Foobar/MyDir").str, expecting: [tmpDir.join("tmp/Foobar/MyDir").str], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: nil, expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: [tmpDir.join("tmp/MetalToolchain1.0").str, tmpDir.join("tmp/MetalToolchain2.0").str, tmpDir.join("tmp/MetalToolchain3.0").str].joined(separator: String(Path.pathEnvironmentSeparator)), expecting: [ tmpDir.join("tmp/MetalToolchain1.0").str, tmpDir.join("tmp/MetalToolchain2.0").str, tmpDir.join("tmp/MetalToolchain3.0").str, ], originalToolchain) - try await testExternalToolchainPath(withSetEnv: nil, expecting: [], originalToolchain) - try await testExternalToolchainPath(withSetEnv: "", expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: nil, expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: "", expecting: [], originalToolchain) // Environment overrides - try await testExternalToolchainPath(withSetEnv: nil, expecting: [], originalToolchain) // Clear + try await testExternalToolchainPath(toolchainPath: tmpDir, withSetEnv: nil, expecting: [], originalToolchain) // Clear - try await testExternalToolchainPath(environmentOverrides: ["Hello":"world"], expecting: [], originalToolchain) - try await testExternalToolchainPath(environmentOverrides: ["EXTERNAL_TOOLCHAINS_DIR": tmpDir.join("tmp/Foobar/MyDir").str], expecting: [tmpDir.join("tmp/Foobar/MyDir").str], originalToolchain) - try await testExternalToolchainPath(environmentOverrides: [:], expecting: [], originalToolchain) - try await testExternalToolchainPath(environmentOverrides: [ + try await testExternalToolchainPath(toolchainPath: tmpDir, environmentOverrides: ["Hello":"world"], expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, environmentOverrides: ["EXTERNAL_TOOLCHAINS_DIR": tmpDir.join("tmp/Foobar/MyDir").str], expecting: [tmpDir.join("tmp/Foobar/MyDir").str], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, environmentOverrides: [:], expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, environmentOverrides: [ "EXTERNAL_TOOLCHAINS_DIR" : [tmpDir.join("tmp/MetalToolchain1.0").str, tmpDir.join("tmp/MetalToolchain2.0").str, tmpDir.join("tmp/MetalToolchain3.0").str].joined(separator: String(Path.pathEnvironmentSeparator)), ], expecting: [ tmpDir.join("tmp/MetalToolchain1.0").str, tmpDir.join("tmp/MetalToolchain2.0").str, tmpDir.join("tmp/MetalToolchain3.0").str, ], originalToolchain) - try await testExternalToolchainPath(environmentOverrides: [:], expecting: [], originalToolchain) + try await testExternalToolchainPath(toolchainPath: tmpDir, environmentOverrides: [:], expecting: [], originalToolchain) } } func toolchainPathsCount() async throws -> Int { - let delegate = Delegate() - let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: []) - await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint()) - await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint()) - await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint()) - await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self) - struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { - .root + try await withTemporaryDirectory { tmpDir in + try localFS.createDirectory(tmpDir.join("Toolchains")) + let delegate = Delegate() + let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: []) + await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint()) + await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint()) + await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint()) + await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self) + struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension { + let toolchainPath: Path + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { + .swiftToolchain(toolchainPath, xcodeDeveloperPath: nil) + } } - } - struct MockToolchainExtension: ToolchainRegistryExtension { - func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] { - guard context.toolchainRegistry.lookup(ToolchainRegistry.defaultToolchainIdentifier) == nil else { - return [] + struct MockToolchainExtension: ToolchainRegistryExtension { + func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] { + guard context.toolchainRegistry.lookup(ToolchainRegistry.defaultToolchainIdentifier) == nil else { + return [] + } + return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)] } - return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)] } - } - await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self) - await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self) - let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess) - for diagnostic in delegate.diagnostics { - if diagnostic.formatLocalizedDescription(.debug).hasPrefix("warning: found previously-unknown deployment target macro ") { - continue + await pluginManager.register(MockDeveloperDirectoryExtensionPoint(toolchainPath: tmpDir), type: DeveloperDirectoryExtensionPoint.self) + await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self) + let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess) + for diagnostic in delegate.diagnostics { + if diagnostic.formatLocalizedDescription(.debug).hasPrefix("warning: found previously-unknown deployment target macro ") { + continue + } + Issue.record("\(diagnostic.formatLocalizedDescription(.debug))") } - Issue.record("\(diagnostic.formatLocalizedDescription(.debug))") + return try #require(core?.toolchainPaths).count } - return try #require(core?.toolchainPaths).count } - func testExternalToolchainPath(withSetEnv externalToolchainPathsString: String?, expecting expectedPathStrings: [String], _ originalToolchainCount: Int) async throws { + func testExternalToolchainPath(toolchainPath: Path, withSetEnv externalToolchainPathsString: String?, expecting expectedPathStrings: [String], _ originalToolchainCount: Int) async throws { var env = Environment.current.filter { $0.key != .externalToolchainsDir } if let externalToolchainPathsString { env[.externalToolchainsDir] = externalToolchainPathsString @@ -417,11 +424,11 @@ import SWBServiceCore try await withEnvironment(env, clean: true) { #expect(getEnvironmentVariable(.externalToolchainsDir) == externalToolchainPathsString) - try await testExternalToolchainPath(environmentOverrides: [:], expecting: expectedPathStrings, originalToolchainCount) + try await testExternalToolchainPath(toolchainPath: toolchainPath, environmentOverrides: [:], expecting: expectedPathStrings, originalToolchainCount) } } - func testExternalToolchainPath(environmentOverrides: [String:String], expecting expectedPathStrings: [String], _ originalToolchainCount: Int) async throws { + func testExternalToolchainPath(toolchainPath: Path, environmentOverrides: [String:String], expecting expectedPathStrings: [String], _ originalToolchainCount: Int) async throws { let delegate = Delegate() let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: []) await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint()) @@ -429,8 +436,9 @@ import SWBServiceCore await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint()) await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self) struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { - .root + let toolchainPath: Path + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { + .swiftToolchain(toolchainPath, xcodeDeveloperPath: nil) } } struct MockToolchainExtension: ToolchainRegistryExtension { @@ -441,7 +449,7 @@ import SWBServiceCore return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)] } } - await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self) + await pluginManager.register(MockDeveloperDirectoryExtensionPoint(toolchainPath: toolchainPath), type: DeveloperDirectoryExtensionPoint.self) await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self) let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: environmentOverrides, buildServiceModTime: Date(), connectionMode: .inProcess) for diagnostic in delegate.diagnostics { diff --git a/Tests/SWBCoreTests/PlatformRegistryTests.swift b/Tests/SWBCoreTests/PlatformRegistryTests.swift index 88dafa57..ae74a237 100644 --- a/Tests/SWBCoreTests/PlatformRegistryTests.swift +++ b/Tests/SWBCoreTests/PlatformRegistryTests.swift @@ -49,7 +49,7 @@ import SWBMacro } var developerPath: Core.DeveloperPath { - .fallback(Path.temporaryDirectory) + .swiftToolchain(.temporaryDirectory, xcodeDeveloperPath: nil) } } diff --git a/Tests/SWBCoreTests/ToolchainRegistryTests.swift b/Tests/SWBCoreTests/ToolchainRegistryTests.swift index e8f8753d..60510119 100644 --- a/Tests/SWBCoreTests/ToolchainRegistryTests.swift +++ b/Tests/SWBCoreTests/ToolchainRegistryTests.swift @@ -30,7 +30,10 @@ import SWBServiceCore infoPlistName: String = "ToolchainInfo.plist", postProcess: (Path) throws -> Void = { _ in }, perform: (ToolchainRegistry, [(String, String)], [(String, String)]) throws -> Void) async throws { - try await withTemporaryDirectory { tmpDirPath in + try await withTemporaryDirectory { baseTmpDirPath in + let tmpDirPath = baseTmpDirPath.join("tmp") + try fs.createDirectory(tmpDirPath) + try fs.createDirectory(baseTmpDirPath.join("Swift").join("Toolchains"), recursive: true) for (name, dataOpt) in inputs { let itemPath = tmpDirPath.join(name).join(infoPlistName) @@ -76,8 +79,9 @@ import SWBServiceCore await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint()) await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self) struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension { - func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? { - .root + let toolchainPath: Path + func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? { + .swiftToolchain(toolchainPath, xcodeDeveloperPath: nil) } } struct MockToolchainExtension: ToolchainRegistryExtension { @@ -88,7 +92,7 @@ import SWBServiceCore return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)] } } - await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self) + await pluginManager.register(MockDeveloperDirectoryExtensionPoint(toolchainPath: baseTmpDirPath.join("Swift")), type: DeveloperDirectoryExtensionPoint.self) await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self) let coreDelegate = TestingCoreDelegate() let core = await Core.getInitializedCore(coreDelegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess) @@ -172,7 +176,7 @@ import SWBServiceCore ], infoPlistName: "Info.plist") { registry, _, errors in #expect(Set(registry.toolchainsByIdentifier.keys) == Set(["org.swift.3020161114a", "org.swift.3020161115a"] + additionalToolchains)) - #expect(errors.count == 0) + #expect(errors.count == 0, "\(errors)") #expect(registry.lookup("org.swift.3020161115a")?.identifier == "org.swift.3020161115a") #expect(registry.lookup("org.swift.3020161114a")?.identifier == "org.swift.3020161114a") diff --git a/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift index c8cd71e9..7dfea111 100644 --- a/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/CustomTaskConstructionTests.swift @@ -59,7 +59,7 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) task.checkEnvironment(["ENVVAR": "VALUE"]) #expect(task.workingDirectory == Path.root.join("working/directory")) @@ -119,14 +119,14 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) - results.checkTaskDoesNotFollow(task, .matchRule(["CustomTask", "My Custom Task 2"])) + results.checkTaskDoesNotFollow(task, .matchRulePattern(["CustomTask", "My Custom Task 2", .any])) } - results.checkTask(.matchRule(["CustomTask", "My Custom Task 2"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task 2", .any])) { task in task.checkCommandLine(["tool2", "-bar", "-foo"]) - results.checkTaskDoesNotFollow(task, .matchRule(["CustomTask", "My Custom Task"])) + results.checkTaskDoesNotFollow(task, .matchRulePattern(["CustomTask", "My Custom Task", .any])) } } } @@ -170,7 +170,7 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkCommandLine(["tool", "-foo", "-bar"]) task.checkOutputs([ // Virtual output @@ -223,9 +223,64 @@ fileprivate struct CustomTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .host) { results in results.checkNoDiagnostics() - results.checkTask(.matchRule(["CustomTask", "My Custom Task"])) { task in + results.checkTask(.matchRulePattern(["CustomTask", "My Custom Task", .any])) { task in task.checkEnvironment(["ENVVAR": "VALUE", "MY_SETTING": "FOO"]) } } } + + @Test(.requireSDKs(.host)) + func customTasksWithDuplicateDescriptions() async throws { + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + TestFile("input.txt"), + TestFile("input2.txt"), + TestFile("main.c"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "auto", + ]), + ], + targets: [ + TestStandardTarget( + "CoreFoo", type: .framework, + buildPhases: [ + TestSourcesBuildPhase(["main.c"]) + ], + customTasks: [ + TestCustomTask( + commandLine: ["tool", "-foo", "-bar"], + environment: ["ENVVAR": "VALUE"], + workingDirectory: Path.root.join("working/directory").str, + executionDescription: "My Custom Task", + inputs: ["$(SRCROOT)/Sources/input.txt"], + outputs: [Path.root.join("output").str], + enableSandboxing: false, + preparesForIndexing: false), + TestCustomTask( + commandLine: ["tool", "-foo", "-bar"], + environment: ["ENVVAR": "VALUE"], + workingDirectory: Path.root.join("working/directory").str, + executionDescription: "My Custom Task", + inputs: ["$(SRCROOT)/Sources/input2.txt"], + outputs: [Path.root.join("output2").str], + enableSandboxing: false, + preparesForIndexing: false) + ] + ), + ]) + let tester = try await TaskConstructionTester(getCore(), testProject) + await tester.checkBuild(runDestination: .host) { results in + // Ensure we don't incorrectly diagnose duplicate custom tasks + results.checkNoDiagnostics() + } + } } diff --git a/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift index 42904bd0..79189a64 100644 --- a/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift @@ -4042,6 +4042,102 @@ fileprivate struct SwiftTaskConstructionTests: CoreBasedTests { } } + @Test(.requireSDKs(.macOS)) + func enableHardeningInSwift() async throws { + + func setupHardeningTest(_ tmpDir: Path, + hardeningMode: String) async throws -> TaskConstructionTester { + let testProject = try await TestProject( + "TestProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("source.swift"), + TestFile("source.cpp") + ]), + targets: [ + TestStandardTarget( + "testFramework", type: .framework, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "CLANG_CXX_STANDARD_LIBRARY_HARDENING": hardeningMode + ]), + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift", "source.cpp"]) + ] + ) + ]) + let tester = try await TaskConstructionTester(getCore(), testProject) + return tester + } + + // Verify that we don't enable hardening in Swift compilations when C++ + // hardening is none. + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "none") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContainsUninterrupted(["-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE"]) + } + } + } + + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "fast") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContainsUninterrupted(["-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST"]) + } + } + } + + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "extensive") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContainsUninterrupted(["-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"]) + } + } + } + + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "debug") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContainsUninterrupted(["-Xcc", "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG"]) + } + } + } + + // Verify that we don't enable hardening in Swift compilations when C++ + // hardening mode is garbage. + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "unexpected") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineNoMatch([.prefix("-D_LIBCPP_HARDENING_MODE=")]) + } + } + } + + // Verify that we don't enable hardening in Swift compilations when C++ + // hardening mode is empty. + try await withTemporaryDirectory { tmpDir in + let tester = try await setupHardeningTest(tmpDir, hardeningMode: "") + await tester.checkBuild(runDestination: .macOS) { results in + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineNoMatch([.prefix("-D_LIBCPP_HARDENING_MODE=")]) + } + } + } + } + @Test(.requireSDKs(.macOS)) func cxxInteropLinkerArgGeneration() async throws { // When Swift is generating additional linker args, we should not try to inject the response file when a target is a dependent of a cxx-interop target but has no Swift source of its own.