diff --git a/README.md b/README.md index d0d58d7d6..fa49d4e59 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ The goal of the new Swift driver is to provide a drop-in replacement for the exi * Platform support * [x] Teach the `DarwinToolchain` to also handle iOS, tvOS, watchOS * [x] Fill out the `GenericUnixToolchain` toolchain to get it working - * [ ] Implement a `WindowsToolchain` + * [x] Implement a `WindowsToolchain` * [x] Implement proper tokenization for response files * Compilation modes * [x] Batch mode diff --git a/Sources/SwiftDriver/CMakeLists.txt b/Sources/SwiftDriver/CMakeLists.txt index 4fd92ba7c..b464e6d36 100644 --- a/Sources/SwiftDriver/CMakeLists.txt +++ b/Sources/SwiftDriver/CMakeLists.txt @@ -1,6 +1,6 @@ # This source file is part of the Swift.org open source project # -# Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +# Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See http://swift.org/LICENSE.txt for license information @@ -58,10 +58,12 @@ add_library(SwiftDriver Jobs/Toolchain+LinkerSupport.swift Jobs/VerifyDebugInfoJob.swift Jobs/VerifyModuleInterfaceJob.swift + Jobs/WindowsToolchain+LinkerSupport.swift Toolchains/DarwinToolchain.swift Toolchains/GenericUnixToolchain.swift Toolchains/Toolchain.swift + Toolchains/WindowsToolchain.swift Utilities/Bits.swift Utilities/Bitstream.swift diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 2cd9caad1..ba2aa52e0 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -370,6 +370,9 @@ public struct Driver { self.numParallelJobs = Self.determineNumParallelJobs(&parsedOptions, diagnosticsEngine: diagnosticEngine, env: env) Self.validateWarningControlArgs(&parsedOptions, diagnosticEngine: diagnosticEngine) + Self.validateCRuntimeArgs(&parsedOptions, + diagnosticEngine: diagnosticsEngine, + targetTriple: self.frontendTargetInfo.target.triple) Self.validateProfilingArgs(&parsedOptions, fileSystem: fileSystem, workingDirectory: workingDirectory, @@ -1299,6 +1302,11 @@ extension Driver { sanitizerSupported = false } + // Currently only ASAN is supported on Windows. + if sanitizer != .address && targetTriple.isWindows { + sanitizerSupported = false + } + if !sanitizerSupported { diagnosticEngine.emit( .error_unsupported_opt_for_target( @@ -1655,6 +1663,14 @@ extension Driver { } } + static func validateCRuntimeArgs(_ parsedOptions: inout ParsedOptions, + diagnosticEngine: DiagnosticsEngine, + targetTriple: Triple) { + if parsedOptions.hasArgument(.libc) && !targetTriple.isWindows { + diagnosticEngine.emit(.error_unsupported_opt_for_target(arg: "-libc", target: targetTriple)) + } + } + static func validateProfilingArgs(_ parsedOptions: inout ParsedOptions, fileSystem: FileSystem, workingDirectory: AbsolutePath?, @@ -1718,7 +1734,7 @@ extension Triple { case .freeBSD, .haiku: return GenericUnixToolchain.self case .win32: - fatalError("Windows target not supported yet") + return WindowsToolchain.self default: diagnosticsEngine.emit(.error_unknown_target(triple)) throw Diagnostics.fatalError @@ -1731,7 +1747,7 @@ extension Driver { #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) static let defaultToolchainType: Toolchain.Type = DarwinToolchain.self #elseif os(Windows) - static let defaultToolchainType: Toolchain.Type = { fatalError("Windows target not supported yet") }() + static let defaultToolchainType: Toolchain.Type = WindowsToolchain.self #else static let defaultToolchainType: Toolchain.Type = GenericUnixToolchain.self #endif diff --git a/Sources/SwiftDriver/Jobs/CommandLineArguments.swift b/Sources/SwiftDriver/Jobs/CommandLineArguments.swift index 06e76a188..b560d4f8b 100644 --- a/Sources/SwiftDriver/Jobs/CommandLineArguments.swift +++ b/Sources/SwiftDriver/Jobs/CommandLineArguments.swift @@ -165,10 +165,10 @@ extension Array where Element == Job.ArgTemplate { var joinedArguments: String { return self.map { switch $0 { - case .flag(let string): - return string.spm_shellEscaped() - case .path(let path): - return path.name.spm_shellEscaped() + case .flag(let string): + return string.spm_shellEscaped() + case .path(let path): + return path.name.spm_shellEscaped() case .responseFilePath(let path): return "@\(path.name.spm_shellEscaped())" case let .joinedOptionAndPath(option, path): diff --git a/Sources/SwiftDriver/Jobs/Toolchain+InterpreterSupport.swift b/Sources/SwiftDriver/Jobs/Toolchain+InterpreterSupport.swift index fc9e9628c..ab860276e 100644 --- a/Sources/SwiftDriver/Jobs/Toolchain+InterpreterSupport.swift +++ b/Sources/SwiftDriver/Jobs/Toolchain+InterpreterSupport.swift @@ -81,3 +81,14 @@ extension GenericUnixToolchain { return envVars } } + +extension WindowsToolchain { + public func platformSpecificInterpreterEnvironmentVariables( + env: [String : String], + parsedOptions: inout ParsedOptions, + sdkPath: String?, + targetTriple: Triple) throws -> [String: String] { + // TODO: See whether Windows needs `platformSpecificInterpreterEnvironmentVariables` + return [:] + } +} diff --git a/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift b/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift index a9ad52e12..f495f09d5 100644 --- a/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift +++ b/Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift @@ -30,6 +30,12 @@ extension Toolchain { resourceDirBase = sdkPath .appending(components: "usr", "lib", isShared ? "swift" : "swift_static") + } else if triple.isWindows, + let SDKROOT = env["SDKROOT"], + let sdkPath = try? AbsolutePath(validating: SDKROOT) { + resourceDirBase = sdkPath + .appending(components: "usr", "lib", + isShared ? "swift" : "swift_static") } else { resourceDirBase = try getToolPath(.swiftCompiler) .parentDirectory // remove /swift @@ -48,12 +54,20 @@ extension Toolchain { for triple: Triple, parsedOptions: inout ParsedOptions ) throws -> AbsolutePath { - return try computeResourceDirPath(for: triple, - parsedOptions: &parsedOptions, - isShared: true) - .parentDirectory // Remove platform name. - .appending(components: "clang", "lib", - triple.platformName(conflatingDarwin: true)!) + #if os(Windows) + return try getToolPath(.swiftCompiler) + .parentDirectory // remove /swift + .parentDirectory // remove /bin + .appending(components: "lib", "swift", "clang", "lib", + triple.platformName(conflatingDarwin: true)!) + #else + return try computeResourceDirPath(for: triple, + parsedOptions: &parsedOptions, + isShared: true) + .parentDirectory // Remove platform name. + .appending(components: "clang", "lib", + triple.platformName(conflatingDarwin: true)!) + #endif } func runtimeLibraryPaths( @@ -258,3 +272,5 @@ extension DarwinToolchain { } } + +// TODO: Implement `addArgsToLinkStdlib` for `WindowsToolchain`. diff --git a/Sources/SwiftDriver/Jobs/WindowsToolchain+LinkerSupport.swift b/Sources/SwiftDriver/Jobs/WindowsToolchain+LinkerSupport.swift new file mode 100644 index 000000000..02a75498d --- /dev/null +++ b/Sources/SwiftDriver/Jobs/WindowsToolchain+LinkerSupport.swift @@ -0,0 +1,190 @@ +//===---------------- WindowsToolchain+LinkerSupport.swift ----------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import TSCBasic +import SwiftOptions + +extension WindowsToolchain { + public func addPlatformSpecificLinkerArgs( + to commandLine: inout [Job.ArgTemplate], + parsedOptions: inout ParsedOptions, + linkerOutputType: LinkOutputType, + inputs: [TypedVirtualPath], + outputFile: VirtualPath, + shouldUseInputFileList: Bool, + sdkPath: String?, + sanitizers: Set, + targetInfo: FrontendTargetInfo + ) throws -> AbsolutePath { + let targetTriple = targetInfo.target.triple + + switch linkerOutputType { + case .dynamicLibrary: + commandLine.appendFlags("-Xlinker", "-dll") + fallthrough + case .executable: + if !targetTriple.triple.isEmpty { + commandLine.appendFlag("-target") + commandLine.appendFlag(targetTriple.triple) + } + // Configure the toolchain. + // + // By default use the system `clang` to perform the link. We use `clang` for + // the driver here because we do not wish to select a particular C++ runtime. + // Furthermore, until C++ interop is enabled, we cannot have a dependency on + // C++ code from pure Swift code. If linked libraries are C++ based, they + // should properly link C++. In the case of static linking, the user can + // explicitly specify the C++ runtime to link against. This is particularly + // important for platforms like android where as it is a Linux platform, the + // default C++ runtime is `libstdc++` which is unsupported on the target but + // as the builds are usually cross-compiled from Linux, libstdc++ is going to + // be present. This results in linking the wrong version of libstdc++ + // generating invalid binaries. It is also possible to use different C++ + // runtimes than the default C++ runtime for the platform (e.g. libc++ on + // Windows rather than msvcprt). When C++ interop is enabled, we will need to + // surface this via a driver flag. For now, opt for the simpler approach of + // just using `clang` and avoid a dependency on the C++ runtime. + var clangPath = try getToolPath(.clang) + if let toolsDirPath = parsedOptions.getLastArgument(.toolsDirectory) { + // FIXME: What if this isn't an absolute path? + let toolsDir = try AbsolutePath(validating: toolsDirPath.asSingle) + + // If there is a clang in the toolchain folder, use that instead. + if let tool = lookupExecutablePath(filename: "clang.exe", searchPaths: [toolsDir]) { + clangPath = tool + } + + // Look for binutils in the toolchain folder. + commandLine.appendFlag("-B") + commandLine.appendPath(toolsDir) + } + + let linker: String + if let arg = parsedOptions.getLastArgument(.useLd) { + linker = arg.asSingle + } else { + linker = "link" + } + commandLine.appendFlag("-fuse-ld=\(linker)") + + // FIXME: Do we really need `oldnames`? + commandLine.appendFlags("-autolink-library", "oldnames") + if let crt = parsedOptions.getLastArgument(.libc) { + switch crt.asSingle { + case "MT": commandLine.appendFlags("-autolink-library", "libcmt") + case "MTd": commandLine.appendFlags("-autolink-library", "libcmtd") + case "MD": commandLine.appendFlags("-autolink-library", "msvcrt") + case "MDd": commandLine.appendFlags("-autolink-library", "msvcrtd") + default: fatalError("Invalid C runtime value should be filtered") + } + } else { + commandLine.appendFlags("-autolink-library", "msvcrt") + } + + let staticStdlib = parsedOptions.hasFlag(positive: .staticStdlib, + negative: .noStaticStdlib, + default: false) + let staticExecutable = parsedOptions.hasFlag(positive: .staticExecutable, + negative: .noStaticExecutable, + default: false) + let hasRuntimeArgs = !(staticStdlib || staticExecutable) + + let runtimePaths = try runtimeLibraryPaths( + for: targetTriple, + parsedOptions: &parsedOptions, + sdkPath: sdkPath, + isShared: hasRuntimeArgs + ) + + let sharedResourceDirPath = try computeResourceDirPath( + for: targetTriple, + parsedOptions: &parsedOptions, + isShared: true + ) + + let swiftrtPath = sharedResourceDirPath.appending( + components: targetTriple.archName, "swiftrt.obj" + ) + commandLine.appendPath(swiftrtPath) + + let inputFiles: [Job.ArgTemplate] = inputs.compactMap { input in + // Autolink inputs are handled specially + if input.type == .autolink { + return .responseFilePath(input.file) + } else if input.type == .object { + return .path(input.file) + } else { + return nil + } + } + commandLine.append(contentsOf: inputFiles) + + let fSystemArgs = parsedOptions.arguments(for: .F, .Fsystem) + for opt in fSystemArgs { + if opt.option == .Fsystem { + commandLine.appendFlag("-iframework") + } else { + commandLine.appendFlag(.F) + } + commandLine.appendPath(try VirtualPath(path: opt.argument.asSingle)) + } + + // Add the runtime library link paths. + for path in runtimePaths { + commandLine.appendFlag(.L) + commandLine.appendPath(path) + } + + if hasRuntimeArgs { + commandLine.appendFlag("-lswiftCore") + } + + // Explicitly pass the target to the linker + commandLine.appendFlags("-target", targetTriple.triple) + + // Delegate to Clang for sanitizers. It will figure out the correct linker + // options. + if linkerOutputType == .executable && !sanitizers.isEmpty { + let sanitizerNames = sanitizers + .map { $0.rawValue } + .sorted() // Sort so we get a stable, testable order + .joined(separator: ",") + commandLine.appendFlag("-fsanitize=\(sanitizerNames)") + + // The TSan runtime depends on the blocks runtime and libdispatch. + if sanitizers.contains(.thread) { + commandLine.appendFlag("-lBlocksRuntime") + commandLine.appendFlag("-ldispatch") + } + } + + // Run clang++ in verbose mode if "-v" is set + try commandLine.appendLast(.v, from: &parsedOptions) + + // These custom arguments should be right before the object file at the + // end. + try commandLine.append( + contentsOf: parsedOptions.arguments(in: .linkerOption) + ) + try commandLine.appendAllArguments(.Xlinker, from: &parsedOptions) + try commandLine.appendAllArguments(.XclangLinker, from: &parsedOptions) + + // This should be the last option, for convenience in checking output. + commandLine.appendFlag(.o) + commandLine.appendPath(outputFile) + return clangPath + case .staticLibrary: + commandLine.append(.joinedOptionAndPath("-out:", outputFile)) + commandLine.append(contentsOf: inputs.map { .path($0.file) }) + return try getToolPath(.staticLinker) + } + } +} diff --git a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift index e63ed010b..9469bc3f1 100644 --- a/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift +++ b/Sources/SwiftDriver/Toolchains/DarwinToolchain.swift @@ -20,15 +20,15 @@ import SwiftOptions public final class DarwinToolchain: Toolchain { public let env: [String: String] - /// Doubles as path cache and point for overriding normal lookup - private var toolPaths = [Tool: AbsolutePath]() - /// The executor used to run processes used to find tools and retrieve target info. public let executor: DriverExecutor /// The file system to use for any file operations. public let fileSystem: FileSystem + /// Doubles as path cache and point for overriding normal lookup + private var toolPaths = [Tool: AbsolutePath]() + public init(env: [String: String], executor: DriverExecutor, fileSystem: FileSystem = localFileSystem) { self.env = env self.executor = executor diff --git a/Sources/SwiftDriver/Toolchains/Toolchain.swift b/Sources/SwiftDriver/Toolchains/Toolchain.swift index 11ec15ab4..ebfe27107 100644 --- a/Sources/SwiftDriver/Toolchains/Toolchain.swift +++ b/Sources/SwiftDriver/Toolchains/Toolchain.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -129,20 +129,26 @@ extension Toolchain { /// looks in the `executableDir`, `xcrunFind` or in the `searchPaths`. /// - Parameter executable: executable to look for [i.e. `swift`]. func lookup(executable: String) throws -> AbsolutePath { + let filename: String + #if os(Windows) + filename = "\(executable).exe" + #else + filename = executable + #endif if let overrideString = envVar(forExecutable: executable) { return try AbsolutePath(validating: overrideString) - } else if let path = lookupExecutablePath(filename: executable, searchPaths: [executableDir]) { + } else if let path = lookupExecutablePath(filename: filename, searchPaths: [executableDir]) { return path } else if let path = try? xcrunFind(executable: executable) { return path } else if !["swift-frontend", "swift"].contains(executable), let parentDirectory = try? getToolPath(.swiftCompiler).parentDirectory, parentDirectory != executableDir, - let path = lookupExecutablePath(filename: executable, searchPaths: [parentDirectory]) { + let path = lookupExecutablePath(filename: filename, searchPaths: [parentDirectory]) { // If the driver library's client and the frontend are in different directories, // try looking for tools next to the frontend. return path - } else if let path = lookupExecutablePath(filename: executable, searchPaths: searchPaths) { + } else if let path = lookupExecutablePath(filename: filename, searchPaths: searchPaths) { return path } else if executable == "swift-frontend" { // Temporary shim: fall back to looking for "swift" before failing. diff --git a/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift b/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift new file mode 100644 index 000000000..1659e9ca4 --- /dev/null +++ b/Sources/SwiftDriver/Toolchains/WindowsToolchain.swift @@ -0,0 +1,141 @@ +//===--------- WindowsToolchain.swift - Swift Windows Toolchain -----------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import TSCBasic +import SwiftOptions + +/// Toolchain for Windows. +public final class WindowsToolchain: Toolchain { + public let env: [String: String] + + /// The executor used to run processes used to find tools and retrieve target info. + public let executor: DriverExecutor + + /// The file system to use for queries. + public let fileSystem: FileSystem + + /// Doubles as path cache and point for overriding normal lookup + private var toolPaths = [Tool: AbsolutePath]() + + public func archName(for triple: Triple) -> String { + switch triple.arch { + case .aarch64: return "aarch64" + case .arm: return "armv7" + case .x86: return "i386" + case nil, .x86_64: return "x86_64" + default: fatalError("unknown arch \(triple.archName) for Windows") + } + } + + public init(env: [String: String], executor: DriverExecutor, fileSystem: FileSystem = localFileSystem) { + self.env = env + self.executor = executor + self.fileSystem = fileSystem + } + + public func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String { + switch type { + case .executable: return "\(moduleName).exe" + case .dynamicLibrary: return "\(moduleName).dll" + case .staticLibrary: return "lib\(moduleName).lib" + } + } + + /// Retrieve the absolute path for a given tool. + public func getToolPath(_ tool: Tool) throws -> AbsolutePath { + // Check the cache + if let toolPath = toolPaths[tool] { + return toolPath + } + let path = try lookupToolPath(tool) + // Cache the path + toolPaths[tool] = path + return path + } + + private func lookupToolPath(_ tool: Tool) throws -> AbsolutePath { + switch tool { + case .swiftCompiler: + return try lookup(executable: "swift-frontend") + case .staticLinker: + return try lookup(executable: "lib") + case .dynamicLinker: + // FIXME: This needs to look in the tools_directory first. + return try lookup(executable: "link") + case .clang: + return try lookup(executable: "clang") + case .swiftAutolinkExtract: + return try lookup(executable: "swift-autolink-extract") + case .dsymutil: + return try lookup(executable: "llvm-dsymutil") + case .lldb: + return try lookup(executable: "lldb") + case .dwarfdump: + return try lookup(executable: "llvm-dwarfdump") + case .swiftHelp: + return try lookup(executable: "swift-help") + } + } + + public func overrideToolPath(_ tool: Tool, path: AbsolutePath) { + toolPaths[tool] = path + } + + public func defaultSDKPath(_ target: Triple?) throws -> AbsolutePath? { + return nil + } + + public var shouldStoreInvocationInDebugInfo: Bool { false } + + public func runtimeLibraryName( + for sanitizer: Sanitizer, + targetTriple: Triple, + isShared: Bool + ) throws -> String { + return "clang_rt.\(sanitizer.libraryName)-\(archName(for: targetTriple)).lib" + } +} + +extension WindowsToolchain { + public func validateArgs(_ parsedOptions: inout ParsedOptions, + targetTriple: Triple, + targetVariantTriple: Triple?, + diagnosticsEngine: DiagnosticsEngine) throws { + // Windows executables should be profiled with ETW, whose support needs to be + // implemented before we can enable the option. + if parsedOptions.hasArgument(.profileGenerate) { + throw ToolchainValidationError.argumentNotSupported("-profile-generate") + } + if parsedOptions.hasArgument(.profileUse) { + throw ToolchainValidationError.argumentNotSupported("-profile-use=") + } + + if let crt = parsedOptions.getLastArgument(.libc) { + if !["MT", "MTd", "MD", "MDd"].contains(crt.asSingle) { + throw ToolchainValidationError.illegalCrtName(crt.asSingle) + } + } + } + + public enum ToolchainValidationError: Error, DiagnosticData { + case argumentNotSupported(String) + case illegalCrtName(String) + + public var description: String { + switch self { + case .argumentNotSupported(let argument): + return "\(argument) is not supported for Windows" + case .illegalCrtName(let argument): + return "\(argument) is not a valid C Runtime for Windows" + } + } + } +}