Merge pull request #73 from ddunbar/command-triggers
[BuildSystem] Add support for "command timestamp" nodes.
diff --git a/docs/buildsystem.rst b/docs/buildsystem.rst
index c48fd5f..8ceace8 100644
--- a/docs/buildsystem.rst
+++ b/docs/buildsystem.rst
@@ -275,6 +275,19 @@
to files in the file system matching the name. This attribute can be used
to override that default.
+ * - is-command-timestamp
+ - A boolean value, indicating whether the node should be used to represent
+ the "timestamp" at which a command was run. When set, the node should
+ also be the output of some command in the graph. Whenever that command is
+ run, the node will take on a value representing the timestamp at which
+ the command was run.
+
+ This node can then be used as a (virtual) input to another command in
+ order to cause the downstream command to rerun whenever the producing
+ command is run.
+
+ Such nodes are always virtual nodes.
+
.. note::
FIXME: At some point, we probably want to support custom node types.
diff --git a/include/llbuild/BuildSystem/BuildNode.h b/include/llbuild/BuildSystem/BuildNode.h
index 3d1039f..5a8e64d 100644
--- a/include/llbuild/BuildSystem/BuildNode.h
+++ b/include/llbuild/BuildSystem/BuildNode.h
@@ -33,12 +33,20 @@
/// Whether or not this node is "virtual" (i.e., not a filesystem path).
bool virtualNode;
+ /// Whether this node represents a "command timestamp".
+ ///
+ /// Such nodes should always also be virtual.
+ bool commandTimestamp;
+
public:
- explicit BuildNode(StringRef name, bool isVirtual)
- : Node(name), virtualNode(isVirtual) {}
+ explicit BuildNode(StringRef name, bool isVirtual, bool isCommandTimestamp)
+ : Node(name), virtualNode(isVirtual),
+ commandTimestamp(isCommandTimestamp) {}
bool isVirtual() const { return virtualNode; }
+ bool isCommandTimestamp() const { return commandTimestamp; }
+
virtual bool configureAttribute(const ConfigureContext& ctx, StringRef name,
StringRef value) override;
virtual bool configureAttribute(const ConfigureContext& ctx, StringRef name,
diff --git a/include/llbuild/Core/BuildEngine.h b/include/llbuild/Core/BuildEngine.h
index 6374e10..959ea5a 100644
--- a/include/llbuild/Core/BuildEngine.h
+++ b/include/llbuild/Core/BuildEngine.h
@@ -30,6 +30,10 @@
class BuildDB;
class BuildEngine;
+/// A monotonically increasing timestamp identifying which iteration of a build
+/// an event occurred during.
+typedef uint64_t Timestamp;
+
/// This object contains the result of executing a task to produce the value for
/// a key.
struct Result {
@@ -234,6 +238,12 @@
/// Return the delegate the engine was configured with.
BuildEngineDelegate* getDelegate();
+ /// Get the current build timestamp used by the engine.
+ ///
+ /// The timestamp is a monotonically increasing value which is incremented
+ /// with each requested build.
+ Timestamp getCurrentTimestamp();
+
/// @name Rule Definition
/// @{
diff --git a/lib/BuildSystem/BuildNode.cpp b/lib/BuildSystem/BuildNode.cpp
index 6e1389f..f0e5b92 100644
--- a/lib/BuildSystem/BuildNode.cpp
+++ b/lib/BuildSystem/BuildNode.cpp
@@ -30,6 +30,19 @@
virtualNode = true;
} else if (value == "false") {
virtualNode = false;
+ commandTimestamp = false;
+ } else {
+ ctx.error("invalid value: '" + value + "' for attribute '"
+ + name + "'");
+ return false;
+ }
+ return true;
+ } else if (name == "is-command-timestamp") {
+ if (value == "true") {
+ commandTimestamp = true;
+ virtualNode = true;
+ } else if (value == "false") {
+ commandTimestamp = false;
} else {
ctx.error("invalid value: '" + value + "' for attribute '"
+ name + "'");
diff --git a/lib/BuildSystem/BuildSystem.cpp b/lib/BuildSystem/BuildSystem.cpp
index 154d64e..8c3feb3 100644
--- a/lib/BuildSystem/BuildSystem.cpp
+++ b/lib/BuildSystem/BuildSystem.cpp
@@ -831,7 +831,8 @@
std::unique_ptr<BuildNode>
BuildSystemImpl::lookupNode(StringRef name, bool isImplicit) {
bool isVirtual = !name.empty() && name[0] == '<' && name.back() == '>';
- return llvm::make_unique<BuildNode>(name, isVirtual);
+ return llvm::make_unique<BuildNode>(name, isVirtual,
+ /*isCommandTimestamp=*/false);
}
bool BuildSystemImpl::build(StringRef target) {
diff --git a/lib/BuildSystem/ExternalCommand.cpp b/lib/BuildSystem/ExternalCommand.cpp
index 2030e64..8cedaa9 100644
--- a/lib/BuildSystem/ExternalCommand.cpp
+++ b/lib/BuildSystem/ExternalCommand.cpp
@@ -129,7 +129,11 @@
assert(value.isSuccessfulCommand());
// If the node is virtual, the output is always a virtual input value.
- if (static_cast<BuildNode*>(node)->isVirtual()) {
+ //
+ // FIXME: Eliminate this, and make the build value array contain an array of
+ // build values.
+ auto buildNode = static_cast<BuildNode*>(node);
+ if (buildNode->isVirtual() && !buildNode->isCommandTimestamp()) {
return BuildValue::makeVirtualInput();
}
@@ -310,7 +314,14 @@
// FIXME: We need to delegate to the node here.
SmallVector<FileInfo, 8> outputInfos;
for (auto* node: outputs) {
- if (node->isVirtual()) {
+ if (node->isCommandTimestamp()) {
+ // FIXME: We currently have to shoehorn the timestamp into a fake file
+ // info, but need to refactor the command result to just store the node
+ // subvalues instead.
+ FileInfo info{};
+ info.size = bsci.getBuildEngine().getCurrentTimestamp();
+ outputInfos.push_back(info);
+ } else if (node->isVirtual()) {
outputInfos.push_back(FileInfo{});
} else {
outputInfos.push_back(node->getFileInfo(
diff --git a/lib/Core/BuildEngine.cpp b/lib/Core/BuildEngine.cpp
index 2232deb..d4b5c1d 100644
--- a/lib/Core/BuildEngine.cpp
+++ b/lib/Core/BuildEngine.cpp
@@ -1049,6 +1049,10 @@
return &delegate;
}
+ Timestamp getCurrentTimestamp() {
+ return currentTimestamp;
+ }
+
RuleInfo& getRuleInfoForKey(const KeyType& key) {
// Check if we have already found the rule.
auto it = ruleInfos.find(key);
@@ -1344,6 +1348,10 @@
return static_cast<BuildEngineImpl*>(impl)->getDelegate();
}
+Timestamp BuildEngine::getCurrentTimestamp() {
+ return static_cast<BuildEngineImpl*>(impl)->getCurrentTimestamp();
+}
+
void BuildEngine::addRule(Rule&& rule) {
static_cast<BuildEngineImpl*>(impl)->addRule(std::move(rule));
}
diff --git a/tests/BuildSystem/Build/command-dependencies.llbuild b/tests/BuildSystem/Build/command-dependencies.llbuild
new file mode 100644
index 0000000..5c4a9e9
--- /dev/null
+++ b/tests/BuildSystem/Build/command-dependencies.llbuild
@@ -0,0 +1,59 @@
+# Check the behavior of command dependencies.
+#
+# RUN: rm -rf %t.build
+# RUN: mkdir -p %t.build
+# RUN: touch %t.build/input
+# RUN: cp %s %t.build/build.llbuild
+
+# Both commands should run on the initial build.
+#
+# RUN: %{llbuild} buildsystem build --serial --chdir %t.build > %t.initial.out
+# RUN: %{FileCheck} --check-prefix CHECK-INITIAL --input-file %t.initial.out %s
+# CHECK-INITIAL: C.output-2
+# CHECK-INITIAL: C.output-1
+# RUN: diff %t.build/output-1 %t.build/output-2
+
+# No commands should run on a null rebuild.
+#
+# RUN: %{llbuild} buildsystem build --serial --chdir %t.build > %t.null.out
+# RUN: echo EOF >> %t.null.out
+# RUN: %{FileCheck} --check-prefix CHECK-NULL --input-file %t.null.out %s
+# RUN: diff %t.build/output-1 %t.build/output-2
+# CHECK-NULL-NOT: C.output
+# CHECK-NULL: EOF
+
+# Forcing the initial command to run should cause them both to run again.
+#
+# RUN: echo modified >> %t.build/input
+# RUN: %{llbuild} buildsystem build --serial --chdir %t.build > %t.rebuild.out
+# RUN: cat %t.rebuild.out
+# RUN: %{FileCheck} --check-prefix CHECK-REBUILD --input-file %t.rebuild.out %s
+# RUN: diff %t.build/output-1 %t.build/output-2
+#
+# CHECK-REBUILD: C.output-2
+# CHECK-REBUILD: C.output-1
+
+client:
+ name: basic
+
+targets:
+ "": ["<output>"]
+
+nodes:
+ "<C.output-2.timestamp>":
+ is-command-timestamp: true
+
+commands:
+ C.output-1:
+ tool: shell
+ inputs: ["<C.output-2.timestamp>"]
+ outputs: ["<output>"]
+ description: C.output-1
+ args: cp output-2 output-1
+
+ C.output-2:
+ tool: shell
+ inputs: ["input"]
+ outputs: ["<C.output-2.timestamp>"]
+ description: C.output-2
+ args: cp input output-2