diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index cc1156fca..66100b43c 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -269,6 +269,10 @@ extension Driver { commandLine.appendPath(localPluginPath) } + if isFrontendArgSupported(.externalPluginPath) { + try commandLine.appendAll(.externalPluginPath, from: &parsedOptions) + } + // Pass down -user-module-version if we are working with a compiler that // supports it. if let ver = parsedOptions.getLastArgument(.userModuleVersion)?.asSingle, diff --git a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift index ea1ebd1ef..ae08c02ed 100644 --- a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift @@ -373,6 +373,32 @@ public final class DarwinToolchain: Toolchain { frontendTargetInfo: FrontendTargetInfo, driver: inout Driver ) throws { + // Pass -external-plugin-path if the current toolchain is not a Xcode + // default toolchain. + if + driver.isFrontendArgSupported(.externalPluginPath), + let xcodeDir = try self.findCurrentSelectedXcodeDir(), + try !self.executableDir.isDescendant(of: xcodeDir), + let xcodeExecutableDir = try self.findXcodeExecutableDir() + { + let xcodePluginServerPath = xcodeExecutableDir + .appending(component: "swift-plugin-server") + + if fileSystem.isExecutableFile(xcodePluginServerPath) { + let xcodeToolchainUsrPath = xcodeExecutableDir.parentDirectory + + let xcodePluginPath = xcodeToolchainUsrPath + .appending(components: "lib", "swift", "host", "plugins") + commandLine.appendFlag(.externalPluginPath) + commandLine.appendFlag(xcodePluginPath.pathString + "#" + xcodePluginServerPath.pathString) + + let xcodeLocalPluginPath = xcodeToolchainUsrPath + .appending(components: "local", "lib", "swift", "host", "plugins") + commandLine.appendFlag(.externalPluginPath) + commandLine.appendFlag(xcodeLocalPluginPath.pathString + "#" + xcodePluginServerPath.pathString) + } + } + guard let sdkPath = frontendTargetInfo.sdkPath?.path, let sdkInfo = getTargetSDKInfo(sdkPath: sdkPath) else { return } @@ -441,3 +467,38 @@ private extension Version { return self.description } } + +extension DarwinToolchain { + func findXcodeExecutableDir() throws -> AbsolutePath? { +#if os(macOS) + let result = try executor.checkNonZeroExit( + args: "xcrun", "-toolchain", "default", "-f", "swiftc", + environment: env + ).trimmingCharacters(in: .whitespacesAndNewlines) + + guard !result.isEmpty else { + return nil + } + return try AbsolutePath(validating: result) + .parentDirectory // swiftc +#else + return nil +#endif + } + + func findCurrentSelectedXcodeDir() throws -> AbsolutePath? { +#if os(macOS) + let result = try executor.checkNonZeroExit( + args: "xcode-select", "-p", + environment: env + ).trimmingCharacters(in: .whitespacesAndNewlines) + + guard !result.isEmpty else { + return nil + } + return try AbsolutePath(validating: result) +#else + return nil +#endif + } +} diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index 109a91a9c..0ff67e672 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -54,6 +54,7 @@ extension Option { public static let checkOnoneCompleteness: Option = Option("-check-onone-completeness", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Print errors if the compile OnoneSupport module is missing symbols") public static let clangBuildSessionFile: Option = Option("-clang-build-session-file", .separate, attributes: [.frontend, .argumentIsPath], helpText: "Use the last modification time of as the underlying Clang build session timestamp") public static let clangHeaderExposeDecls: Option = Option("-clang-header-expose-decls=", .joined, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "all-public|has-expose-attr", helpText: "Which declarations should be exposed in the generated clang header.") + public static let clangHeaderExposeModule: Option = Option("-clang-header-expose-module", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "=", helpText: "Allow the compiler to assume that APIs from the specified module are exposed to C/C++/Objective-C in another generated header, so that APIs in the current module that depend on declarations from the specified module can be exposed in the generated header.") public static let clangTarget: Option = Option("-clang-target", .separate, attributes: [.frontend], helpText: "Separately set the target we should use for internal Clang instance") public static let codeCompleteCallPatternHeuristics: Option = Option("-code-complete-call-pattern-heuristics", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Use heuristics to guess whether we want call pattern completions") public static let codeCompleteInitsInPostfixExpr: Option = Option("-code-complete-inits-in-postfix-expr", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Include initializers when completing a postfix expression") @@ -156,6 +157,8 @@ extension Option { public static let disableInferPublicConcurrentValue: Option = Option("-disable-infer-public-sendable", .flag, attributes: [.frontend, .noDriver], helpText: "Disable inference of Sendable conformances for public structs and enums") public static let disableInterfaceLockfile: Option = Option("-disable-interface-lock", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Don't lock interface file when building module") public static let disableInvalidEphemeralnessAsError: Option = Option("-disable-invalid-ephemeralness-as-error", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Diagnose invalid ephemeral to non-ephemeral conversions as warnings") + public static let disableLayoutStringValueWitnessesInstantiation: Option = Option("-disable-layout-string-value-witnesses-instantiation", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable runtime instantiation of layout string value witnesses for generic types") + public static let disableLayoutStringValueWitnesses: Option = Option("-disable-layout-string-value-witnesses", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable layout string based value witnesses") public static let disableLegacyTypeInfo: Option = Option("-disable-legacy-type-info", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Completely disable legacy type layout") public static let disableLlvmOptzns: Option = Option("-disable-llvm-optzns", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Don't run LLVM optimization passes") public static let disableLlvmValueNames: Option = Option("-disable-llvm-value-names", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Don't add names to local values in LLVM IR") @@ -357,11 +360,12 @@ extension Option { public static let enableExplicitExistentialTypes: Option = Option("-enable-explicit-existential-types", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable experimental support for explicit existential types") public static let enableImplicitBacktracingModuleImport: Option = Option("-enable-implicit-backtracing-module-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable the implicit import of the _Backtracing module.") public static let enableImplicitDynamic: Option = Option("-enable-implicit-dynamic", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Add 'dynamic' to all declarations") - public static let enableImportObjcForwardDeclarations: Option = Option("-enable-import-objc-forward-declarations", .flag, attributes: [.helpHidden, .frontend, .noDriver, .moduleInterface], helpText: "Attempt to import Objective-C forward declarations") public static let enableImportPtrauthFieldFunctionPointers: Option = Option("-enable-import-ptrauth-field-function-pointers", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable import of custom ptrauth qualified field function pointers") public static let enableIncrementalImports: Option = Option("-enable-incremental-imports", .flag, attributes: [.frontend], helpText: "Enable cross-module incremental build metadata and driver scheduling for Swift modules") public static let enableInferPublicConcurrentValue: Option = Option("-enable-infer-public-sendable", .flag, attributes: [.frontend, .noDriver], helpText: "Enable inference of Sendable conformances for public structs and enums") public static let enableInvalidEphemeralnessAsError: Option = Option("-enable-invalid-ephemeralness-as-error", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Diagnose invalid ephemeral to non-ephemeral conversions as errors") + public static let enableLayoutStringValueWitnessesInstantiation: Option = Option("-enable-layout-string-value-witnesses-instantiation", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable runtime instantiation of layout string value witnesses for generic types") + public static let enableLayoutStringValueWitnesses: Option = Option("-enable-layout-string-value-witnesses", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable layout string based value witnesses") public static let enableLexicalBorrowScopes: Option = Option("-enable-lexical-borrow-scopes=", .joined, attributes: [.helpHidden, .frontend, .noDriver, .moduleInterface], metaVar: "true|false", helpText: "Whether to emit lexical borrow scopes (default: true)") public static let enableLexicalLifetimes: Option = Option("-enable-lexical-lifetimes=", .joined, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "true|false", helpText: "Whether to enable lexical lifetimes") public static let enableLexicalLifetimesNoArg: Option = Option("-enable-lexical-lifetimes", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable lexical lifetimes") @@ -423,6 +427,7 @@ extension Option { public static let explicitSwiftModuleMap: Option = Option("-explicit-swift-module-map-file", .separate, attributes: [.frontend, .noDriver], metaVar: "", helpText: "Specify a JSON file containing information of explicit Swift modules") public static let exportAs: Option = Option("-export-as", .separate, attributes: [.frontend], helpText: "Module name to use when referenced in clients module interfaces") public static let externalPassPipelineFilename: Option = Option("-external-pass-pipeline-filename", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "", helpText: "Use the pass pipeline defined by ") + public static let externalPluginPath: Option = Option("-external-plugin-path", .separate, attributes: [.frontend, .argumentIsPath], metaVar: "#", helpText: "Add directory to the plugin search path with a plugin server executable") public static let e: Option = Option("-e", .separate, attributes: [], helpText: "Executes a line of code provided on the command line") public static let FEQ: Option = Option("-F=", .joined, alias: Option.F, attributes: [.frontend, .argumentIsPath]) public static let fileCompilationDir: Option = Option("-file-compilation-dir", .separate, attributes: [.frontend], metaVar: "", helpText: "The compilation directory to embed in the debug info. Coverage mapping is not supported yet.") @@ -711,6 +716,7 @@ extension Option { public static let typecheckModuleFromInterface: Option = Option("-typecheck-module-from-interface", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Treat the (single) input as a swiftinterface and typecheck it", group: .modes) public static let typecheck: Option = Option("-typecheck", .flag, attributes: [.frontend, .noInteractive, .doesNotAffectIncrementalBuild], helpText: "Parse and type-check input file(s)", group: .modes) public static let typoCorrectionLimit: Option = Option("-typo-correction-limit", .separate, attributes: [.helpHidden, .frontend], metaVar: "", helpText: "Limit the number of times the compiler will attempt typo correction to ") + public static let unavailableDeclOptimizationEQ: Option = Option("-unavailable-decl-optimization=", .joined, attributes: [.frontend, .noInteractive], metaVar: "", helpText: "Specify the optimization mode for unavailable declarations. The value may be 'none' (no optimization) or 'complete' (code is not generated at all unavailable declarations)") public static let updateCode: Option = Option("-update-code", .flag, attributes: [.helpHidden, .frontend, .noInteractive, .doesNotAffectIncrementalBuild], helpText: "Update Swift code") public static let useClangFunctionTypes: Option = Option("-use-clang-function-types", .flag, attributes: [.frontend, .noDriver], helpText: "Use stored Clang function types for computing canonical types.") public static let useFrontendParseableOutput: Option = Option("-use-frontend-parseable-output", .flag, attributes: [.helpHidden], helpText: "Emit parseable-output from swift-frontend jobs instead of from the driver") @@ -816,6 +822,7 @@ extension Option { Option.checkOnoneCompleteness, Option.clangBuildSessionFile, Option.clangHeaderExposeDecls, + Option.clangHeaderExposeModule, Option.clangTarget, Option.codeCompleteCallPatternHeuristics, Option.codeCompleteInitsInPostfixExpr, @@ -918,6 +925,8 @@ extension Option { Option.disableInferPublicConcurrentValue, Option.disableInterfaceLockfile, Option.disableInvalidEphemeralnessAsError, + Option.disableLayoutStringValueWitnessesInstantiation, + Option.disableLayoutStringValueWitnesses, Option.disableLegacyTypeInfo, Option.disableLlvmOptzns, Option.disableLlvmValueNames, @@ -1119,11 +1128,12 @@ extension Option { Option.enableExplicitExistentialTypes, Option.enableImplicitBacktracingModuleImport, Option.enableImplicitDynamic, - Option.enableImportObjcForwardDeclarations, Option.enableImportPtrauthFieldFunctionPointers, Option.enableIncrementalImports, Option.enableInferPublicConcurrentValue, Option.enableInvalidEphemeralnessAsError, + Option.enableLayoutStringValueWitnessesInstantiation, + Option.enableLayoutStringValueWitnesses, Option.enableLexicalBorrowScopes, Option.enableLexicalLifetimes, Option.enableLexicalLifetimesNoArg, @@ -1185,6 +1195,7 @@ extension Option { Option.explicitSwiftModuleMap, Option.exportAs, Option.externalPassPipelineFilename, + Option.externalPluginPath, Option.e, Option.FEQ, Option.fileCompilationDir, @@ -1473,6 +1484,7 @@ extension Option { Option.typecheckModuleFromInterface, Option.typecheck, Option.typoCorrectionLimit, + Option.unavailableDeclOptimizationEQ, Option.updateCode, Option.useClangFunctionTypes, Option.useFrontendParseableOutput, diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index 2170ac14a..4f54956d5 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -6727,6 +6727,55 @@ final class SwiftDriverTests: XCTestCase { XCTAssertTrue(job.commandLine.contains(.path(.absolute(try driver.toolchain.executableDir.parentDirectory.appending(components: "local", "lib", "swift", "host", "plugins"))))) } + func testExternalPluginPaths() throws { +#if !os(macOS) + throw XCTSkip("Supported only in macOS") +#endif + + try withTemporaryDirectory { tmpDir in + var driver = try Driver(args: ["swiftc", "-typecheck", "foo.swift"], + compilerExecutableDir: tmpDir) + guard driver.isFrontendArgSupported(.externalPluginPath) else { + return + } + + let jobs = try driver.planBuild().removingAutolinkExtractJobs() + XCTAssertEqual(jobs.count, 1) + let job = jobs.first! + + // This happens only Xcode toolchain has 'swift-plugin-server' which we + // don't know. + let idx1 = job.commandLine.firstIndex(of: .flag("-external-plugin-path")) + try XCTSkipIf(idx1 == nil) + switch job.commandLine[job.commandLine.index(after: idx1!)] { + case .flag(let value): + let components = value.split(separator: "#") + if components.count == 2 { + XCTAssertTrue(components[0].hasSuffix("/usr/lib/swift/host/plugins")) + XCTAssertTrue(components[1].hasSuffix("/usr/bin/swift-plugin-server")) + } else { + XCTFail("# separated count must 2") + } + default: + XCTFail("invalid arg type after '-external-plugin-path'") + } + + let idx2 = job.commandLine[job.commandLine.index(after: idx1!)...].firstIndex(of: .flag("-external-plugin-path")) + switch job.commandLine[job.commandLine.index(after: try XCTUnwrap(idx2))] { + case .flag(let value): + let components = value.split(separator: "#") + if (components.count == 2) { + XCTAssertTrue(components[0].hasSuffix("/usr/local/lib/swift/host/plugins")) + XCTAssertTrue(components[1].hasSuffix("/usr/bin/swift-plugin-server")) + } else { + XCTFail("# separated count must 2") + } + default: + XCTFail("invalid arg type after '-external-plugin-path'") + } + } + } + func testClangModuleValidateOnce() throws { let flagTest = try Driver(args: ["swiftc", "-typecheck", "foo.swift"]) guard flagTest.isFrontendArgSupported(.clangBuildSessionFile), diff --git a/Tests/TestUtilities/DriverExtensions.swift b/Tests/TestUtilities/DriverExtensions.swift index b19a7171d..9aafc2fba 100644 --- a/Tests/TestUtilities/DriverExtensions.swift +++ b/Tests/TestUtilities/DriverExtensions.swift @@ -23,7 +23,8 @@ extension Driver { env: [String: String] = ProcessEnv.vars, diagnosticsEngine: DiagnosticsEngine = DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]), fileSystem: FileSystem = localFileSystem, - integratedDriver: Bool = true + integratedDriver: Bool = true, + compilerExecutableDir: AbsolutePath? = nil ) throws { let executor = try SwiftDriverExecutor(diagnosticsEngine: diagnosticsEngine, processSet: ProcessSet(), @@ -34,7 +35,8 @@ extension Driver { diagnosticsEngine: diagnosticsEngine, fileSystem: fileSystem, executor: executor, - integratedDriver: integratedDriver) + integratedDriver: integratedDriver, + compilerExecutableDir: compilerExecutableDir) } /// For tests that need to set the sdk path.