From 3f785b647ef0056cac330121a276f1234c768f3d Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Fri, 28 Aug 2020 17:52:57 -0700 Subject: [PATCH] Provide capability for clients to build a node and walk the rule result info for the node and its dependencies This is implemented by having the client optionally pass an implementation of a `RuleResultsWalker` which receives the related information for the nodes. I preferred this method of allowing the client to "hook" in and get the info before the `build` call returns and does its clean up and any related "teardown". That way we ensure the data is readily available for the client to inspect. This method also allows to restrict the rule result queries only to the nodes related to the just built node, which simplifies things compared to exposing general APIs for querying any node, even if it wasn't involved in the build. Related to rdar://67816715 --- include/llbuild/BuildSystem/BuildSystem.h | 6 +- .../llbuild/BuildSystem/BuildSystemFrontend.h | 3 +- include/llbuild/Core/BuildEngine.h | 26 ++- lib/BuildSystem/BuildSystem.cpp | 12 +- lib/BuildSystem/BuildSystemFrontend.cpp | 8 +- lib/Core/BuildEngine.cpp | 36 +++- products/libllbuild/BuildSystem-C-API.cpp | 45 ++++ .../libllbuild/include/llbuild/buildsystem.h | 54 +++++ .../llbuildSwift/BuildSystemBindings.swift | 94 +++++++++ products/llbuildSwift/BuildValue.swift | 16 ++ unittests/Swift/BuildSystemEngineTests.swift | 193 ++++++++++++++---- 11 files changed, 441 insertions(+), 52 deletions(-) diff --git a/include/llbuild/BuildSystem/BuildSystem.h b/include/llbuild/BuildSystem/BuildSystem.h index 999bb08ee..cae9fc926 100644 --- a/include/llbuild/BuildSystem/BuildSystem.h +++ b/include/llbuild/BuildSystem/BuildSystem.h @@ -30,6 +30,9 @@ namespace basic { class ExecutionQueue; class FileSystem; } +namespace core { + class RuleResultsWalker; +} namespace buildsystem { @@ -262,8 +265,9 @@ class BuildSystem { /// /// A build description *must* have been loaded before calling this method. /// + /// \param resultsWalker Optional walker for receiving the rule results of the node and its dependencies. /// \returns The result of computing the value, or nil if the build failed. - llvm::Optional build(BuildKey target); + llvm::Optional build(BuildKey target, core::RuleResultsWalker* resultsWalker = nullptr); /// Reset mutable build state before a new build operation. void resetForBuild(); diff --git a/include/llbuild/BuildSystem/BuildSystemFrontend.h b/include/llbuild/BuildSystem/BuildSystemFrontend.h index 22b387263..5660ed735 100644 --- a/include/llbuild/BuildSystem/BuildSystemFrontend.h +++ b/include/llbuild/BuildSystem/BuildSystemFrontend.h @@ -84,8 +84,9 @@ class BuildSystemFrontend { /// Build a single node using the specified invocation parameters. /// + /// \param resultsWalker Optional walker for receiving the rule results of the node and its dependencies. /// \returns True on success, or false if there were errors. - bool buildNode(StringRef nodeToBuild); + bool buildNode(StringRef nodeToBuild, core::RuleResultsWalker* resultsWalker = nullptr); /// @} }; diff --git a/include/llbuild/Core/BuildEngine.h b/include/llbuild/Core/BuildEngine.h index 6cdc4e730..93f549a31 100644 --- a/include/llbuild/Core/BuildEngine.h +++ b/include/llbuild/Core/BuildEngine.h @@ -389,6 +389,29 @@ class BuildEngineDelegate { }; +/// Abstract class for visiting the rule results of a node and its dependencies. +class RuleResultsWalker { +public: + /// Specifies how node visitation should proceed. + enum class ActionKind { + /// Continue visiting the rule results of the current node dependencies. + VisitDependencies = 0, + + /// Continue visiting but skip the current node dependencies. + SkipDependencies = 1, + + /// Stop visitation. + Stop = 2 + }; + + /// Accepts the rule result for a node. + /// + /// \param key The key for the currenly visited node. + /// \param key The rule result for the currenly visited node. + /// \returns An action kind to indicate how visitation should proceed. + virtual ActionKind visitResult(const KeyType& key, const core::Result& result) = 0; +}; + /// A build engine supports fast, incremental, persistent, and parallel /// execution of computational graphs. /// @@ -444,10 +467,11 @@ class BuildEngine { /// Build the result for a particular key. /// + /// \param resultsWalker Optional walker for receiving the rule results of the node and its dependencies. /// \returns The result of computing the key, or the empty value if the key /// could not be computed; the latter case only happens if a cycle was /// discovered currently. - const ValueType& build(const KeyType& key); + const ValueType& build(const KeyType& key, RuleResultsWalker* resultsWalker = nullptr); /// Cancel the currently running build. /// diff --git a/lib/BuildSystem/BuildSystem.cpp b/lib/BuildSystem/BuildSystem.cpp index cbc76dc6f..64639d082 100644 --- a/lib/BuildSystem/BuildSystem.cpp +++ b/lib/BuildSystem/BuildSystem.cpp @@ -326,7 +326,7 @@ class BuildSystemImpl { } /// Build the given key, and return the result and an indication of success. - llvm::Optional build(BuildKey key); + llvm::Optional build(BuildKey key, RuleResultsWalker* resultsWalker); bool build(StringRef target); @@ -1823,7 +1823,7 @@ BuildSystemImpl::lookupNode(StringRef name, bool isImplicit) { return BuildNode::makePlain(name); } -llvm::Optional BuildSystemImpl::build(BuildKey key) { +llvm::Optional BuildSystemImpl::build(BuildKey key, RuleResultsWalker* resultsWalker) { if (basic::sys::raiseOpenFileLimit() != 0) { error(getMainFilename(), "failed to raise open file limit"); @@ -1832,7 +1832,7 @@ llvm::Optional BuildSystemImpl::build(BuildKey key) { // Build the target. buildWasAborted = false; - auto result = buildEngine.build(key.toData()); + auto result = buildEngine.build(key.toData(), resultsWalker); // Clear out the shell handlers, as we do not want to hold on to them across // multiple builds. @@ -1863,7 +1863,7 @@ bool BuildSystemImpl::build(StringRef target) { return false; } - return build(BuildKey::makeTarget(target)).hasValue(); + return build(BuildKey::makeTarget(target), nullptr).hasValue(); } #pragma mark - PhonyTool implementation @@ -3864,8 +3864,8 @@ bool BuildSystem::enableTracing(StringRef path, return static_cast(impl)->enableTracing(path, error_out); } -llvm::Optional BuildSystem::build(BuildKey key) { - return static_cast(impl)->build(key); +llvm::Optional BuildSystem::build(BuildKey key, RuleResultsWalker* resultsWalker) { + return static_cast(impl)->build(key, resultsWalker); } bool BuildSystem::build(StringRef name) { diff --git a/lib/BuildSystem/BuildSystemFrontend.cpp b/lib/BuildSystem/BuildSystemFrontend.cpp index a199a3f29..a9c4b267f 100644 --- a/lib/BuildSystem/BuildSystemFrontend.cpp +++ b/lib/BuildSystem/BuildSystemFrontend.cpp @@ -435,7 +435,7 @@ struct BuildSystemFrontendImpl { } - bool buildNode(StringRef nodeToBuild) { + bool buildNode(StringRef nodeToBuild, core::RuleResultsWalker* resultsWalker) { llbuild_defer { resetAfterBuild(); }; @@ -444,7 +444,7 @@ struct BuildSystemFrontendImpl { return false; } - auto buildValue = system->build(BuildKey::makeNode(nodeToBuild)); + auto buildValue = system->build(BuildKey::makeNode(nodeToBuild), resultsWalker); if (!buildValue.hasValue()) { return false; } @@ -808,6 +808,6 @@ bool BuildSystemFrontend::build(StringRef targetToBuild) { return static_cast(impl)->build(targetToBuild); } -bool BuildSystemFrontend::buildNode(StringRef nodeToBuild) { - return static_cast(impl)->buildNode(nodeToBuild); +bool BuildSystemFrontend::buildNode(StringRef nodeToBuild, core::RuleResultsWalker* resultsWalker) { + return static_cast(impl)->buildNode(nodeToBuild, resultsWalker); } diff --git a/lib/Core/BuildEngine.cpp b/lib/Core/BuildEngine.cpp index c9cd42b78..b8d636600 100644 --- a/lib/Core/BuildEngine.cpp +++ b/lib/Core/BuildEngine.cpp @@ -18,6 +18,7 @@ #include "llbuild/Core/BuildDB.h" #include "llbuild/Core/KeyID.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringMap.h" @@ -1476,7 +1477,7 @@ class BuildEngineImpl : public BuildDBDelegate { /// @name Client API /// @{ - const ValueType& build(const KeyType& key) { + const ValueType& build(const KeyType& key, RuleResultsWalker* resultsWalker) { // Protect the engine against invalid concurrent use. if (buildRunning.exchange(true)) { delegate.error("build engine busy"); @@ -1574,6 +1575,35 @@ class BuildEngineImpl : public BuildDBDelegate { return emptyValue; } + if (resultsWalker) { + // Pass the rule results of the node and its dependencies. + SmallVector queue; + queue.push_back(getKeyID(key)); + + llvm::SmallSet visitedKeys; + while (!queue.empty()) { + KeyID key = queue.pop_back_val(); + bool inserted = visitedKeys.insert(key).second; + if (!inserted) { + continue; // already visited. + } + + auto& ruleInfo = getRuleInfoForKey(key); + switch (resultsWalker->visitResult(getKeyForID(key), ruleInfo.result)) { + case RuleResultsWalker::ActionKind::VisitDependencies: + for (AttributedKeyIDs::KeyIDAndFlag keyIDAndFlag : ruleInfo.result.dependencies) { + queue.push_back(keyIDAndFlag.keyID); + } + break; + case RuleResultsWalker::ActionKind::SkipDependencies: + break; + case RuleResultsWalker::ActionKind::Stop: + queue.clear(); + break; + } + } + } + // The task queue should be empty and the rule complete. auto& ruleInfo = getRuleInfoForKey(key); assert(taskInfos.empty() && ruleInfo.isComplete(this)); @@ -1854,8 +1884,8 @@ void BuildEngine::addRule(std::unique_ptr&& rule) { static_cast(impl)->addRule(std::move(rule)); } -const ValueType& BuildEngine::build(const KeyType& key) { - return static_cast(impl)->build(key); +const ValueType& BuildEngine::build(const KeyType& key, RuleResultsWalker* resultsWalker) { + return static_cast(impl)->build(key, resultsWalker); } void BuildEngine::resetForBuild() { diff --git a/products/libllbuild/BuildSystem-C-API.cpp b/products/libllbuild/BuildSystem-C-API.cpp index 8bf9cd537..94d3046bf 100644 --- a/products/libllbuild/BuildSystem-C-API.cpp +++ b/products/libllbuild/BuildSystem-C-API.cpp @@ -584,6 +584,46 @@ class CAPIBuildSystem { return getFrontend().buildNode(key); } + bool buildNodeAndWalkResults(llvm::StringRef key, llb_buildsystem_rule_result_walker_t cWalker) { + class CAPIRuleResultsWalker : public core::RuleResultsWalker { + llb_buildsystem_rule_result_walker_t cAPIWalker; + + public: + explicit CAPIRuleResultsWalker(llb_buildsystem_rule_result_walker_t walker) : cAPIWalker(walker) {} + + ActionKind visitResult(const core::KeyType& key, const core::Result& result) override { + auto llbKey = llb_data_t { + key.size(), + (const uint8_t *)key.data() + }; + auto llbValue = llb_data_t { + result.value.size(), + result.value.data() + }; + auto llbResult = llb_buildsystem_rule_result_t { + llbValue, + result.signature.value, + result.computedAt, + result.builtAt, + result.start, + result.end, + }; + auto llbAction = cAPIWalker.visit_result(cAPIWalker.context, llbKey, llbResult); + switch (llbAction) { + case llb_buildsystem_rule_result_walk_action_kind_visit_dependencies: + return ActionKind::VisitDependencies; + case llb_buildsystem_rule_result_walk_action_kind_skip_dependencies: + return ActionKind::SkipDependencies; + case llb_buildsystem_rule_result_walk_action_kind_stop: + return ActionKind::Stop; + } + } + }; + + CAPIRuleResultsWalker walker(cWalker); + return getFrontend().buildNode(key, &walker); + } + void cancel() { frontendDelegate->cancel(); } @@ -900,6 +940,11 @@ bool llb_buildsystem_build_node(llb_buildsystem_t* system_p, const llb_data_t* k return system->buildNode(llvm::StringRef((const char*)key->data, key->length)); } +bool llb_buildsystem_build_node_and_walk_results(llb_buildsystem_t* system_p, const llb_data_t* key, llb_buildsystem_rule_result_walker_t walker) { + CAPIBuildSystem* system = (CAPIBuildSystem*) system_p; + return system->buildNodeAndWalkResults(llvm::StringRef((const char*)key->data, key->length), walker); +} + void llb_buildsystem_cancel(llb_buildsystem_t* system_p) { CAPIBuildSystem* system = (CAPIBuildSystem*) system_p; system->cancel(); diff --git a/products/libllbuild/include/llbuild/buildsystem.h b/products/libllbuild/include/llbuild/buildsystem.h index 47cd70a53..9ba35d6eb 100644 --- a/products/libllbuild/include/llbuild/buildsystem.h +++ b/products/libllbuild/include/llbuild/buildsystem.h @@ -544,6 +544,60 @@ llb_buildsystem_build(llb_buildsystem_t* system, const llb_data_t* key); LLBUILD_EXPORT bool llb_buildsystem_build_node(llb_buildsystem_t* system, const llb_data_t* key); +/// Defines the result of a task. +typedef struct llb_buildsystem_rule_result_t_ { + + /// The value that resulted from executing the task + llb_data_t value; + + /// Signature of the node that generated the result + uint64_t signature; + + /// The build iteration this result was computed at + uint64_t computed_at; + + /// The build iteration this result was built at + uint64_t built_at; + + /// The start of the command as a duration since a reference time + double start; + + /// The duration since a reference time of when the command finished computing + double end; +} llb_buildsystem_rule_result_t; + +/// Specifies how node visitation should proceed. +typedef enum LLBUILD_ENUM_ATTRIBUTES { + llb_buildsystem_rule_result_walk_action_kind_visit_dependencies LLBUILD_SWIFT_NAME(visitDependencies) = 0, + llb_buildsystem_rule_result_walk_action_kind_skip_dependencies LLBUILD_SWIFT_NAME(skipDependencies) = 1, + llb_buildsystem_rule_result_walk_action_kind_stop = 2, +} llb_buildsystem_rule_result_walk_action_kind_t LLBUILD_SWIFT_NAME(RuleResultWalkActionKind); + +/// Visitor for the rule results of a node and its dependencies. +typedef struct llb_buildsystem_rule_result_walker_t_ { + /// User context pointer. + void* context; + + /// Receives the key and rule result for the currently visited node. The `llb_data_t` objects of `key` and `result.value` point to transient memory that is only valid to access during the duration of this function call. + /// Copy the `llb_data_t` data of these objects if you want to refer to the data after this function returns. + llb_buildsystem_rule_result_walk_action_kind_t (*visit_result)(void* context, llb_data_t key, + llb_buildsystem_rule_result_t result); +} llb_buildsystem_rule_result_walker_t; + +/// Build a single node and walk the rule results of the node and its dependencies if successful. If it returns false, no visitation occurred. +/// +/// It is an unchecked error for the client to request multiple builds +/// concurrently. +/// +/// This will automatically initialize the build system if it has not already +/// been initialized. +/// +/// \param walker Receiver for the rule results of the node and its dependencies. +/// \returns True on success, or false if the build was aborted (for example, if +/// a cycle was discovered). +LLBUILD_EXPORT bool +llb_buildsystem_build_node_and_walk_results(llb_buildsystem_t* system, const llb_data_t* key, llb_buildsystem_rule_result_walker_t walker); + /// Cancel any ongoing build. /// /// This method may be called from any thread. diff --git a/products/llbuildSwift/BuildSystemBindings.swift b/products/llbuildSwift/BuildSystemBindings.swift index 596f42622..c4da1fcab 100644 --- a/products/llbuildSwift/BuildSystemBindings.swift +++ b/products/llbuildSwift/BuildSystemBindings.swift @@ -702,6 +702,64 @@ private final class CStyleEnvironment { } } +/// Defines the result of a task. +public struct BuildSystemRuleResult { + /// The value of the result + public let value: BuildValue + /// Signature of the node that generated the result + public let signature: UInt64 + /// The build iteration this result was computed at + public let computedAt: UInt64 + /// The build iteration this result was built at + public let builtAt: UInt64 + /// The start of the command as a duration since a reference time + public let start: Double + /// The duration since a reference time of when the command finished computing + public let end: Double + /// The duration in seconds the result needed to finish + public var duration: Double { return end - start } + + public init(value: BuildValue, signature: UInt64, computedAt: UInt64, builtAt: UInt64, start: Double, end: Double) { + self.value = value + self.signature = signature + self.computedAt = computedAt + self.builtAt = builtAt + self.start = start + self.end = end + } + + fileprivate init?(result: llb_buildsystem_rule_result_t) { + guard let value = BuildValue.construct(from: Value(ValueType(UnsafeBufferPointer(start: result.value.data, count: Int(result.value.length))))) else { + return nil + } + self.init(value: value, signature: result.signature, computedAt: result.computed_at, builtAt: result.built_at, start: result.start, end: result.end) + } +} + +extension BuildSystemRuleResult: Equatable { + public static func == (lhs: BuildSystemRuleResult, rhs: BuildSystemRuleResult) -> Bool { + return lhs.value == rhs.value && lhs.signature == rhs.signature && lhs.computedAt == rhs.computedAt && lhs.builtAt == rhs.builtAt && lhs.start == rhs.start && lhs.end == rhs.end + } +} + +extension BuildSystemRuleResult: CustomStringConvertible { + public var description: String { + return "" + } +} + +/// Specifies how node visitation should proceed. +public enum RuleResultsWalkActionKind { + case visitDependencies + case skipDependencies + case stop +} + +/// Visitor for the rule results of a node and its dependencies. +public protocol RuleResultsWalker { + func visit(key: BuildKey, result: BuildSystemRuleResult) -> RuleResultsWalkActionKind +} + /// This class allows building using llbuild's native BuildSystem component. public final class BuildSystem { public typealias SchedulerAlgorithm = llbuild.SchedulerAlgorithm @@ -835,6 +893,42 @@ public final class BuildSystem { return llb_buildsystem_build_node(_system, &data) } + public func build(node: String, resultsWalker: RuleResultsWalker) -> Bool { + var data = copiedDataFromBytes([UInt8](node.utf8)) + defer { + llb_data_destroy(&data) + } + + class WalkerWrapper { + let walker: RuleResultsWalker + + init(_ walker: RuleResultsWalker) { + self.walker = walker + } + + func visit(cKey: llb_data_t, cResult: llb_buildsystem_rule_result_t) -> RuleResultWalkActionKind { + guard let result = BuildSystemRuleResult(result: cResult) else { + return .visitDependencies + } + switch walker.visit(key: BuildKey.construct(data: cKey), result: result) { + case .visitDependencies: return .visitDependencies + case .skipDependencies: return .skipDependencies + case .stop: return .stop + } + } + + static func toWrapper(_ context: UnsafeMutableRawPointer) -> WalkerWrapper { + return Unmanaged.fromOpaque(UnsafeRawPointer(context)).takeUnretainedValue() + } + } + + let wrapper = WalkerWrapper(resultsWalker) + var cWalker = llb_buildsystem_rule_result_walker_t() + cWalker.context = Unmanaged.passUnretained(wrapper).toOpaque() + cWalker.visit_result = { return WalkerWrapper.toWrapper($0!).visit(cKey: $1, cResult: $2) } + return llb_buildsystem_build_node_and_walk_results(_system, &data, cWalker) + } + /// Build the default target, or optionally a specific target. /// /// The client is responsible for ensuring only one build is ever executing concurrently. diff --git a/products/llbuildSwift/BuildValue.swift b/products/llbuildSwift/BuildValue.swift index 3ca994c56..f36a39d01 100644 --- a/products/llbuildSwift/BuildValue.swift +++ b/products/llbuildSwift/BuildValue.swift @@ -19,6 +19,8 @@ import Glibc #error("Missing libc or equivalent") #endif +import struct Foundation.Date + // We don't need this import if we're building // this file as part of the llbuild framework. #if !LLBUILD_FRAMEWORK @@ -70,12 +72,26 @@ extension BuildValueFileTimestamp: Equatable { } } +extension BuildValueFileTimestamp: Comparable { + public static func < (lhs: BuildValueFileTimestamp, rhs: BuildValueFileTimestamp) -> Bool { + if lhs.seconds != rhs.seconds { return lhs.seconds < rhs.seconds } + return lhs.nanoseconds < rhs.nanoseconds + } +} + extension BuildValueFileTimestamp: CustomStringConvertible { public var description: String { return "" } } +public extension Date { + init(_ ft: BuildValueFileTimestamp) { + // Using reference date, instead of 1970, which offers a bit more nanosecond precision since it is a lower absolute number. + self.init(timeIntervalSinceReferenceDate: Double(ft.seconds) - Date.timeIntervalBetween1970AndReferenceDate + (1.0e-9 * Double(ft.nanoseconds))) + } +} + /// Abstract class of a build value, use subclasses public class BuildValue: CustomStringConvertible, Equatable, Hashable { /// Build values can be of different kind - each has its own subclass diff --git a/unittests/Swift/BuildSystemEngineTests.swift b/unittests/Swift/BuildSystemEngineTests.swift index be41ba03f..40d21c3c6 100644 --- a/unittests/Swift/BuildSystemEngineTests.swift +++ b/unittests/Swift/BuildSystemEngineTests.swift @@ -95,6 +95,45 @@ class DependentCommand: BasicCommand { } } +// Enhanced command that returns a custom build value +class EnhancedCommand: ExternalCommand, ProducesCustomBuildValue { + private let fileInfo: BuildValueFileInfo + private var executed = false + + init(_ fileInfo: BuildValueFileInfo) { + self.fileInfo = fileInfo + } + + func getSignature(_ command: Command) -> [UInt8] { + return [] + } + + func start(_ command: Command, _ commandInterface: BuildSystemCommandInterface) {} + + func provideValue(_ command: Command, _ commandInterface: BuildSystemCommandInterface, _ buildValue: BuildValue, _ inputID: UInt) {} + + func execute(_ command: Command, _ commandInterface: BuildSystemCommandInterface) -> BuildValue { + executed = true + return BuildValue.SuccessfulCommand(outputInfos: [fileInfo]) + } + + func isResultValid(_ command: Command, _ buildValue: BuildValue) -> Bool { + guard let value = buildValue as? BuildValue.SuccessfulCommand else { + return false + } + + return value.outputInfos.count == 1 && value.outputInfos[0] == BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp()) + } + + func wasExecuted() -> Bool { + return executed + } + + func reset() { + executed = false + } +} + final class TestTool: Tool { let expectedCommands: [String: ExternalCommand] @@ -185,6 +224,10 @@ class TestBuildSystem { func run(target: String) { XCTAssertTrue(buildSystem.build(target: target)) } + + func build(node: String, walker: RuleResultsWalker) { + XCTAssertTrue(buildSystem.build(node: node, resultsWalker: walker)) + } } class BuildSystemEngineTests: XCTestCase { @@ -323,43 +366,8 @@ commands: let buildFile = makeTemporaryFile(basicBuildManifest) let databaseFile = makeTemporaryFile() - // Enhanced command that returns a custom build value - class EnhancedCommand: ExternalCommand, ProducesCustomBuildValue { - private var executed = false - - func getSignature(_ command: Command) -> [UInt8] { - return [] - } - - func start(_ command: Command, _ commandInterface: BuildSystemCommandInterface) {} - - func provideValue(_ command: Command, _ commandInterface: BuildSystemCommandInterface, _ buildValue: BuildValue, _ inputID: UInt) {} - - func execute(_ command: Command, _ commandInterface: BuildSystemCommandInterface) -> BuildValue { - executed = true - let fileInfo = BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp()) - return BuildValue.SuccessfulCommand(outputInfos: [fileInfo]) - } - - func isResultValid(_ command: Command, _ buildValue: BuildValue) -> Bool { - guard let value = buildValue as? BuildValue.SuccessfulCommand else { - return false - } - - return value.outputInfos.count == 1 && value.outputInfos[0] == BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp()) - } - - func wasExecuted() -> Bool { - return executed - } - - func reset() { - executed = false - } - } - let expectedCommands = [ - "maincommand": EnhancedCommand() + "maincommand": EnhancedCommand(BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp())) ] let buildSystem = TestBuildSystem( @@ -396,4 +404,117 @@ commands: let fileInfo = BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp()) XCTAssertEqual(maincommandResult.value, BuildValue.SuccessfulCommand(outputInfos: [fileInfo])) } + + func testBuildAndWalkNodes() { + let buildManifest = """ +client: + name: basic + version: 0 + file-system: default + +tools: + testtool: {} + +targets: + all: [""] + +commands: + cmdAll: + tool: testtool + inputs: ["a"] + outputs: [""] + cmdA: + tool: testtool + inputs: ["b", "c"] + outputs: ["a"] + cmdB: + tool: testtool + inputs: ["d"] + outputs: ["b"] + cmdC: + tool: testtool + outputs: ["c"] + cmdD: + tool: testtool + outputs: ["d"] + +""" + let buildFile = makeTemporaryFile(buildManifest) + let databaseFile = makeTemporaryFile() + + let fileInfoA = BuildValueFileInfo(device: 1, inode: 2, mode: 3, size: 4, modTime: BuildValueFileTimestamp(seconds: 1, nanoseconds: 1)) + let fileInfoB = BuildValueFileInfo(device: 11, inode: 12, mode: 13, size: 14, modTime: BuildValueFileTimestamp(seconds: 11, nanoseconds: 1)) + let fileInfoC = BuildValueFileInfo(device: 21, inode: 22, mode: 23, size: 24, modTime: BuildValueFileTimestamp(seconds: 21, nanoseconds: 1)) + let fileInfoD = BuildValueFileInfo(device: 31, inode: 32, mode: 33, size: 34, modTime: BuildValueFileTimestamp(seconds: 31, nanoseconds: 1)) + + let expectedCommands: [String: ExternalCommand] = [ + "cmdAll": BasicCommand(), + "cmdA": EnhancedCommand(fileInfoA), + "cmdB": EnhancedCommand(fileInfoB), + "cmdC": EnhancedCommand(fileInfoC), + "cmdD": EnhancedCommand(fileInfoD), + ] + + struct NodeResult: Equatable { + let key: String + let value: BuildValueFileInfo + + init(_ key: String, _ value: BuildValueFileInfo) { + self.key = key + self.value = value + } + } + + class TestResultsWalker: RuleResultsWalker { + let topNode: String + let action: RuleResultsWalkActionKind + var results: [NodeResult] = [] + + init(topNode: String, action: RuleResultsWalkActionKind) { + self.topNode = topNode + self.action = action + } + + func visit(key: BuildKey, result: BuildSystemRuleResult) -> RuleResultsWalkActionKind { + guard key.key != topNode else { + return .visitDependencies + } + guard let inputVal = result.value as? BuildValue.ExistingInput else { + return .visitDependencies + } + results.append(NodeResult(key.key, inputVal.fileInfo)) + return action + } + } + + func checkResults(_ action: RuleResultsWalkActionKind, block: ([NodeResult])->()) { + let buildSystem = TestBuildSystem( + buildFile: buildFile, + databaseFile: databaseFile, + expectedCommands: expectedCommands + ) + let walker = TestResultsWalker(topNode: "a", action: action) + buildSystem.build(node: "a", walker: walker) + block(walker.results) + } + + checkResults(.visitDependencies) { results in + XCTAssertEqual(results, [ + NodeResult("c", fileInfoC), + NodeResult("b", fileInfoB), + NodeResult("d", fileInfoD), + ]) + } + checkResults(.skipDependencies) { results in + XCTAssertEqual(results, [ + NodeResult("b", fileInfoB), + NodeResult("c", fileInfoC), + ]) + } + checkResults(.stop) { results in + XCTAssertEqual(results, [ + NodeResult("b", fileInfoB), + ]) + } + } }