diff --git a/Package.swift b/Package.swift index e373d830c..902ebbed9 100644 --- a/Package.swift +++ b/Package.swift @@ -27,15 +27,25 @@ let package = Package( .library( name: "SwiftOptions", targets: ["SwiftOptions"]), + .library( + name: "SwiftDriverExecution", + targets: ["SwiftDriverExecution"]), ], targets: [ /// The driver library. .target( name: "SwiftDriver", dependencies: ["SwiftOptions", "SwiftToolsSupport-auto", "Yams"]), + + /// The execution library. + .target( + name: "SwiftDriverExecution", + dependencies: ["SwiftDriver", "SwiftToolsSupport-auto"]), + + /// Driver tests. .testTarget( name: "SwiftDriverTests", - dependencies: ["SwiftDriver", "swift-driver"]), + dependencies: ["SwiftDriver", "SwiftDriverExecution", "swift-driver"]), /// The options library. .target( @@ -48,7 +58,7 @@ let package = Package( /// The primary driver executable. .target( name: "swift-driver", - dependencies: ["SwiftDriver"]), + dependencies: ["SwiftDriverExecution", "SwiftDriver"]), /// The help executable. .target( @@ -75,7 +85,7 @@ if ProcessInfo.processInfo.environment["SWIFT_DRIVER_LLBUILD_FWK"] == nil { .package(path: "../llbuild"), ] } - package.targets.first(where: { $0.name == "SwiftDriver" })!.dependencies += ["llbuildSwift"] + package.targets.first(where: { $0.name == "SwiftDriverExecution" })!.dependencies += ["llbuildSwift"] } if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index f9cf91983..4df57ecfc 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -8,5 +8,6 @@ add_subdirectory(SwiftOptions) add_subdirectory(SwiftDriver) +add_subdirectory(SwiftDriverExecution) add_subdirectory(swift-driver) add_subdirectory(swift-help) diff --git a/Sources/SwiftDriver/CMakeLists.txt b/Sources/SwiftDriver/CMakeLists.txt index 4fd92ba7c..eeb6a9d05 100644 --- a/Sources/SwiftDriver/CMakeLists.txt +++ b/Sources/SwiftDriver/CMakeLists.txt @@ -24,11 +24,9 @@ add_library(SwiftDriver Driver/ToolExecutionDelegate.swift Execution/ArgsResolver.swift - Execution/MultiJobExecutor.swift Execution/DriverExecutor.swift Execution/ParsableOutput.swift Execution/ProcessProtocol.swift - Execution/llbuild.swift "Incremental Compilation/IncrementalCompilationState.swift" "Incremental Compilation/InputIInfoMap.swift" @@ -84,8 +82,7 @@ add_library(SwiftDriver target_link_libraries(SwiftDriver PUBLIC TSCBasic TSCUtility - SwiftOptions - llbuildSwift) + SwiftOptions) target_link_libraries(SwiftDriver PRIVATE CYaml Yams) diff --git a/Sources/SwiftDriver/Driver/OutputFileMap.swift b/Sources/SwiftDriver/Driver/OutputFileMap.swift index 2b628ac21..3be83338c 100644 --- a/Sources/SwiftDriver/Driver/OutputFileMap.swift +++ b/Sources/SwiftDriver/Driver/OutputFileMap.swift @@ -12,8 +12,6 @@ import TSCBasic import Foundation -public typealias FileSystem = TSCBasic.FileSystem - /// Mapping of input file paths to specific output files. public struct OutputFileMap: Hashable, Codable { static let singleInputKey = VirtualPath.relative(RelativePath("")) diff --git a/Sources/SwiftDriver/Execution/DriverExecutor.swift b/Sources/SwiftDriver/Execution/DriverExecutor.swift index 4e7a02270..d7810183c 100644 --- a/Sources/SwiftDriver/Execution/DriverExecutor.swift +++ b/Sources/SwiftDriver/Execution/DriverExecutor.swift @@ -91,87 +91,3 @@ public protocol JobExecutionDelegate { /// Called when a job finished. func jobFinished(job: Job, result: ProcessResult, pid: Int) } - -public final class SwiftDriverExecutor: DriverExecutor { - let diagnosticsEngine: DiagnosticsEngine - let processSet: ProcessSet - let fileSystem: FileSystem - public let resolver: ArgsResolver - let env: [String: String] - - public init(diagnosticsEngine: DiagnosticsEngine, - processSet: ProcessSet, - fileSystem: FileSystem, - env: [String: String]) throws { - self.diagnosticsEngine = diagnosticsEngine - self.processSet = processSet - self.fileSystem = fileSystem - self.env = env - self.resolver = try ArgsResolver(fileSystem: fileSystem) - } - - public func execute(job: Job, - forceResponseFiles: Bool = false, - recordedInputModificationDates: [TypedVirtualPath: Date] = [:]) throws -> ProcessResult { - let arguments: [String] = try resolver.resolveArgumentList(for: job, - forceResponseFiles: forceResponseFiles) - - try job.verifyInputsNotModified(since: recordedInputModificationDates, - fileSystem: fileSystem) - - if job.requiresInPlaceExecution { - for (envVar, value) in job.extraEnvironment { - try ProcessEnv.setVar(envVar, value: value) - } - - try exec(path: arguments[0], args: arguments) - fatalError("unreachable, exec always throws on failure") - } else { - var childEnv = env - childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new }) - - let process = try Process.launchProcess(arguments: arguments, env: childEnv) - return try process.waitUntilExit() - } - } - - public func execute(jobs: [Job], - delegate: JobExecutionDelegate, - numParallelJobs: Int = 1, - forceResponseFiles: Bool = false, - recordedInputModificationDates: [TypedVirtualPath: Date] = [:] - ) throws { - let llbuildExecutor = MultiJobExecutor(jobs: jobs, - resolver: resolver, - executorDelegate: delegate, - diagnosticsEngine: diagnosticsEngine, - numParallelJobs: numParallelJobs, - processSet: processSet, - forceResponseFiles: forceResponseFiles, - recordedInputModificationDates: recordedInputModificationDates) - try llbuildExecutor.execute(env: env, fileSystem: fileSystem) - } - - @discardableResult - public func checkNonZeroExit(args: String..., environment: [String: String] = ProcessEnv.vars) throws -> String { - return try Process.checkNonZeroExit(arguments: args, environment: environment) - } - - public func description(of job: Job, forceResponseFiles: Bool) throws -> String { - let (args, usedResponseFile) = try resolver.resolveArgumentList(for: job, forceResponseFiles: forceResponseFiles) - var result = args.joined(separator: " ") - - if usedResponseFile { - // Print the response file arguments as a comment. - result += " # \(job.commandLine.joinedArguments)" - } - - if !job.extraEnvironment.isEmpty { - result += " #" - for (envVar, val) in job.extraEnvironment { - result += " \(envVar)=\(val)" - } - } - return result - } -} diff --git a/Sources/SwiftDriver/Execution/llbuild.swift b/Sources/SwiftDriver/Execution/llbuild.swift deleted file mode 100644 index 2aa6d01a5..000000000 --- a/Sources/SwiftDriver/Execution/llbuild.swift +++ /dev/null @@ -1,263 +0,0 @@ -//===--------------- llbuild.swift - Swift LLBuild Interaction ------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 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 -// -//===----------------------------------------------------------------------===// - -// FIXME: This is directly copied from SwiftPM, consider moving this to llbuild. - -// We either export the llbuildSwift shared library or the llbuild framework. -#if canImport(llbuildSwift) -@_exported import llbuildSwift -@_exported import llbuild -#else -@_exported import llbuild -#endif - -import Foundation - -/// An llbuild value. -public protocol LLBuildValue: Codable { -} - -/// An llbuild key. -public protocol LLBuildKey: Codable { - /// The value that this key computes. - associatedtype BuildValue: LLBuildValue - - /// The rule that this key operates on. - associatedtype BuildRule: LLBuildRule -} - -public protocol LLBuildEngineDelegate { - func lookupRule(rule: String, key: Key) -> Rule -} - -public final class LLBuildEngine { - - enum Error: Swift.Error, CustomStringConvertible { - case failed(errors: [String]) - - var description: String { - switch self { - case .failed(let errors): - return errors.joined(separator: "\n") - } - } - } - - fileprivate final class Delegate: BuildEngineDelegate { - let delegate: LLBuildEngineDelegate - var errors: [String] = [] - - init(_ delegate: LLBuildEngineDelegate) { - self.delegate = delegate - } - - func lookupRule(_ key: Key) -> Rule { - let ruleKey = RuleKey(key) - return delegate.lookupRule( - rule: ruleKey.rule, key: Key(ruleKey.data)) - } - - func error(_ message: String) { - errors.append(message) - } - } - - private let engine: BuildEngine - private let delegate: Delegate - - public init(delegate: LLBuildEngineDelegate) { - self.delegate = Delegate(delegate) - engine = BuildEngine(delegate: self.delegate) - } - - deinit { - engine.close() - } - - public func build(key: T) throws -> T.BuildValue { - // Clear out any errors from the previous build. - delegate.errors.removeAll() - - let encodedKey = RuleKey( - rule: T.BuildRule.ruleName, data: key.toKey().data).toKey() - let value = engine.build(key: encodedKey) - - // Throw if the engine encountered any fatal error during the build. - if !delegate.errors.isEmpty || value.data.isEmpty { - throw Error.failed(errors: delegate.errors) - } - - return try T.BuildValue(value) - } - - public func attachDB(path: String, schemaVersion: Int = 2) throws { - try engine.attachDB(path: path, schemaVersion: schemaVersion) - } - - public func close() { - engine.close() - } -} - -// FIXME: Rename to something else. -public class LLTaskBuildEngine { - - let engine: TaskBuildEngine - let fileSystem: FileSystem - - init(_ engine: TaskBuildEngine, fileSystem: FileSystem) { - self.engine = engine - self.fileSystem = fileSystem - } - - public func taskNeedsInput(_ key: T, inputID: Int) { - let encodedKey = RuleKey( - rule: T.BuildRule.ruleName, data: key.toKey().data).toKey() - engine.taskNeedsInput(encodedKey, inputID: inputID) - } - - public func taskIsComplete(_ result: T) { - engine.taskIsComplete(result.toValue(), forceChange: false) - } -} - -/// An individual build rule. -open class LLBuildRule: Rule, Task { - - /// The name of the rule. - /// - /// This name will be available in the delegate's lookupRule(rule:key:). - open class var ruleName: String { - fatalError("subclass responsibility") - } - - let fileSystem: FileSystem - - public init(fileSystem: FileSystem) { - self.fileSystem = fileSystem - } - - public func createTask() -> Task { - return self - } - - public func start(_ engine: TaskBuildEngine) { - self.start(LLTaskBuildEngine(engine, fileSystem: fileSystem)) - } - - public func provideValue(_ engine: TaskBuildEngine, inputID: Int, value: Value) { - self.provideValue(LLTaskBuildEngine(engine, fileSystem: fileSystem), inputID: inputID, value: value) - } - - public func inputsAvailable(_ engine: TaskBuildEngine) { - self.inputsAvailable(LLTaskBuildEngine(engine, fileSystem: fileSystem)) - } - - // MARK:- - - open func isResultValid(_ priorValue: Value) -> Bool { - return true - } - - open func start(_ engine: LLTaskBuildEngine) { - } - - open func provideValue(_ engine: LLTaskBuildEngine, inputID: Int, value: Value) { - } - - open func inputsAvailable(_ engine: LLTaskBuildEngine) { - } -} - -// MARK:- Helpers - -private struct RuleKey: Codable { - - let rule: String - let data: [UInt8] - - init(rule: String, data: [UInt8]) { - self.rule = rule - self.data = data - } - - init(_ key: Key) { - self.init(key.data) - } - - init(_ data: [UInt8]) { - self = try! fromBytes(data) - } - - func toKey() -> Key { - return try! Key(toBytes(self)) - } -} - -public extension LLBuildKey { - init(_ key: Key) { - self.init(key.data) - } - - init(_ data: [UInt8]) { - do { - self = try fromBytes(data) - } catch { - let stringValue: String - if let str = String(bytes: data, encoding: .utf8) { - stringValue = str - } else { - stringValue = String(describing: data) - } - fatalError("Please file a bug at https://bugs.swift.org with this info -- LLBuildKey: ###\(error)### ----- ###\(stringValue)###") - } - } - - func toKey() -> Key { - return try! Key(toBytes(self)) - } -} - -public extension LLBuildValue { - init(_ value: Value) throws { - do { - self = try fromBytes(value.data) - } catch { - let stringValue: String - if let str = String(bytes: value.data, encoding: .utf8) { - stringValue = str - } else { - stringValue = String(describing: value.data) - } - fatalError("Please file a bug at https://bugs.swift.org with this info -- LLBuildValue: ###\(error)### ----- ###\(stringValue)###") - } - } - - func toValue() -> Value { - return try! Value(toBytes(self)) - } -} - -private func fromBytes(_ bytes: [UInt8]) throws -> T { - var bytes = bytes - let data = Data(bytes: &bytes, count: bytes.count) - return try JSONDecoder().decode(T.self, from: data) -} - -private func toBytes(_ value: T) throws -> [UInt8] { - let encoder = JSONEncoder() - if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { - encoder.outputFormatting = [.sortedKeys] - } - let encoded = try encoder.encode(value) - return [UInt8](encoded) -} diff --git a/Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift b/Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift index 8429b1437..6dca7ed36 100644 --- a/Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift +++ b/Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift @@ -83,7 +83,7 @@ extension Driver { let success = batchScanResult.exitStatus == .terminated(code: EXIT_SUCCESS) guard success else { throw JobExecutionError.jobFailedWithNonzeroExitCode( - SwiftDriverExecutor.computeReturnCode(exitStatus: batchScanResult.exitStatus), + type(of: executor).computeReturnCode(exitStatus: batchScanResult.exitStatus), try batchScanResult.utf8stderrOutput()) } diff --git a/Sources/SwiftDriver/Utilities/Diagnostics.swift b/Sources/SwiftDriver/Utilities/Diagnostics.swift index 4967d419a..e9759dba9 100644 --- a/Sources/SwiftDriver/Utilities/Diagnostics.swift +++ b/Sources/SwiftDriver/Utilities/Diagnostics.swift @@ -12,9 +12,6 @@ import TSCBasic import SwiftOptions -public typealias Diagnostic = TSCBasic.Diagnostic -public typealias DiagnosticData = TSCBasic.DiagnosticData - extension Diagnostic.Message { static var error_static_emit_executable_disallowed: Diagnostic.Message { .error("-static may not be used with -emit-executable") diff --git a/Sources/SwiftDriverExecution/CMakeLists.txt b/Sources/SwiftDriverExecution/CMakeLists.txt new file mode 100644 index 000000000..c42a4decb --- /dev/null +++ b/Sources/SwiftDriverExecution/CMakeLists.txt @@ -0,0 +1,31 @@ +# This source file is part of the Swift.org open source project +# +# Copyright (c) 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 +# See http://swift.org/CONTRIBUTORS.txt for Swift project authors + +add_library(SwiftDriverExecution + llbuild.swift + MultiJobExecutor.swift + SwiftDriverExecutor.swift) + +target_link_libraries(SwiftDriverExecution PUBLIC + TSCBasic + TSCUtility + SwiftOptions + SwiftDriver) +target_link_libraries(SwiftDriverExecution PRIVATE + llbuildSwift) + +set_property(GLOBAL APPEND PROPERTY SWIFTDRIVER_EXPORTS SwiftDriverExecution) + +# NOTE: workaround for CMake not setting up include flags yet +set_target_properties(SwiftDriverExecution PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +install(TARGETS SwiftDriverExecution + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) diff --git a/Sources/SwiftDriver/Execution/MultiJobExecutor.swift b/Sources/SwiftDriverExecution/MultiJobExecutor.swift similarity index 94% rename from Sources/SwiftDriver/Execution/MultiJobExecutor.swift rename to Sources/SwiftDriverExecution/MultiJobExecutor.swift index 2cacc4fe7..cb2a8c4a1 100644 --- a/Sources/SwiftDriver/Execution/MultiJobExecutor.swift +++ b/Sources/SwiftDriverExecution/MultiJobExecutor.swift @@ -1,4 +1,4 @@ -//===--------------- JobExecutor.swift - Swift Job Execution --------------===// +//===------- MultiJobExecutor.swift - LLBuild-powered job executor --------===// // // This source file is part of the Swift.org open source project // @@ -9,11 +9,22 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// + import TSCBasic import enum TSCUtility.Diagnostics import Foundation import Dispatch +import SwiftDriver + +// We either import the llbuildSwift shared library or the llbuild framework. +#if canImport(llbuildSwift) +@_implementationOnly import llbuildSwift +@_implementationOnly import llbuild +#else +@_implementationOnly import llbuild +#endif + public final class MultiJobExecutor { @@ -33,7 +44,7 @@ public final class MultiJobExecutor { let env: [String: String] /// The file system. - let fileSystem: FileSystem + let fileSystem: TSCBasic.FileSystem /// The job executor delegate. let executorDelegate: JobExecutionDelegate @@ -62,7 +73,7 @@ public final class MultiJobExecutor { init( argsResolver: ArgsResolver, env: [String: String], - fileSystem: FileSystem, + fileSystem: TSCBasic.FileSystem, producerMap: [VirtualPath: Int], jobs: [Job], executorDelegate: JobExecutionDelegate, @@ -138,7 +149,7 @@ public final class MultiJobExecutor { } /// Execute all jobs. - public func execute(env: [String: String], fileSystem: FileSystem) throws { + public func execute(env: [String: String], fileSystem: TSCBasic.FileSystem) throws { let context = createContext(jobs, env: env, fileSystem: fileSystem) let delegate = JobExecutorBuildDelegate(context) @@ -153,7 +164,7 @@ public final class MultiJobExecutor { } /// Create the context required during the execution. - func createContext(_ jobs: [Job], env: [String: String], fileSystem: FileSystem) -> Context { + func createContext(_ jobs: [Job], env: [String: String], fileSystem: TSCBasic.FileSystem) -> Context { var producerMap: [VirtualPath: Int] = [:] for (index, job) in jobs.enumerated() { for output in job.outputs { @@ -234,7 +245,7 @@ class ExecuteAllJobsRule: LLBuildRule { /// True if any of the inputs had any error. private var allInputsSucceeded: Bool = true - init(_ key: Key, jobs: [Job], fileSystem: FileSystem) { + init(_ key: Key, jobs: [Job], fileSystem: TSCBasic.FileSystem) { self.key = RuleKey(key) self.jobs = jobs super.init(fileSystem: fileSystem) @@ -394,12 +405,12 @@ class ExecuteJobRule: LLBuildRule { extension Job: LLBuildValue { } -private extension Diagnostic.Message { - static func error_command_failed(kind: Job.Kind, code: Int32) -> Diagnostic.Message { +private extension TSCBasic.Diagnostic.Message { + static func error_command_failed(kind: Job.Kind, code: Int32) -> TSCBasic.Diagnostic.Message { .error("\(kind.rawValue) command failed with exit code \(code) (use -v to see invocation)") } - static func error_command_signalled(kind: Job.Kind, signal: Int32) -> Diagnostic.Message { + static func error_command_signalled(kind: Job.Kind, signal: Int32) -> TSCBasic.Diagnostic.Message { .error("\(kind.rawValue) command failed due to signal \(signal) (use -v to see invocation)") } } diff --git a/Sources/SwiftDriverExecution/SwiftDriverExecutor.swift b/Sources/SwiftDriverExecution/SwiftDriverExecutor.swift new file mode 100644 index 000000000..f9353b9b3 --- /dev/null +++ b/Sources/SwiftDriverExecution/SwiftDriverExecutor.swift @@ -0,0 +1,99 @@ +//===--- MultiJobExecutor.swift - Builtin DriverExecutor implementation ---===// +// +// 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 SwiftDriver +import TSCBasic +import Foundation + +public final class SwiftDriverExecutor: DriverExecutor { + let diagnosticsEngine: DiagnosticsEngine + let processSet: ProcessSet + let fileSystem: FileSystem + public let resolver: ArgsResolver + let env: [String: String] + + public init(diagnosticsEngine: DiagnosticsEngine, + processSet: ProcessSet, + fileSystem: FileSystem, + env: [String: String]) throws { + self.diagnosticsEngine = diagnosticsEngine + self.processSet = processSet + self.fileSystem = fileSystem + self.env = env + self.resolver = try ArgsResolver(fileSystem: fileSystem) + } + + public func execute(job: Job, + forceResponseFiles: Bool = false, + recordedInputModificationDates: [TypedVirtualPath: Date] = [:]) throws -> ProcessResult { + let arguments: [String] = try resolver.resolveArgumentList(for: job, + forceResponseFiles: forceResponseFiles) + + try job.verifyInputsNotModified(since: recordedInputModificationDates, + fileSystem: fileSystem) + + if job.requiresInPlaceExecution { + for (envVar, value) in job.extraEnvironment { + try ProcessEnv.setVar(envVar, value: value) + } + + try exec(path: arguments[0], args: arguments) + fatalError("unreachable, exec always throws on failure") + } else { + var childEnv = env + childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new }) + + let process = try Process.launchProcess(arguments: arguments, env: childEnv) + return try process.waitUntilExit() + } + } + + public func execute(jobs: [Job], + delegate: JobExecutionDelegate, + numParallelJobs: Int = 1, + forceResponseFiles: Bool = false, + recordedInputModificationDates: [TypedVirtualPath: Date] = [:] + ) throws { + let llbuildExecutor = MultiJobExecutor(jobs: jobs, + resolver: resolver, + executorDelegate: delegate, + diagnosticsEngine: diagnosticsEngine, + numParallelJobs: numParallelJobs, + processSet: processSet, + forceResponseFiles: forceResponseFiles, + recordedInputModificationDates: recordedInputModificationDates) + try llbuildExecutor.execute(env: env, fileSystem: fileSystem) + } + + @discardableResult + public func checkNonZeroExit(args: String..., environment: [String: String] = ProcessEnv.vars) throws -> String { + return try Process.checkNonZeroExit(arguments: args, environment: environment) + } + + public func description(of job: Job, forceResponseFiles: Bool) throws -> String { + let (args, usedResponseFile) = try resolver.resolveArgumentList(for: job, forceResponseFiles: forceResponseFiles) + var result = args.joined(separator: " ") + + if usedResponseFile { + // Print the response file arguments as a comment. + result += " # \(job.commandLine.joinedArguments)" + } + + if !job.extraEnvironment.isEmpty { + result += " #" + for (envVar, val) in job.extraEnvironment { + result += " \(envVar)=\(val)" + } + } + return result + } +} diff --git a/Sources/SwiftDriverExecution/llbuild.swift b/Sources/SwiftDriverExecution/llbuild.swift new file mode 100644 index 000000000..80e8e077a --- /dev/null +++ b/Sources/SwiftDriverExecution/llbuild.swift @@ -0,0 +1,265 @@ +//===--------------- llbuild.swift - Swift LLBuild Interaction ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// FIXME: This is slightly modified from the SwiftPM version, +// consider moving this to llbuild. + +import Foundation +import TSCBasic + +// We either import the llbuildSwift shared library or the llbuild framework. +#if canImport(llbuildSwift) +@_implementationOnly import llbuildSwift +@_implementationOnly import llbuild +#else +@_implementationOnly import llbuild +#endif + +/// An llbuild value. +protocol LLBuildValue: Codable { +} + +/// An llbuild key. +protocol LLBuildKey: Codable { + /// The value that this key computes. + associatedtype BuildValue: LLBuildValue + + /// The rule that this key operates on. + associatedtype BuildRule: LLBuildRule +} + +protocol LLBuildEngineDelegate { + func lookupRule(rule: String, key: Key) -> Rule +} + +final class LLBuildEngine { + + enum Error: Swift.Error, CustomStringConvertible { + case failed(errors: [String]) + + var description: String { + switch self { + case .failed(let errors): + return errors.joined(separator: "\n") + } + } + } + + fileprivate final class Delegate: BuildEngineDelegate { + let delegate: LLBuildEngineDelegate + var errors: [String] = [] + + init(_ delegate: LLBuildEngineDelegate) { + self.delegate = delegate + } + + func lookupRule(_ key: Key) -> Rule { + let ruleKey = RuleKey(key) + return delegate.lookupRule( + rule: ruleKey.rule, key: Key(ruleKey.data)) + } + + func error(_ message: String) { + errors.append(message) + } + } + + private let engine: BuildEngine + private let delegate: Delegate + + init(delegate: LLBuildEngineDelegate) { + self.delegate = Delegate(delegate) + engine = BuildEngine(delegate: self.delegate) + } + + deinit { + engine.close() + } + + func build(key: T) throws -> T.BuildValue { + // Clear out any errors from the previous build. + delegate.errors.removeAll() + + let encodedKey = RuleKey( + rule: T.BuildRule.ruleName, data: key.toKey().data).toKey() + let value = engine.build(key: encodedKey) + + // Throw if the engine encountered any fatal error during the build. + if !delegate.errors.isEmpty || value.data.isEmpty { + throw Error.failed(errors: delegate.errors) + } + + return try T.BuildValue(value) + } + + func attachDB(path: String, schemaVersion: Int = 2) throws { + try engine.attachDB(path: path, schemaVersion: schemaVersion) + } + + func close() { + engine.close() + } +} + +// FIXME: Rename to something else. +class LLTaskBuildEngine { + + let engine: TaskBuildEngine + let fileSystem: TSCBasic.FileSystem + + init(_ engine: TaskBuildEngine, fileSystem: TSCBasic.FileSystem) { + self.engine = engine + self.fileSystem = fileSystem + } + + func taskNeedsInput(_ key: T, inputID: Int) { + let encodedKey = RuleKey( + rule: T.BuildRule.ruleName, data: key.toKey().data).toKey() + engine.taskNeedsInput(encodedKey, inputID: inputID) + } + + func taskIsComplete(_ result: T) { + engine.taskIsComplete(result.toValue(), forceChange: false) + } +} + +/// An individual build rule. +class LLBuildRule: Rule, Task { + + /// The name of the rule. + /// + /// This name will be available in the delegate's lookupRule(rule:key:). + class var ruleName: String { + fatalError("subclass responsibility") + } + + let fileSystem: TSCBasic.FileSystem + + init(fileSystem: TSCBasic.FileSystem) { + self.fileSystem = fileSystem + } + + func createTask() -> Task { + return self + } + + func start(_ engine: TaskBuildEngine) { + self.start(LLTaskBuildEngine(engine, fileSystem: fileSystem)) + } + + func provideValue(_ engine: TaskBuildEngine, inputID: Int, value: Value) { + self.provideValue(LLTaskBuildEngine(engine, fileSystem: fileSystem), inputID: inputID, value: value) + } + + func inputsAvailable(_ engine: TaskBuildEngine) { + self.inputsAvailable(LLTaskBuildEngine(engine, fileSystem: fileSystem)) + } + + // MARK:- + + func isResultValid(_ priorValue: Value) -> Bool { + return true + } + + func start(_ engine: LLTaskBuildEngine) { + } + + func provideValue(_ engine: LLTaskBuildEngine, inputID: Int, value: Value) { + } + + func inputsAvailable(_ engine: LLTaskBuildEngine) { + } +} + +// MARK:- Helpers + +private struct RuleKey: Codable { + + let rule: String + let data: [UInt8] + + init(rule: String, data: [UInt8]) { + self.rule = rule + self.data = data + } + + init(_ key: Key) { + self.init(key.data) + } + + init(_ data: [UInt8]) { + self = try! fromBytes(data) + } + + func toKey() -> Key { + return try! Key(toBytes(self)) + } +} + +extension LLBuildKey { + init(_ key: Key) { + self.init(key.data) + } + + init(_ data: [UInt8]) { + do { + self = try fromBytes(data) + } catch { + let stringValue: String + if let str = String(bytes: data, encoding: .utf8) { + stringValue = str + } else { + stringValue = String(describing: data) + } + fatalError("Please file a bug at https://bugs.swift.org with this info -- LLBuildKey: ###\(error)### ----- ###\(stringValue)###") + } + } + + func toKey() -> Key { + return try! Key(toBytes(self)) + } +} + +extension LLBuildValue { + init(_ value: Value) throws { + do { + self = try fromBytes(value.data) + } catch { + let stringValue: String + if let str = String(bytes: value.data, encoding: .utf8) { + stringValue = str + } else { + stringValue = String(describing: value.data) + } + fatalError("Please file a bug at https://bugs.swift.org with this info -- LLBuildValue: ###\(error)### ----- ###\(stringValue)###") + } + } + + func toValue() -> Value { + return try! Value(toBytes(self)) + } +} + +private func fromBytes(_ bytes: [UInt8]) throws -> T { + var bytes = bytes + let data = Data(bytes: &bytes, count: bytes.count) + return try JSONDecoder().decode(T.self, from: data) +} + +private func toBytes(_ value: T) throws -> [UInt8] { + let encoder = JSONEncoder() + if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { + encoder.outputFormatting = [.sortedKeys] + } + let encoded = try encoder.encode(value) + return [UInt8](encoded) +} diff --git a/Sources/swift-driver/CMakeLists.txt b/Sources/swift-driver/CMakeLists.txt index 8507a7382..ae486e807 100644 --- a/Sources/swift-driver/CMakeLists.txt +++ b/Sources/swift-driver/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(swift-driver main.swift) target_link_libraries(swift-driver PUBLIC - SwiftDriver) + SwiftDriver + SwiftDriverExecution) diff --git a/Sources/swift-driver/main.swift b/Sources/swift-driver/main.swift index 34b112596..6ded8cbe2 100644 --- a/Sources/swift-driver/main.swift +++ b/Sources/swift-driver/main.swift @@ -9,6 +9,7 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import SwiftDriverExecution import SwiftDriver import TSCLibc diff --git a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift index 947899e78..5416061ea 100644 --- a/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift +++ b/Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift @@ -12,6 +12,7 @@ import Foundation @_spi(Testing) import SwiftDriver +import SwiftDriverExecution import TSCBasic import XCTest diff --git a/Tests/SwiftDriverTests/Helpers/DriverExtensions.swift b/Tests/SwiftDriverTests/Helpers/DriverExtensions.swift index 73a1737b3..40b22aaed 100644 --- a/Tests/SwiftDriverTests/Helpers/DriverExtensions.swift +++ b/Tests/SwiftDriverTests/Helpers/DriverExtensions.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import SwiftDriver +import SwiftDriverExecution import TSCBasic extension Driver { diff --git a/Tests/SwiftDriverTests/JobExecutorTests.swift b/Tests/SwiftDriverTests/JobExecutorTests.swift index c1a8e35c9..ad61177c0 100644 --- a/Tests/SwiftDriverTests/JobExecutorTests.swift +++ b/Tests/SwiftDriverTests/JobExecutorTests.swift @@ -14,6 +14,7 @@ import TSCBasic import TSCUtility @_spi(Testing) import SwiftDriver +import SwiftDriverExecution extension Job.ArgTemplate: ExpressibleByStringLiteral { public init(stringLiteral value: String) { diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index b07c1b70d..843c398cb 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation @_spi(Testing) import SwiftDriver +import SwiftDriverExecution import SwiftOptions import TSCBasic import XCTest