blob: 52522f82f84811d49e52748c51883c57fb4727af [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright 2017 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
// This file contains Swift bindings for the llbuild C API.
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
import Foundation
import llbuild
#if !LLBUILD_C_API_VERSION_6
#if swift(>=4.2)
#error("Unsupported llbuild C API version")
#else
import llbuild_c_api_version_unsupported
#endif
#endif
private func bytesFromData(_ data: llb_data_t) -> [UInt8] {
return Array(UnsafeBufferPointer(start: data.data, count: Int(data.length)))
}
/// Create a new `llb_data_t` instance containing an allocated copy of the given `bytes`.
private func copiedDataFromBytes(_ bytes: [UInt8]) -> llb_data_t {
// Create the data.
let buf = UnsafeMutableBufferPointer(start: UnsafeMutablePointer<UInt8>.allocate(capacity: bytes.count), count: bytes.count)
// Copy the data.
memcpy(buf.baseAddress!, UnsafePointer<UInt8>(bytes), buf.count)
// Fill in the result structure.
return llb_data_t(length: UInt64(buf.count), data: unsafeBitCast(buf.baseAddress, to: UnsafePointer<UInt8>.self))
}
// FIXME: We should eventually eliminate the need for this.
private func stringFromData(_ data: llb_data_t) -> String {
return String(decoding: UnsafeBufferPointer(start: data.data, count: Int(data.length)), as: Unicode.UTF8.self)
}
public protocol Tool: class {
/// Called to create a specific command instance of this tool.
func createCommand(_ name: String) -> ExternalCommand
}
private final class ToolWrapper {
let tool: Tool
init(tool: Tool) {
self.tool = tool
}
/// The owning list of all created commands.
//
// FIXME: This is lame, we should be able to destroy these naturally.
private var commandWrappers: [CommandWrapper] = []
func createCommand(_ name: UnsafePointer<llb_data_t>) -> OpaquePointer? {
let command = tool.createCommand(stringFromData(name.pointee))
let wrapper = CommandWrapper(command: command)
self.commandWrappers.append(wrapper)
var _delegate = llb_buildsystem_external_command_delegate_t()
_delegate.context = Unmanaged.passUnretained(wrapper).toOpaque()
_delegate.get_signature = { return BuildSystem.toCommandWrapper($0!).getSignature($1!, $2!) }
_delegate.execute_command = { return BuildSystem.toCommandWrapper($0!).executeCommand($1!, $2!, $3!, $4!) }
// Create the low-level command.
wrapper._command = Command(llb_buildsystem_external_command_create(name, _delegate))
return wrapper._command.handle
}
}
public protocol ExternalCommand: class {
/// Get a signature used to identify the internal state of the command.
///
/// This is checked to determine if the command needs to rebuild versus the last time it was run.
func getSignature(_ command: Command) -> [UInt8]
/// Called to execute the given command.
///
/// - command: A handle to the executing command.
/// - returns: True on success.
func execute(_ command: Command) -> Bool
}
// FIXME: The terminology is really confusing here, we have ExternalCommand which is divorced from the actual internal command implementation of the same name.
private final class CommandWrapper {
let command: ExternalCommand
var _command: Command
init(command: ExternalCommand) {
self.command = command
self._command = Command(nil)
}
func getSignature(_: OpaquePointer, _ data: UnsafeMutablePointer<llb_data_t>) {
data.pointee = copiedDataFromBytes(command.getSignature(_command))
}
func executeCommand(_: OpaquePointer, _ bsci: OpaquePointer, _ task: OpaquePointer, _ jobContext: OpaquePointer) -> Bool {
return command.execute(_command)
}
}
/// Encapsulates a diagnostic as reported by the build system.
public struct Diagnostic {
public enum Kind: CustomStringConvertible {
case note
case warning
case error
public init(_ kind: llb_buildsystem_diagnostic_kind_t) {
switch kind {
case llb_buildsystem_diagnostic_kind_note:
self = .note
case llb_buildsystem_diagnostic_kind_warning:
self = .warning
default:
self = .error
}
}
public var description: String {
switch self {
case .note:
return "note"
case .warning:
return "warning"
case .error:
return "error"
}
}
}
/// The kind of diagnostic.
public let kind: Kind
/// The diagnostic location, if provided.
public let location: (filename: String, line: Int, column: Int)?
/// The diagnostic text.
public let message: String
}
/// Handle for a command as invoked by the low-level BuildSystem.
public struct Command: Hashable {
fileprivate let handle: OpaquePointer?
fileprivate init(_ handle: OpaquePointer?) {
self.handle = handle
}
/// The command name.
//
// FIXME: We shouldn't need to expose this to use for mapping purposes, we should be able to use something more efficient.
public var name: String {
var data = llb_data_t()
withUnsafeMutablePointer(to: &data) { (ptr: UnsafeMutablePointer<llb_data_t>) in
llb_buildsystem_command_get_name(handle, ptr)
}
return stringFromData(data)
}
/// Whether the default status reporting shows status for the command.
public var shouldShowStatus: Bool {
return llb_buildsystem_command_should_show_status(handle)
}
/// The description provided by the command.
public var description: String {
let name = llb_buildsystem_command_get_description(handle)!
defer { free(name) }
return String(cString: name)
}
public var hashValue: Int {
return handle!.hashValue
}
public static func ==(lhs: Command, rhs: Command) -> Bool {
return lhs.handle == rhs.handle
}
}
/// Handle for a process which has been launched by a command.
//
// FIXME: We would like to call this Process, but then it conflicts with Swift's builtin Process. Maybe there is another name?
public struct ProcessHandle: Hashable {
fileprivate let handle: OpaquePointer
fileprivate init(_ handle: OpaquePointer) {
self.handle = handle
}
public var hashValue: Int {
return handle.hashValue
}
public static func ==(lhs: ProcessHandle, rhs: ProcessHandle) -> Bool {
return lhs.handle == rhs.handle
}
}
/// Result of a command execution.
public enum CommandResult {
case succeeded
case failed
case cancelled
case skipped
init(_ result: llb_buildsystem_command_result_t) {
switch result {
case llb_buildsystem_command_result_succeeded:
self = .succeeded
case llb_buildsystem_command_result_failed:
self = .failed
case llb_buildsystem_command_result_cancelled:
self = .cancelled
case llb_buildsystem_command_result_skipped:
self = .skipped
default:
fatalError("unknown command result")
}
}
}
public struct CommandMetrics {
public let utime: UInt64 /// User time (in us)
public let stime: UInt64 /// Sys time (in us)
public let maxRSS: UInt64 /// Max RSS (in bytes)
public init(utime: UInt64, stime: UInt64, maxRSS: UInt64) {
self.utime = utime
self.stime = stime
self.maxRSS = maxRSS
}
}
/// Result of a command execution.
public struct CommandExtendedResult {
public let result: CommandResult /// The result of a command execution
public let exitStatus: Int32 /// The exit code
public let pid: llbuild_pid_t? /// The process identifier (nil if failed to create a process)
public let metrics: CommandMetrics? /// Metrics about the executed command
init(_ result: UnsafePointer<llb_buildsystem_command_extended_result_t>) {
self.result = CommandResult(result.pointee.result)
self.exitStatus = result.pointee.exit_status
if result.pointee.pid >= 0 {
self.pid = result.pointee.pid
} else {
self.pid = nil
}
switch self.result {
case .succeeded, .failed:
self.metrics = CommandMetrics(utime: result.pointee.utime, stime: result.pointee.stime, maxRSS: result.pointee.maxrss)
default:
self.metrics = nil
}
}
public init(result: CommandResult, exitStatus: Int32, pid: llbuild_pid_t?, metrics: CommandMetrics? = nil) {
self.result = result
self.exitStatus = exitStatus
self.pid = pid
self.metrics = metrics
}
}
/// Status change event kinds.
public enum CommandStatusKind {
case isScanning
case isUpToDate
case isComplete
init(_ kind: llb_buildsystem_command_status_kind_t) {
switch kind {
case llb_buildsystem_command_status_kind_is_scanning:
self = .isScanning
case llb_buildsystem_command_status_kind_is_up_to_date:
self = .isUpToDate
case llb_buildsystem_command_status_kind_is_complete:
self = .isComplete
default:
fatalError("unknown status kind")
}
}
}
/// The BuildKey encodes the key space used by the BuildSystem when using the
/// core BuildEngine.
public struct BuildKey {
public enum Kind {
/// A key used to identify a command.
case command
/// A key used to identify a custom task.
case customTask
/// A key used to identify directory contents.
case directoryContents
/// A key used to identify the signature of a complete directory tree.
case directoryTreeSignature
/// A key used to identify a node.
case node
/// A key used to identify a target.
case target
/// An invalid key kind.
case unknown
init(_ kind: llb_build_key_kind_t) {
switch (kind) {
case llb_build_key_kind_command:
self = .command
case llb_build_key_kind_custom_task:
self = .customTask
case llb_build_key_kind_directory_contents:
self = .directoryContents
case llb_build_key_kind_directory_tree_signature:
self = .directoryTreeSignature
case llb_build_key_kind_node:
self = .node
case llb_build_key_kind_target:
self = .target
case llb_build_key_kind_unknown:
self = .unknown
default:
fatalError("unknown build key kind")
}
}
}
/// The kind of key
public let kind: Kind
/// The actual key data
public let key: String
public init(kind: Kind, key: String) {
self.kind = kind
self.key = key
}
init(key: llb_build_key_t) {
self.init(kind: BuildKey.Kind(key.kind), key: String(cString: key.key))
}
}
/// Cycle actions.
public enum CycleAction {
case forceBuild
case supplyPriorValue
init(_ action: llb_cycle_action_t) {
switch action {
case llb_cycle_action_force_build:
self = .forceBuild
case llb_cycle_action_supply_prior_value:
self = .supplyPriorValue
default:
fatalError("unknown cycle action")
}
}
}
/// File system information for a particular file.
///
/// This is a simple wrapper for stat() information.
public protocol FileInfo {
/// Creates a new `FileInfo` object.
init(_ statBuf: stat)
var statBuf: stat { get }
}
/// Abstracted access to file system operations.
// FIXME: We want to remove this protocol eventually and use the FileSystem
// protocol from SwiftPM's Basic target.
public protocol FileSystem {
/// Get the contents of a file.
// FIXME: This should throw.
func readFileContents(_ path: String) -> [UInt8]?
/// Returns the stat of a file at `path`.
func getFileInfo(_ path: String) throws -> FileInfo
}
/// Delegate interface for use with the build system.
public protocol BuildSystemDelegate {
/// The FileSystem to use, if any.
///
/// This is currently very limited.
var fs: FileSystem? { get }
/// Called in response to requests for new tools.
///
/// The client should return an appropriate tool implementation if recognized.
func lookupTool(_ name: String) -> Tool?
/// Called to report any form of command failure.
///
/// This can may be called to report the failure of a command which has
/// executed, but may also be used to report the inability of a command to
/// run. It is expected to be used by the client in making decisions with
/// regard to cancelling the build.
func hadCommandFailure()
/// Called to report an unassociated diagnostic from the build system.
func handleDiagnostic(_ diagnostic: Diagnostic)
/// Called when a command has changed state.
///
/// The system guarantees that any commandStart() call will be paired with
/// exactly one \see commandFinished() call.
func commandStatusChanged(_ command: Command, kind: CommandStatusKind)
/// Called when a command is preparing to start.
///
/// The system guarantees that any commandStart() call will be paired with
/// exactly one \see commandFinished() call.
func commandPreparing(_ command: Command)
/// Called when a command has been started.
///
/// The system guarantees that any commandStart() call will be paired with
/// exactly one \see commandFinished() call.
func commandStarted(_ command: Command)
/// Called to allow the delegate to skip commands without cancelling their
/// dependents. See llbuild's should_command_start.
func shouldCommandStart(_ command: Command) -> Bool
/// Called when a command has been finished.
func commandFinished(_ command: Command, result: CommandResult)
/// Called to report an error during the execution of a command.
func commandHadError(_ command: Command, message: String)
/// Called to report a note during the execution of a command.
func commandHadNote(_ command: Command, message: String)
/// Called to report a warning during the execution of a command.
func commandHadWarning(_ command: Command, message: String)
/// Called by the build system to report a command could not build due to
/// missing inputs.
func commandCannotBuildOutputDueToMissingInputs(_ command: Command, output: BuildKey, inputs: [BuildKey])
/// Called by the build system to report a node could not be built
/// because multiple commands are producing it.
func cannotBuildNodeDueToMultipleProducers(output: BuildKey, commands: [Command])
/// Called when a command's job has started executing an external process.
///
/// The system guarantees that any commandProcessStarted() call will be paired
/// with exactly one \see commandProcessFinished() call.
///
/// - parameter process: A unique handle used in subsequent delegate calls
/// to identify the process. This handle should only be used to associate
/// different status calls relating to the same process. It is only
/// guaranteed to be unique from when it has been provided here to when it
/// has been provided to the \see commandProcessFinished() call.
func commandProcessStarted(_ command: Command, process: ProcessHandle)
/// Called to report an error in the management of a command process.
///
/// - parameter process: The process handle.
/// - parameter message: The error message.
func commandProcessHadError(_ command: Command, process: ProcessHandle, message: String)
/// Called to report a command processes' (merged) standard output and error.
///
/// - parameter process: The process handle.
/// - parameter data: The process output.
func commandProcessHadOutput(_ command: Command, process: ProcessHandle, data: [UInt8])
/// Called when a command's job has finished executing an external process.
///
/// - parameter process: The handle used to identify the process. This
/// handle will become invalid as soon as the client returns from this API
/// call.
///
/// - parameter result: Whether the process suceeded, failed or was cancelled.
/// - parameter exitStatus: The raw exit status of the process.
func commandProcessFinished(_ command: Command, process: ProcessHandle, result: CommandExtendedResult)
/// Called when a cycle is detected by the build engine and it cannot make
/// forward progress.
func cycleDetected(rules: [BuildKey])
/// Called when a cycle is detected by the build engine to check if it should
/// attempt to resolve the cycle and continue
///
/// - parameter rules: The ordered list of items comprising the cycle,
/// starting from the node which was requested to build and ending with the
/// first node in the cycle (i.e., the node participating in the cycle will
/// appear twice).
/// - parameter candidate: The rule the engine will use to attempt to break the
/// cycle.
/// - parameter action: The action the engine will take on the candidateRule.
///
/// Returns true if the engine should attempt to resolve the cycle, false
/// otherwise. Resolution is attempted by either forcing items to be built, or
/// supplying a previously built result to a node in the cycle. The latter
/// action may yield unexpected results and thus this should be opted into
/// with care.
func shouldResolveCycle(rules: [BuildKey], candidate: BuildKey, action: CycleAction) -> Bool
}
/// Utility class for constructing a C-style environment.
private final class CStyleEnvironment {
/// The list of individual bindings, which must be deallocated.
private let bindings: [UnsafeMutablePointer<CChar>]
/// The environment array, which will be a valid C-style environment pointer
/// for the lifetime of the instance.
let envp: [UnsafePointer<CChar>?]
init(_ environment: [String: String]) {
// Allocate the individual binding strings.
self.bindings = environment.map{ "\($0.0)=\($0.1)".withCString(strdup)! }
// Allocate the envp array.
self.envp = self.bindings.map{ UnsafePointer($0) } + [nil]
}
deinit {
bindings.forEach{ free($0) }
}
}
/// This class allows building using llbuild's native BuildSystem component.
public final class BuildSystem {
/// The build file that the system is configured with.
public let buildFile: String
/// The delegate used by the system.
public let delegate: BuildSystemDelegate
/// The internal llbuild build system.
private var _system: OpaquePointer? = nil
/// The C environment, if used.
private let _cEnvironment: CStyleEnvironment?
public init(buildFile: String, databaseFile: String, delegate: BuildSystemDelegate, environment: [String: String]? = nil, serial: Bool = false, traceFile: String? = nil) {
// Safety check that we have linked against a compatibile llbuild framework version
if llb_get_api_version() != LLBUILD_C_API_VERSION {
fatalError("llbuild C API version mismatch, found \(llb_get_api_version()), expect \(LLBUILD_C_API_VERSION)")
}
self.buildFile = buildFile
self.delegate = delegate
// Create a stable C string path.
let pathPtr = strdup(buildFile)
defer { free(pathPtr) }
let dbPathPtr = strdup(databaseFile)
defer { free(dbPathPtr) }
let tracePathPtr = strdup(traceFile ?? "")
defer { free(tracePathPtr) }
// Allocate a C style environment, if necessary.
_cEnvironment = environment.map{ CStyleEnvironment($0) }
var _invocation = llb_buildsystem_invocation_t()
_invocation.buildFilePath = UnsafePointer(pathPtr)
_invocation.dbPath = UnsafePointer(dbPathPtr)
_invocation.traceFilePath = UnsafePointer(tracePathPtr)
_invocation.environment = _cEnvironment.map{ UnsafePointer($0.envp) }
_invocation.showVerboseStatus = true
_invocation.useSerialBuild = serial
// Construct the system delegate.
var _delegate = llb_buildsystem_delegate_t()
_delegate.context = Unmanaged.passUnretained(self).toOpaque()
if delegate.fs != nil {
_delegate.fs_get_file_contents = { BuildSystem.toSystem($0!).fsGetFileContents(String(cString: $1!), $2!) }
_delegate.fs_get_file_info = { BuildSystem.toSystem($0!).fsGetFileInfo(String(cString: $1!), $2!) }
// FIXME: This should be a separate callback, not shared with getFileInfo (or get FileInfo should take a parameter).
_delegate.fs_get_link_info = { BuildSystem.toSystem($0!).fsGetFileInfo(String(cString: $1!), $2!) }
}
_delegate.lookup_tool = { return BuildSystem.toSystem($0!).lookupTool($1!) }
_delegate.had_command_failure = { BuildSystem.toSystem($0!).hadCommandFailure() }
_delegate.handle_diagnostic = { BuildSystem.toSystem($0!).handleDiagnostic($1, String(cString: $2!), Int($3), Int($4), String(cString: $5!)) }
_delegate.command_status_changed = { BuildSystem.toSystem($0!).commandStatusChanged(Command($1), $2) }
_delegate.command_preparing = { BuildSystem.toSystem($0!).commandPreparing(Command($1)) }
_delegate.command_started = { BuildSystem.toSystem($0!).commandStarted(Command($1)) }
_delegate.should_command_start = { BuildSystem.toSystem($0!).shouldCommandStart(Command($1)) }
_delegate.command_finished = { BuildSystem.toSystem($0!).commandFinished(Command($1), CommandResult($2)) }
_delegate.command_had_error = { BuildSystem.toSystem($0!).commandHadError(Command($1), $2!) }
_delegate.command_had_note = { BuildSystem.toSystem($0!).commandHadNote(Command($1), $2!) }
_delegate.command_had_warning = { BuildSystem.toSystem($0!).commandHadWarning(Command($1), $2!) }
_delegate.command_cannot_build_output_due_to_missing_inputs = {
let inputsPtr = $3!
let inputs = (0..<Int($4)).map { BuildKey(key: inputsPtr[$0]) }
BuildSystem.toSystem($0!).commandCannotBuildOutputDueToMissingInputs(Command($1), BuildKey(key: $2!.pointee), inputs)
}
_delegate.cannot_build_node_due_to_multiple_producers = {
let commandsPtr = $2!
let commands = (0..<Int($3)).map { Command(commandsPtr[$0]) }
BuildSystem.toSystem($0!).cannotBuildNodeDueToMultipleProducers(BuildKey(key: $1!.pointee), commands)
}
_delegate.command_process_started = { BuildSystem.toSystem($0!).commandProcessStarted(Command($1), ProcessHandle($2!)) }
_delegate.command_process_had_error = { BuildSystem.toSystem($0!).commandProcessHadError(Command($1), ProcessHandle($2!), $3!) }
_delegate.command_process_had_output = { BuildSystem.toSystem($0!).commandProcessHadOutput(Command($1), ProcessHandle($2!), $3!) }
_delegate.command_process_finished = { BuildSystem.toSystem($0!).commandProcessFinished(Command($1), ProcessHandle($2!), CommandExtendedResult($3!)) }
_delegate.cycle_detected = {
var rules = [BuildKey]()
UnsafeBufferPointer(start: $1, count: Int($2)).forEach {
rules.append(BuildKey(key: $0))
}
BuildSystem.toSystem($0!).cycleDetected(rules)
}
_delegate.should_resolve_cycle = {
var rules = [BuildKey]()
UnsafeBufferPointer(start: $1, count: Int($2)).forEach {
rules.append(BuildKey(kind: BuildKey.Kind($0.kind), key: String(cString: $0.key)))
}
let candidate = BuildKey(kind: BuildKey.Kind($3.kind), key: String(cString: $3.key))
let result = BuildSystem.toSystem($0!).shouldResolveCycle(rules, candidate, CycleAction($4))
return (result) ? 1 : 0;
}
// Create the system.
_system = llb_buildsystem_create(_delegate, _invocation)
}
deinit {
assert(_system != nil)
llb_buildsystem_destroy(_system)
}
/// Build the default target, or optionally a single node.
///
/// The client is responsible for ensuring only one build is ever executing concurrently.
///
/// - parameter node: Optional path to a single node to build.
/// - returns: True if the build was successful, false otherwise.
public func build(node: String? = nil) -> Bool {
if let node = node {
var data = copiedDataFromBytes([UInt8](node.utf8))
return llb_buildsystem_build_node(_system, &data)
} else {
var data = llb_data_t(length: 0, data: nil)
return llb_buildsystem_build(_system, &data)
}
}
/// Cancel any running build.
public func cancel() {
llb_buildsystem_cancel(_system)
}
/// MARK: Internal Delegate Implementation
/// Helper function for getting the system from the delegate context.
static private func toSystem(_ context: UnsafeMutableRawPointer) -> BuildSystem {
return Unmanaged<BuildSystem>.fromOpaque(UnsafeRawPointer(context)).takeUnretainedValue()
}
/// Helper function for getting the tool wrapper from the delegate context.
static private func toToolWrapper(_ context: UnsafeMutableRawPointer) -> ToolWrapper {
return Unmanaged<ToolWrapper>.fromOpaque(UnsafeRawPointer(context)).takeUnretainedValue()
}
/// Helper function for getting the command wrapper from the delegate context.
static fileprivate func toCommandWrapper(_ context: UnsafeMutableRawPointer) -> CommandWrapper {
return Unmanaged<CommandWrapper>.fromOpaque(UnsafeRawPointer(context)).takeUnretainedValue()
}
private func fsGetFileContents(_ path: String, _ data: UnsafeMutablePointer<llb_data_t>) -> Bool {
let fs = delegate.fs!
// Get the contents for the file.
guard let contents = fs.readFileContents(path) else {
return false
}
data.pointee = copiedDataFromBytes(contents)
return true
}
private func fsGetFileInfo(_ path: String, _ info: UnsafeMutablePointer<llb_fs_file_info_t>) {
// Ignore invalid paths.
guard path.first == "/" else {
info.pointee = llb_fs_file_info_t()
return
}
// If the path doesn't exist, it is missing.
let fs = delegate.fs!
guard let s = try? fs.getFileInfo(path).statBuf else {
info.pointee = llb_fs_file_info_t()
return
}
// Otherwise, we have some kind of file.
info.pointee.device = UInt64(s.st_dev)
info.pointee.inode = UInt64(s.st_ino)
info.pointee.mode = UInt64(s.st_mode)
info.pointee.size = UInt64(s.st_size)
#if os(macOS)
info.pointee.mod_time.seconds = UInt64(s.st_mtimespec.tv_sec)
info.pointee.mod_time.nanoseconds = UInt64(s.st_mtimespec.tv_nsec)
#else
info.pointee.mod_time.seconds = UInt64(s.st_mtim.tv_sec)
info.pointee.mod_time.nanoseconds = UInt64(s.st_mtim.tv_nsec)
#endif
}
private func fsGetLinkInfo(_ path: String, _ info: UnsafeMutablePointer<llb_fs_file_info_t>) {
// FIXME: We do not support this natively, yet.
return fsGetFileInfo(path, info)
}
/// The owning list of all created tools.
//
// FIXME: This is lame, we should be able to destroy these naturally.
private var toolWrappers: [ToolWrapper] = []
private func lookupTool(_ name: UnsafePointer<llb_data_t>) -> OpaquePointer? {
// Look up the named tool.
guard let tool = delegate.lookupTool(stringFromData(name.pointee)) else {
return nil
}
// If we got a tool, save it and create an appropriate low-level instance.
let wrapper = ToolWrapper(tool: tool)
self.toolWrappers.append(wrapper)
var _delegate = llb_buildsystem_tool_delegate_t()
_delegate.context = Unmanaged.passUnretained(wrapper).toOpaque()
_delegate.create_command = { return BuildSystem.toToolWrapper($0!).createCommand($1!) }
// Create the tool.
return llb_buildsystem_tool_create(name, _delegate)
}
private func hadCommandFailure() {
delegate.hadCommandFailure()
}
private func handleDiagnostic(_ _kind: llb_buildsystem_diagnostic_kind_t, _ filename: String, _ line: Int, _ column: Int, _ message: String) {
let kind: Diagnostic.Kind = Diagnostic.Kind(_kind)
// Clean up the location.
let location: (filename: String, line: Int, column: Int)?
if filename == "<unknown>" || (line == -1 && column == -1) {
location = nil
} else {
location = (filename: filename, line: line, column: column)
}
delegate.handleDiagnostic(Diagnostic(kind: kind, location: location, message: message))
}
private func commandStatusChanged(_ command: Command, _ kind: llb_buildsystem_command_status_kind_t) {
delegate.commandStatusChanged(command, kind: CommandStatusKind(kind))
}
private func commandPreparing(_ command: Command) {
delegate.commandPreparing(command)
}
private func commandStarted(_ command: Command) {
delegate.commandStarted(command)
}
private func shouldCommandStart(_ command: Command) -> Bool {
return delegate.shouldCommandStart(command)
}
private func commandFinished(_ command: Command, _ result: CommandResult) {
delegate.commandFinished(command, result: result)
}
private func commandHadError(_ command: Command, _ data: UnsafePointer<llb_data_t>) {
delegate.commandHadError(command, message: stringFromData(data.pointee))
}
private func commandHadNote(_ command: Command, _ data: UnsafePointer<llb_data_t>) {
delegate.commandHadNote(command, message: stringFromData(data.pointee))
}
private func commandHadWarning(_ command: Command, _ data: UnsafePointer<llb_data_t>) {
delegate.commandHadWarning(command, message: stringFromData(data.pointee))
}
private func commandCannotBuildOutputDueToMissingInputs(_ command: Command, _ output: BuildKey, _ inputs: [BuildKey]) {
delegate.commandCannotBuildOutputDueToMissingInputs(command, output: output, inputs: inputs)
}
private func cannotBuildNodeDueToMultipleProducers(_ output: BuildKey, _ commands: [Command]) {
delegate.cannotBuildNodeDueToMultipleProducers(output: output, commands: commands)
}
private func commandProcessStarted(_ command: Command, _ process: ProcessHandle) {
delegate.commandProcessStarted(command, process: process)
}
private func commandProcessHadError(_ command: Command, _ process: ProcessHandle, _ data: UnsafePointer<llb_data_t>) {
delegate.commandProcessHadError(command, process: process, message: stringFromData(data.pointee))
}
private func commandProcessHadOutput(_ command: Command, _ process: ProcessHandle, _ data: UnsafePointer<llb_data_t>) {
delegate.commandProcessHadOutput(command, process: process, data: bytesFromData(data.pointee))
}
private func commandProcessFinished(_ command: Command, _ process: ProcessHandle, _ result: CommandExtendedResult) {
delegate.commandProcessFinished(command, process: process, result: result)
}
private func cycleDetected(_ rules: [BuildKey]) {
delegate.cycleDetected(rules: rules)
}
private func shouldResolveCycle(_ rules: [BuildKey], _ candidate: BuildKey, _ action: CycleAction) -> Bool {
return delegate.shouldResolveCycle(rules: rules, candidate: candidate, action: action)
}
/// Toggle tracing
public static func setTracing(enabled: Bool) {
if (enabled) {
llb_enable_tracing()
} else {
llb_disable_tracing()
}
}
public enum SchedulerAlgorithm: String {
case commandNamePriority
case fifo
init(_ algorithm: llb_scheduler_algorithm_t) {
switch algorithm {
case llb_scheduler_algorithm_command_name_priority:
self = .commandNamePriority
case llb_scheduler_algorithm_fifo:
self = .fifo
default:
self = .commandNamePriority
}
}
}
/// Get the scheduler algorithm
public static func getSchedulerLaneWidth() -> SchedulerAlgorithm {
return SchedulerAlgorithm(llb_get_scheduler_algorithm())
}
/// Set scheduler algorithm
public static func setSchedulerAlgorithm(_ algorithm: SchedulerAlgorithm) {
switch (algorithm) {
case .commandNamePriority:
llb_set_scheduler_algorithm(
llb_scheduler_algorithm_command_name_priority)
case .fifo:
llb_set_scheduler_algorithm(llb_scheduler_algorithm_fifo)
}
}
/// Get scheduler lane width
public static func getSchedulerLaneWidth() -> UInt32 {
return llb_get_scheduler_lane_width()
}
/// Set scheduler lane width
public static func setSchedulerLaneWidth(width: UInt32) {
llb_set_scheduler_lane_width(width)
}
}