blob: 4a91c13212b849a7230037569776a722939dd526 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright 2015 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.
import Foundation
enum DatabaseError: Error {
case AttachFailure(message: String)
}
private func stringFromData(data: llb_data_t) -> String {
// Convert as a UTF8 string, if possible.
let tmp = NSData(bytes: unsafeBitCast(data.data, to: UnsafeRawPointer.self), length: Int(data.length))
if let str = String(data: tmp as Data, encoding: String.Encoding.utf8) {
return str
}
// Otherwise, return a string representation of the bytes.
return String(describing: [UInt8](UnsafeBufferPointer(start: data.data, count: Int(data.length))))
}
private func stringFromUInt8Array(_ data: [UInt8]) -> String {
// Convert as a UTF8 string, if possible.
let tmp = NSData(bytes: data, length: data.count)
if let str = String(data: tmp as Data, encoding: String.Encoding.utf8) {
return str
}
// Otherwise, return a string representation of the bytes.
return String(describing: data)
}
/// Key objects are used to identify rules that can be built.
public struct Key: CustomStringConvertible, Equatable, Hashable {
public let data: [UInt8]
// MARK: CustomStringConvertible Conformance
public var description: String {
return "<Key: '\(toString())'>"
}
// MARK: Hashable Conformance
public var hashValue: Int {
// FIXME: Use a real hash function.
var result = data.count
for c in data {
result = result*31 + Int(c)
}
return result
}
// MARK: Implementation
public init(_ data: [UInt8]) { self.data = data }
public init(_ str: String) { self.init(Array(str.utf8)) }
/// Convert to a string representation.
public func toString() -> String {
return stringFromUInt8Array(self.data)
}
/// Create a Key object from an llb_data_t.
fileprivate static func fromInternalData(_ data: llb_data_t) -> Key {
return Key([UInt8](UnsafeBufferPointer(start: data.data, count: Int(data.length))))
}
/// Provide a Key contents as an llb_data_t pointer.
fileprivate func withInternalDataPtr<T>(closure: (UnsafePointer<llb_data_t>) -> T) -> T {
return data.withUnsafeBufferPointer { (dataPtr: UnsafeBufferPointer<UInt8>) -> T in
var value = llb_data_t(length: UInt64(self.data.count), data: dataPtr.baseAddress)
return withUnsafePointer(to: &value, closure)
}
}
}
public func ==(lhs: Key, rhs: Key) -> Bool {
return lhs.data == rhs.data
}
/// Value objects are the result of building rules.
public struct Value: CustomStringConvertible {
public let data: [UInt8]
public var description: String {
return "<Value: '\(toString())'>"
}
public init(_ data: [UInt8]) { self.data = data }
public init(_ str: String) { self.init(Array(str.utf8)) }
/// Convert to a string representation.
public func toString() -> String {
return stringFromUInt8Array(self.data)
}
/// Create a Value object from an llb_data_t.
fileprivate static func fromInternalData(_ data: llb_data_t) -> Value {
return Value([UInt8](UnsafeBufferPointer(start: data.data, count: Int(data.length))))
}
/// Provide a Value contents as an llb_data_t pointer.
fileprivate func withInternalDataPtr<T>(closure: (UnsafePointer<llb_data_t>) -> T) -> T {
return data.withUnsafeBufferPointer { (dataPtr: UnsafeBufferPointer<UInt8>) -> T in
var value = llb_data_t(length: UInt64(self.data.count), data: dataPtr.baseAddress)
return withUnsafePointer(to: &value, closure)
}
}
/// Create a Value object by providing an pointer to write the output into.
///
/// \param closure The closure to execute with a pointer to a llb_data_t to
/// use. The structure *must* be filled in by the closure.
///
/// \return The output Value.
fileprivate static func fromInternalDataOutputPtr(closure: (UnsafeMutablePointer<llb_data_t>) -> Void) -> Value {
var data = llb_data_t()
withUnsafeMutablePointer(to: &data, closure)
return Value.fromInternalData(data)
}
}
/// Enumeration describing the possible status of a Rule, used by \see
/// Rule.updateStatus().
public enum RuleStatus {
/// Indicates the rule is being scanned.
case IsScanning
/// Indicates the rule is up-to-date, and doesn't need to run.
case IsUpToDate
/// Indicates the rule was run, and is now complete.
case IsComplete
}
/// A rule represents an individual element of computation that can be performed
/// by the build engine.
///
/// Each rule is identified by a unique key and the value for that key can be
/// computed to produce a result, and supplies a set of callbacks that are used
/// to implement the rule's behavior.
///
/// The computation for a rule is done by invocation of its \see Action
/// callback, which is responsible for creating a Task object which will manage
/// the computation.
///
/// All callbacks for the Rule are always invoked synchronously on the primary
/// BuildEngine thread.
public protocol Rule {
/// Called to create the task to build the rule, when necessary.
func createTask() -> Task
/// Called to check whether the previously computed value for this rule is
/// still valid.
///
/// This callback is designed for use in synchronizing values which represent
/// state managed externally to the build engine. For example, a rule which
/// computes something on the file system may use this to verify that the
/// computed output has not changed since it was built.
func isResultValid(_ priorValue: Value) -> Bool
/// Called to indicate a change in the rule status.
func updateStatus(_ status: RuleStatus)
}
/// Protocol extension for default Rule methods.
public extension Rule {
final func isResultValid(_ priorValue: Value) -> Bool { return true }
final func updateStatus(_ status: RuleStatus) { }
}
/// A task object represents an abstract in-progress computation in the build
/// engine.
///
/// The task represents not just the primary computation, but also the process
/// of starting the computation and necessary input dependencies. Tasks are
/// expected to be created in response to \see BuildEngine requests to initiate
/// the production of particular result value.
///
/// The creator may use \see TaskBuildEngine.taskNeedsInput() to specify input
/// dependencies on the Task. The Task itself may also specify additional input
/// dependencies dynamically during the execution of \see Task.start() or \see
/// Task.provideValue().
///
/// Once a task has been created and registered, the BuildEngine will invoke
/// \see Task::start() to initiate the computation. The BuildEngine will provide
/// the in progress task with its requested inputs via \see
/// Task.provideValue().
///
/// After all inputs requested by the Task have been delivered, the BuildEngine
/// will invoke \see Task.inputsAvailable() to instruct the Task it should
/// complete its computation and provide the output. The Task is responsible for
/// providing the engine with the computed value when ready using \see
/// TaskBuildEngine.taskIsComplete().
public protocol Task {
/// Executed by the build engine when the task should be started.
func start(_ engine: TaskBuildEngine)
/// Invoked by the build engine to provide an input value as it becomes
/// available.
///
/// \param inputID The unique identifier provided to the build engine to
/// represent this input when requested in \see
/// TaskBuildEngine.taskNeedsInput().
///
/// \param value The computed value for the given input.
func provideValue(_ engine: TaskBuildEngine, inputID: Int, value: Value)
/// Executed by the build engine to indicate that all inputs have been
/// provided, and the task should begin its computation.
///
/// The task is expected to call \see TaskBuildEngine.taskIsComplete() when it is
/// done with its computation.
///
/// It is an error for any client to request an additional input for a task
/// after the last requested input has been provided by the build engine.
func inputsAvailable(_ engine: TaskBuildEngine)
}
/// Delegate interface for use with the build engine.
public protocol BuildEngineDelegate {
/// Get the rule to use for the given Key.
///
/// The delegate *must* provide a rule for any possible key that can be
/// requested (either by a client, through \see BuildEngine.build(), or via a
/// Task through mechanisms such as \see TaskBuildEngine.taskNeedsInput(). If a
/// requested Key cannot be supplied, the delegate should provide a dummy rule
/// that the client can translate into an error.
func lookupRule(_ key: Key) -> Rule
}
/// Wrapper to allow passing an opaque pointer to a protocol type.
//
// FIXME: Why do we need this, why can't we get a pointer to the protocol
// typed object?
private class Wrapper<T> {
let item: T
init(_ item: T) { self.item = item }
}
/// This protocol encapsulates the API that a task can use to communicate with
/// the build engine.
public protocol TaskBuildEngine {
var engine: BuildEngine { get }
/// Specify that the task depends upon the result of computing \arg key.
///
/// The result, when available, will be provided to the task via \see
/// Task.provideValue(), supplying the provided \arg inputID to allow the task
/// to identify the particular input.
///
/// NOTE: It is an unchecked error for a task to request the same input value
/// multiple times.
///
/// \param inputID An arbitrary value that may be provided by the client to
/// use in efficiently associating this input.
func taskNeedsInput(_ key: Key, inputID: Int)
/// Specify that the task must be built subsequent to the computation of \arg
/// key.
///
/// The value of the computation of \arg key is not available to the task, and
/// the only guarantee the engine provides is that if \arg key is computed
/// during a build, then task will not be computed until after it.
func taskMustFollow(_ key: Key)
/// Inform the engine of an input dependency that was discovered by the task
/// during its execution, a la compiler generated dependency files.
///
/// This call may only be made after a task has received all of its inputs;
/// inputs discovered prior to that point should simply be requested as normal
/// input dependencies.
///
/// Such a dependency is not used to provide additional input to the task,
/// rather it is a way for the task to report an additional input which should
/// be considered the next time the rule is evaluated. The expected use case
/// for a discovered dependency is is when a processing task cannot predict
/// all of its inputs prior to being run, but can presume that any unknown
/// inputs already exist. In such cases, the task can go ahead and run and can
/// report the all of the discovered inputs as it executes. Once the task is
/// complete, these inputs will be recorded as being dependencies of the task
/// so that it will be recomputed when any of the inputs change.
///
/// It is legal to call this method from any thread, but the caller is
/// responsible for ensuring that it is never called concurrently for the same
/// task.
func taskDiscoveredDependency(_ key: Key)
/// Indicate that the task has completed and provide its resulting value.
///
/// It is legal to call this method from any thread.
///
/// \param value The new value for the task's rule.
///
/// \param forceChange If true, treat the value as changed and trigger
/// dependents to rebuild, even if the value itself is not different from the
/// prior result.
func taskIsComplete(_ result: Value, forceChange: Bool)
}
/// Single concrete implementation of the TaskBuildEngine protocol.
private class TaskWrapper: CustomStringConvertible, TaskBuildEngine {
let engine: BuildEngine
let task: Task
var taskInternal: OpaquePointer?
var description: String {
return "<TaskWrapper engine:\(engine), task:\(task)>"
}
init(_ engine: BuildEngine, _ task: Task) {
self.engine = engine
self.task = task
}
func taskNeedsInput(_ key: Key, inputID: Int) {
engine.taskNeedsInput(self, key: key, inputID: inputID)
}
func taskMustFollow(_ key: Key) {
engine.taskMustFollow(self, key: key)
}
func taskDiscoveredDependency(_ key: Key) {
engine.taskDiscoveredDependency(self, key: key)
}
func taskIsComplete(_ result: Value, forceChange: Bool = false) {
engine.taskIsComplete(self, result: result, forceChange: forceChange)
}
}
/// A build engine supports fast, incremental, persistent, and parallel
/// execution of computational graphs.
///
/// Computational elements in the graph are modeled by \see Rule objects, which
/// are assocated with a specific \see Key, and which can be executed to produce
/// an output \see Value for that key.
///
/// Rule objects are evaluated by first invoking their action to produce a \see
/// Task object which is responsible for the live execution of the
/// computation. The Task object can interact with the BuildEngine to request
/// inputs or to notify the engine of its completion, and receives various
/// callbacks from the engine as the computation progresses.
///
/// The engine itself executes using a deterministic, serial operation, but it
/// supports parallel computation by allowing the individual Task objects to
/// defer their own computation to signal the BuildEngine of its completion on
/// alternate threads.
///
/// To support persistence, the engine allows attaching a database (\see
/// attachDB()) which can be used to record the prior results of evaluating Rule
/// instances.
public class BuildEngine {
/// The client delegate.
private var delegate: BuildEngineDelegate
/// The internal llbuild build engine.
private var _engine: OpaquePointer?
/// Our llbuild engine delegate object.
private var _delegate = llb_buildengine_delegate_t()
/// The number of rules which have been defined.
public var numRules: Int = 0
public init(delegate: BuildEngineDelegate) {
self.delegate = delegate
// Initialize the delegate.
_delegate.context = unsafeBitCast(Unmanaged.passUnretained(self), to: UnsafeMutableRawPointer.self)
_delegate.lookup_rule = { BuildEngine.toEngine($0!).lookupRule($1!, $2!) }
// FIXME: Include cycleDetected callback.
// Create the engine.
_engine = llb_buildengine_create(_delegate)
}
deinit {
if _engine != nil {
close()
}
}
public func close() {
assert(_engine != nil)
llb_buildengine_destroy(_engine)
_engine = nil
}
/// Build the result for a particular key.
public func build(key: Key) -> Value {
return Value.fromInternalDataOutputPtr { resultPtr in
key.withInternalDataPtr { llb_buildengine_build(self._engine, $0, resultPtr) }
}
}
/// Attach a database for persisting build state.
///
/// A database should only be attached immediately after creating the engine,
/// it is an error to attach a database after adding rules or initiating any
/// builds, or to attempt to attach multiple databases.
public func attachDB(path: String, schemaVersion: Int = 0) throws {
let errorPtr = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 1)
defer { errorPtr.deinitialize(count: 1) }
// FIXME: Why do I have to name the closure signature here?
var errorMsgOpt: String? = nil
Key(path).withInternalDataPtr { (ptr) -> Void in
if !llb_buildengine_attach_db(self._engine, ptr, UInt32(schemaVersion), errorPtr) {
// If there was an error, report it.
if let errorPointee = errorPtr.pointee {
errorMsgOpt = String(cString: errorPointee)
}
}
}
// Throw the error, if found.
if let errorMsg = errorMsgOpt {
throw DatabaseError.AttachFailure(message: errorMsg)
}
}
/// MARK: Internal Task-Only API
fileprivate func taskNeedsInput(_ taskWrapper: TaskWrapper, key: Key, inputID: Int) {
key.withInternalDataPtr { keyPtr in
llb_buildengine_task_needs_input(self._engine, taskWrapper.taskInternal, keyPtr, UInt(inputID))
}
}
fileprivate func taskMustFollow(_ taskWrapper: TaskWrapper, key: Key) {
key.withInternalDataPtr { keyPtr in
llb_buildengine_task_must_follow(self._engine, taskWrapper.taskInternal, keyPtr)
}
}
fileprivate func taskDiscoveredDependency(_ taskWrapper: TaskWrapper, key: Key) {
key.withInternalDataPtr { keyPtr in
llb_buildengine_task_discovered_dependency(self._engine, taskWrapper.taskInternal, keyPtr)
}
}
fileprivate func taskIsComplete(_ taskWrapper: TaskWrapper, result: Value, forceChange: Bool = false) {
result.withInternalDataPtr { dataPtr in
llb_buildengine_task_is_complete(self._engine, taskWrapper.taskInternal, dataPtr, forceChange)
}
}
/// MARK: Internal Delegate Implementation
/// Helper function for getting the engine from the delegate context.
static fileprivate func toEngine(_ context: UnsafeMutableRawPointer) -> BuildEngine {
return Unmanaged<BuildEngine>.fromOpaque(context).takeUnretainedValue()
}
/// Helper function for getting the rule from a rule delegate context.
static fileprivate func toRule(_ context: UnsafeMutableRawPointer) -> Rule {
return Unmanaged<Wrapper<Rule>>.fromOpaque(context).takeUnretainedValue().item
}
/// Helper function for getting the task from a task delegate context.
static fileprivate func toTaskWrapper(_ context: UnsafeMutableRawPointer) -> TaskWrapper {
return Unmanaged<TaskWrapper>.fromOpaque(context).takeUnretainedValue()
}
fileprivate func lookupRule(_ key: UnsafePointer<llb_data_t>, _ ruleOut: UnsafeMutablePointer<llb_rule_t>) {
numRules += 1
// Get the rule from the client.
let rule = delegate.lookupRule(Key.fromInternalData(key.pointee))
// Fill in the output structure.
//
// FIXME: We need a deallocation callback in order to ensure this is released.
ruleOut.pointee.context = unsafeBitCast(Unmanaged.passRetained(Wrapper(rule)), to: UnsafeMutableRawPointer.self)
ruleOut.pointee.create_task = { (context, engineContext) -> OpaquePointer! in
let rule = BuildEngine.toRule(context!)
let engine = BuildEngine.toEngine(engineContext!)
return engine.ruleCreateTask(rule)
}
ruleOut.pointee.is_result_valid = { (context, engineContext, internalRule, value) -> Bool in
let rule = BuildEngine.toRule(context!)
return rule.isResultValid(Value.fromInternalData(value!.pointee))
}
ruleOut.pointee.update_status = { (context, engineContext, status) in
let rule = BuildEngine.toRule(context!)
let status = { (kind: llb_rule_status_kind_t) -> RuleStatus in
switch kind.rawValue {
case llb_rule_is_scanning.rawValue: return .IsScanning
case llb_rule_is_up_to_date.rawValue: return .IsUpToDate
case llb_rule_is_complete.rawValue: return .IsComplete
default:
fatalError("unknown status kind")
} }(status)
return rule.updateStatus(status)
}
}
private func ruleCreateTask(_ rule: Rule) -> OpaquePointer {
// Create the task.
let task = rule.createTask()
// Create the task wrapper.
//
// Note that the wrapper here is serving two purposes, it is providing a way
// to communicate the internal task object to clients, and it is providing a
// way to segregate the Task-only API from the rest of the BuildEngine API.
let taskWrapper = TaskWrapper(self, task)
// Create the task delegate.
//
// FIXME: Separate the delegate from the context pointer.
var taskDelegate = llb_task_delegate_t()
// FIXME: We need a deallocation callback in order to ensure this is released.
taskDelegate.context = unsafeBitCast(Unmanaged.passRetained(taskWrapper), to: UnsafeMutableRawPointer.self)
taskDelegate.start = { (context, engineContext, internalTask) in
let taskWrapper = BuildEngine.toTaskWrapper(context!)
taskWrapper.task.start(taskWrapper)
}
taskDelegate.provide_value = { (context, engineContext, internalTask, inputID, value) in
let taskWrapper = BuildEngine.toTaskWrapper(context!)
taskWrapper.task.provideValue(taskWrapper, inputID: Int(inputID), value: Value.fromInternalData(value!.pointee))
}
taskDelegate.inputs_available = { (context, engineContext, internalTask) in
let taskWrapper = BuildEngine.toTaskWrapper(context!)
taskWrapper.task.inputsAvailable(taskWrapper)
}
// Create the internal task.
taskWrapper.taskInternal = llb_task_create(taskDelegate)
// FIXME: Why do we have both of these, it is kind of annoying. It makes
// some amount of sense in the C++ API, but the C API should probably just
// collapse them.
return llb_buildengine_register_task(self._engine, taskWrapper.taskInternal)
}
}