summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux.yml3
-rw-r--r--.gitignore4
-rw-r--r--CMakeLists.txt8
-rwxr-xr-xconfigure.py2
-rw-r--r--doc/manual.asciidoc70
-rwxr-xr-xmisc/output_test.py18
-rw-r--r--misc/write_fake_manifests.py8
-rw-r--r--src/build.cc39
-rw-r--r--src/build_test.cc316
-rw-r--r--src/clparser.cc3
-rw-r--r--src/depfile_parser.cc2
-rw-r--r--src/deps_log_test.cc2
-rw-r--r--src/disk_interface.cc7
-rw-r--r--src/disk_interface_test.cc8
-rw-r--r--src/eval_env.h2
-rw-r--r--src/graph.cc88
-rw-r--r--src/graph.h18
-rw-r--r--src/graph_test.cc115
-rw-r--r--src/includes_normalize-win32.cc2
-rw-r--r--src/lexer.cc225
-rw-r--r--src/lexer.h1
-rw-r--r--src/lexer.in.cc2
-rw-r--r--src/manifest_parser.cc26
-rw-r--r--src/manifest_parser_test.cc10
-rw-r--r--src/missing_deps.cc2
-rw-r--r--src/missing_deps_test.cc2
-rw-r--r--src/ninja.cc112
-rw-r--r--src/ninja_test.cc2
-rw-r--r--src/state.cc6
-rw-r--r--src/state.h1
-rw-r--r--src/util.cc3
31 files changed, 899 insertions, 208 deletions
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 80c88c6..3c93e00 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -13,6 +13,9 @@ jobs:
image: centos:7
steps:
- uses: actions/checkout@v2
+ - uses: codespell-project/actions-codespell@master
+ with:
+ ignore_words_list: fo,wee
- name: Install dependencies
run: |
curl -L -O https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-Linux-x86_64.sh
diff --git a/.gitignore b/.gitignore
index fdca015..ca36ec8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,7 @@
/.clangd/
/compile_commands.json
/.cache/
+
+# Visual Studio files
+/.vs/
+/out/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b49c5b0..70fc5e9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,7 +39,7 @@ if(RE2C)
# the depfile parser and ninja lexers are generated using re2c.
function(re2c IN OUT)
add_custom_command(DEPENDS ${IN} OUTPUT ${OUT}
- COMMAND ${RE2C} -b -i --no-generation-date -o ${OUT} ${IN}
+ COMMAND ${RE2C} -b -i --no-generation-date --no-version -o ${OUT} ${IN}
)
endfunction()
re2c(${PROJECT_SOURCE_DIR}/src/depfile_parser.in.cc ${PROJECT_BINARY_DIR}/depfile_parser.cc)
@@ -122,10 +122,8 @@ if(WIN32)
src/msvc_helper-win32.cc
src/msvc_helper_main-win32.cc
src/getopt.c
+ src/minidump-win32.cc
)
- if(MSVC)
- target_sources(libninja PRIVATE src/minidump-win32.cc)
- endif()
else()
target_sources(libninja PRIVATE src/subprocess-posix.cc)
if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
@@ -234,4 +232,4 @@ if(BUILD_TESTING)
add_test(NAME NinjaTest COMMAND ninja_test)
endif()
-install(TARGETS ninja DESTINATION bin)
+install(TARGETS ninja)
diff --git a/configure.py b/configure.py
index e0a5a22..4390434 100755
--- a/configure.py
+++ b/configure.py
@@ -479,7 +479,7 @@ def has_re2c():
return False
if has_re2c():
n.rule('re2c',
- command='re2c -b -i --no-generation-date -o $out $in',
+ command='re2c -b -i --no-generation-date --no-version -o $out $in',
description='RE2C $out')
# Generate the .cc files in the source directory so we can check them in.
n.build(src('depfile_parser.cc'), 're2c', src('depfile_parser.in.cc'))
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 2b948b9..2062a2a 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -257,6 +257,10 @@ than the _depth_ mode.
executed in order, may be used to rebuild those targets, assuming that all
output files are out of date.
+`inputs`:: given a list of targets, print a list of all inputs used to
+rebuild those targets.
+_Available since Ninja 1.11._
+
`clean`:: remove built files. By default it removes all built files
except for those created by the generator. Adding the `-g` flag also
removes built files created by the generator (see <<ref_rule,the rule
@@ -294,7 +298,7 @@ of a generator-target) implicitly, but does not have an explicit or order-only
dependency path to the generator-target, is considered broken.
+
The tool's findings can be verified by trying to build the listed targets in
-a clean outdir without buidling any other targets. The build should fail for
+a clean outdir without building any other targets. The build should fail for
each of them with a missing include error or equivalent pointing to the
generated file.
_Available since Ninja 1.11._
@@ -304,9 +308,40 @@ _Available since Ninja 1.11._
`restat`:: updates all recorded file modification timestamps in the `.ninja_log`
file. _Available since Ninja 1.10._
-`rules`:: output the list of all rules (eventually with their description
-if they have one). It can be used to know which rule name to pass to
-+ninja -t targets rule _name_+ or +ninja -t compdb+.
+`rules`:: output the list of all rules. It can be used to know which rule name
+to pass to +ninja -t targets rule _name_+ or +ninja -t compdb+. Adding the `-d`
+flag also prints the description of the rules.
+
+`msvc`:: Available on Windows hosts only.
+Helper tool to invoke the `cl.exe` compiler with a pre-defined set of
+environment variables, as in:
++
+----
+ninja -t msvc -e ENVFILE -- cl.exe <arguments>
+----
++
+Where `ENVFILE` is a binary file that contains an environment block suitable
+for CreateProcessA() on Windows (i.e. a series of zero-terminated strings that
+look like NAME=VALUE, followed by an extra zero terminator). Note that this uses
+the local codepage encoding.
+
+This tool also supports a deprecated way of parsing the compiler's output when
+the `/showIncludes` flag is used, and generating a GCC-compatible depfile from it.
++
+---
+ninja -t msvc -o DEPFILE [-p STRING] -- cl.exe /showIncludes <arguments>
+---
++
+
+When using this option, `-p STRING` can be used to pass the localized line prefix
+that `cl.exe` uses to output dependency information. For English-speaking regions
+this is `"Note: including file: "` without the double quotes, but will be different
+for other regions.
+
+Note that Ninja supports this natively now, with the use of `deps = msvc` and
+`msvc_deps_prefix` in Ninja files. Native support also avoids launching an extra
+tool process each time the compiler must be called, which can speed up builds
+noticeably on Windows.
`wincodepage`:: Available on Windows hosts (_since Ninja 1.11_).
Prints the Windows code page whose encoding is expected in the build file.
@@ -748,6 +783,8 @@ A file is a series of declarations. A declaration can be one of:
Order-only dependencies may be tacked on the end with +||
_dependency1_ _dependency2_+. (See <<ref_dependencies,the reference on
dependency types>>.)
+ Validations may be taked on the end with +|@ _validation1_ _validation2_+.
+ (See <<validations,the reference on validations>>.)
+
Implicit outputs _(available since Ninja 1.7)_ may be added before
the `:` with +| _output1_ _output2_+ and do not appear in `$out`.
@@ -1006,6 +1043,31 @@ express the implicit dependency.)
File paths are compared as is, which means that an absolute path and a
relative path, pointing to the same file, are considered different by Ninja.
+[[validations]]
+Validations
+~~~~~~~~~~~
+Validations listed on the build line cause the specified files to be
+added to the top level of the build graph (as if they were specified
+on the Ninja command line) whenever the build line is a transitive
+dependency of one of the targets specified on the command line or a
+default target.
+
+Validations are added to the build graph regardless of whether the output
+files of the build statement are dirty are not, and the dirty state of
+the build statement that outputs the file being used as a validation
+has no effect on the dirty state of the build statement that requested it.
+
+A build edge can list another build edge as a validation even if the second
+edge depends on the first.
+
+Validations are designed to handle rules that perform error checking but
+don't produce any artifacts needed by the build, for example static
+analysis tools. Marking the static analysis rule as an implicit input
+of the main build rule of the source files or of the rules that depend
+on the main build rule would slow down the critical path of the build,
+but using a validation would allow the build to proceed in parallel with
+the static analysis rule once the main build rule is complete.
+
Variable expansion
~~~~~~~~~~~~~~~~~~
diff --git a/misc/output_test.py b/misc/output_test.py
index 45698f1..141716c 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -134,5 +134,23 @@ red
output = run(Output.BUILD_SIMPLE_ECHO, flags='-C$PWD', pipe=True)
self.assertEqual(output.splitlines()[0][:25], "ninja: Entering directory")
+ def test_tool_inputs(self):
+ plan = '''
+rule cat
+ command = cat $in $out
+build out1 : cat in1
+build out2 : cat in2 out1
+build out3 : cat out2 out1 | implicit || order_only
+'''
+ self.assertEqual(run(plan, flags='-t inputs out3'),
+'''implicit
+in1
+in2
+order_only
+out1
+out2
+''')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py
index b3594de..abcb677 100644
--- a/misc/write_fake_manifests.py
+++ b/misc/write_fake_manifests.py
@@ -65,7 +65,7 @@ class GenRandom(object):
def _n_unique_strings(self, n):
seen = set([None])
return [self._unique_string(seen, avg_options=3, p_suffix=0.4)
- for _ in xrange(n)]
+ for _ in range(n)]
def target_name(self):
return self._unique_string(p_suffix=0, seen=self.seen_names)
@@ -73,7 +73,7 @@ class GenRandom(object):
def path(self):
return os.path.sep.join([
self._unique_string(self.seen_names, avg_options=1, p_suffix=0)
- for _ in xrange(1 + paretoint(0.6, alpha=4))])
+ for _ in range(1 + paretoint(0.6, alpha=4))])
def src_obj_pairs(self, path, name):
num_sources = paretoint(55, alpha=2) + 1
@@ -84,7 +84,7 @@ class GenRandom(object):
def defines(self):
return [
'-DENABLE_' + self._unique_string(self.seen_defines).upper()
- for _ in xrange(paretoint(20, alpha=3))]
+ for _ in range(paretoint(20, alpha=3))]
LIB, EXE = 0, 1
@@ -227,7 +227,7 @@ def random_targets(num_targets, src_dir):
gen = GenRandom(src_dir)
# N-1 static libraries, and 1 executable depending on all of them.
- targets = [Target(gen, LIB) for i in xrange(num_targets - 1)]
+ targets = [Target(gen, LIB) for i in range(num_targets - 1)]
for i in range(len(targets)):
targets[i].deps = [t for t in targets[0:i] if random.random() < 0.05]
diff --git a/src/build.cc b/src/build.cc
index cf07846..6f11ed7 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -384,8 +384,21 @@ bool Plan::RefreshDyndepDependents(DependencyScan* scan, const Node* node,
Node* n = *i;
// Check if this dependent node is now dirty. Also checks for new cycles.
- if (!scan->RecomputeDirty(n, err))
+ std::vector<Node*> validation_nodes;
+ if (!scan->RecomputeDirty(n, &validation_nodes, err))
return false;
+
+ // Add any validation nodes found during RecomputeDirty as new top level
+ // targets.
+ for (std::vector<Node*>::iterator v = validation_nodes.begin();
+ v != validation_nodes.end(); ++v) {
+ if (Edge* in_edge = (*v)->in_edge()) {
+ if (!in_edge->outputs_ready() &&
+ !AddTarget(*v, err)) {
+ return false;
+ }
+ }
+ }
if (!n->dirty())
continue;
@@ -553,16 +566,28 @@ Node* Builder::AddTarget(const string& name, string* err) {
}
bool Builder::AddTarget(Node* target, string* err) {
- if (!scan_.RecomputeDirty(target, err))
+ std::vector<Node*> validation_nodes;
+ if (!scan_.RecomputeDirty(target, &validation_nodes, err))
return false;
- if (Edge* in_edge = target->in_edge()) {
- if (in_edge->outputs_ready())
- return true; // Nothing to do.
+ Edge* in_edge = target->in_edge();
+ if (!in_edge || !in_edge->outputs_ready()) {
+ if (!plan_.AddTarget(target, err)) {
+ return false;
+ }
}
- if (!plan_.AddTarget(target, err))
- return false;
+ // Also add any validation nodes found during RecomputeDirty as top level
+ // targets.
+ for (std::vector<Node*>::iterator n = validation_nodes.begin();
+ n != validation_nodes.end(); ++n) {
+ if (Edge* validation_in_edge = (*n)->in_edge()) {
+ if (!validation_in_edge->outputs_ready() &&
+ !plan_.AddTarget(*n, err)) {
+ return false;
+ }
+ }
+ }
return true;
}
diff --git a/src/build_test.cc b/src/build_test.cc
index 8b6dca2..4ef62b2 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -897,6 +897,14 @@ TEST_F(BuildTest, MissingTarget) {
EXPECT_EQ("unknown target: 'meow'", err);
}
+TEST_F(BuildTest, MissingInputTarget) {
+ // Target is a missing input file
+ string err;
+ Dirty("in1");
+ EXPECT_FALSE(builder_.AddTarget("in1", &err));
+ EXPECT_EQ("'in1' missing and no known rule to make it", err);
+}
+
TEST_F(BuildTest, MakeDirs) {
string err;
@@ -1229,6 +1237,7 @@ void TestPhonyUseCase(BuildTest* t, int i) {
));
// Set up test.
+ builder_.command_runner_.release(); // BuildTest owns the CommandRunner
builder_.command_runner_.reset(&command_runner_);
fs_.Create("blank", ""); // a "real" file
@@ -2346,7 +2355,7 @@ struct BuildWithDepsLogTest : public BuildTest {
void* builder_;
};
-/// Run a straightforwad build where the deps log is used.
+/// Run a straightforward build where the deps log is used.
TEST_F(BuildWithDepsLogTest, Straightforward) {
string err;
// Note: in1 was created by the superclass SetUp().
@@ -3228,6 +3237,67 @@ TEST_F(BuildTest, DyndepBuildDiscoverNewInput) {
EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
}
+TEST_F(BuildTest, DyndepBuildDiscoverNewInputWithValidation) {
+ // Verify that a dyndep file cannot contain the |@ validation
+ // syntax.
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch $out\n"
+"rule cp\n"
+" command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build out: touch || dd\n"
+" dyndep = dd\n"
+));
+ fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep |@ validation\n"
+);
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ EXPECT_FALSE(builder_.Build(&err));
+
+ string err_first_line = err.substr(0, err.find("\n"));
+ EXPECT_EQ("dd:2: expected newline, got '|@'", err_first_line);
+}
+
+TEST_F(BuildTest, DyndepBuildDiscoverNewInputWithTransitiveValidation) {
+ // Verify that a dyndep file can be built and loaded to discover
+ // a new input to an edge that has a validation edge.
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch $out\n"
+"rule cp\n"
+" command = cp $in $out\n"
+"build dd: cp dd-in\n"
+"build in: touch |@ validation\n"
+"build validation: touch in out\n"
+"build out: touch || dd\n"
+" dyndep = dd\n"
+ ));
+ fs_.Create("dd-in",
+"ninja_dyndep_version = 1\n"
+"build out: dyndep | in\n"
+);
+ fs_.Tick();
+ fs_.Create("out", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(4u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
+ EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
+ EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
+ EXPECT_EQ("touch validation", command_runner_.commands_ran_[3]);
+}
+
TEST_F(BuildTest, DyndepBuildDiscoverImplicitConnection) {
// Verify that a dyndep file can be built and loaded to discover
// that one edge has an implicit output that is also an implicit
@@ -3671,3 +3741,247 @@ TEST_F(BuildTest, DyndepTwoLevelDiscoveredDirty) {
EXPECT_EQ("touch tmp", command_runner_.commands_ran_[3]);
EXPECT_EQ("touch out", command_runner_.commands_ran_[4]);
}
+
+TEST_F(BuildTest, Validation) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "build out: cat in |@ validate\n"
+ "build validate: cat in2\n"));
+
+ fs_.Create("in", "");
+ fs_.Create("in2", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+
+ // Test touching "in" only rebuilds "out" ("validate" doesn't depend on
+ // "out").
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
+
+ // Test touching "in2" only rebuilds "validate" ("out" doesn't depend on
+ // "validate").
+ fs_.Tick();
+ fs_.Create("in2", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in2 > validate", command_runner_.commands_ran_[0]);
+}
+
+TEST_F(BuildTest, ValidationDependsOnOutput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "build out: cat in |@ validate\n"
+ "build validate: cat in2 | out\n"));
+
+ fs_.Create("in", "");
+ fs_.Create("in2", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+
+ // Test touching "in" rebuilds "out" and "validate".
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+
+ // Test touching "in2" only rebuilds "validate" ("out" doesn't depend on
+ // "validate").
+ fs_.Tick();
+ fs_.Create("in2", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in2 > validate", command_runner_.commands_ran_[0]);
+}
+
+TEST_F(BuildWithDepsLogTest, ValidationThroughDepfile) {
+ const char* manifest =
+ "build out: cat in |@ validate\n"
+ "build validate: cat in2 | out\n"
+ "build out2: cat in3\n"
+ " deps = gcc\n"
+ " depfile = out2.d\n";
+
+ string err;
+
+ {
+ fs_.Create("in", "");
+ fs_.Create("in2", "");
+ fs_.Create("in3", "");
+ fs_.Create("out2.d", "out: out");
+
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
+ builder.command_runner_.reset(&command_runner_);
+
+ EXPECT_TRUE(builder.AddTarget("out2", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // On the first build, only the out2 command is run.
+ ASSERT_EQ(command_runner_.commands_ran_.size(), 1);
+ EXPECT_EQ("cat in3 > out2", command_runner_.commands_ran_[0]);
+
+ // The deps file should have been removed.
+ EXPECT_EQ(0, fs_.Stat("out2.d", &err));
+
+ deps_log.Close();
+ builder.command_runner_.release();
+ }
+
+ fs_.Tick();
+ command_runner_.commands_ran_.clear();
+
+ {
+ fs_.Create("in2", "");
+ fs_.Create("in3", "");
+
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
+ builder.command_runner_.reset(&command_runner_);
+
+ EXPECT_TRUE(builder.AddTarget("out2", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // The out and validate actions should have been run as well as out2.
+ ASSERT_EQ(command_runner_.commands_ran_.size(), 3);
+ // out has to run first, as both out2 and validate depend on it.
+ EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
+
+ deps_log.Close();
+ builder.command_runner_.release();
+ }
+}
+
+TEST_F(BuildTest, ValidationCircular) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "build out: cat in |@ out2\n"
+ "build out2: cat in2 |@ out\n"));
+
+ fs_.Create("in", "");
+ fs_.Create("in2", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+
+ // Test touching "in" rebuilds "out".
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
+
+ // Test touching "in2" rebuilds "out2".
+ fs_.Tick();
+ fs_.Create("in2", "");
+
+ err.clear();
+ command_runner_.commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in2 > out2", command_runner_.commands_ran_[0]);
+}
+
+TEST_F(BuildTest, ValidationWithCircularDependency) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "build out: cat in |@ validate\n"
+ "build validate: cat validate_in | out\n"
+ "build validate_in: cat validate\n"));
+
+ fs_.Create("in", "");
+
+ string err;
+ EXPECT_FALSE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err);
+}
diff --git a/src/clparser.cc b/src/clparser.cc
index 070bcfd..3d3e7de 100644
--- a/src/clparser.cc
+++ b/src/clparser.cc
@@ -72,7 +72,8 @@ bool CLParser::FilterInputFilename(string line) {
return EndsWith(line, ".c") ||
EndsWith(line, ".cc") ||
EndsWith(line, ".cxx") ||
- EndsWith(line, ".cpp");
+ EndsWith(line, ".cpp") ||
+ EndsWith(line, ".c++");
}
// static
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index bffeb76..98fba2e 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 1.3 */
+/* Generated by re2c */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc
index 1c29d89..13fcc78 100644
--- a/src/deps_log_test.cc
+++ b/src/deps_log_test.cc
@@ -390,7 +390,7 @@ TEST_F(DepsLogTest, Truncated) {
DepsLog log;
EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
if (!err.empty()) {
- // At some point the log will be so short as to be unparseable.
+ // At some point the log will be so short as to be unparsable.
break;
}
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index a37c570..e73d901 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -180,12 +180,13 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
dir = path;
}
- transform(dir.begin(), dir.end(), dir.begin(), ::tolower);
+ string dir_lowercase = dir;
+ transform(dir.begin(), dir.end(), dir_lowercase.begin(), ::tolower);
transform(base.begin(), base.end(), base.begin(), ::tolower);
- Cache::iterator ci = cache_.find(dir);
+ Cache::iterator ci = cache_.find(dir_lowercase);
if (ci == cache_.end()) {
- ci = cache_.insert(make_pair(dir, DirCache())).first;
+ ci = cache_.insert(make_pair(dir_lowercase, DirCache())).first;
if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) {
cache_.erase(ci);
return -1;
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index 339aea1..5e952ed 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -272,7 +272,7 @@ TEST_F(StatTest, Simple) {
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL);
+ scan_.RecomputeDirty(out, NULL, NULL);
ASSERT_EQ(2u, stats_.size());
ASSERT_EQ("out", stats_[0]);
ASSERT_EQ("in", stats_[1]);
@@ -288,7 +288,7 @@ TEST_F(StatTest, TwoStep) {
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL);
+ scan_.RecomputeDirty(out, NULL, NULL);
ASSERT_EQ(3u, stats_.size());
ASSERT_EQ("out", stats_[0]);
ASSERT_TRUE(GetNode("out")->dirty());
@@ -308,7 +308,7 @@ TEST_F(StatTest, Tree) {
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL);
+ scan_.RecomputeDirty(out, NULL, NULL);
ASSERT_EQ(1u + 6u, stats_.size());
ASSERT_EQ("mid1", stats_[1]);
ASSERT_TRUE(GetNode("mid1")->dirty());
@@ -329,7 +329,7 @@ TEST_F(StatTest, Middle) {
EXPECT_TRUE(out->Stat(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL);
+ scan_.RecomputeDirty(out, NULL, NULL);
ASSERT_FALSE(GetNode("in")->dirty());
ASSERT_TRUE(GetNode("mid")->dirty());
ASSERT_TRUE(GetNode("out")->dirty());
diff --git a/src/eval_env.h b/src/eval_env.h
index ca7daa4..677dc21 100644
--- a/src/eval_env.h
+++ b/src/eval_env.h
@@ -55,7 +55,7 @@ private:
TokenList parsed_;
};
-/// An invokable build command and associated metadata (description, etc.).
+/// An invocable build command and associated metadata (description, etc.).
struct Rule {
explicit Rule(const std::string& name) : name_(name) {}
diff --git a/src/graph.cc b/src/graph.cc
index c875d3b..43ba45a 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -15,6 +15,7 @@
#include "graph.h"
#include <algorithm>
+#include <deque>
#include <assert.h>
#include <stdio.h>
@@ -46,13 +47,42 @@ void Node::UpdatePhonyMtime(TimeStamp mtime) {
}
}
-bool DependencyScan::RecomputeDirty(Node* node, string* err) {
- vector<Node*> stack;
- return RecomputeDirty(node, &stack, err);
+bool DependencyScan::RecomputeDirty(Node* initial_node,
+ std::vector<Node*>* validation_nodes,
+ string* err) {
+ std::vector<Node*> stack;
+ std::vector<Node*> new_validation_nodes;
+
+ std::deque<Node*> nodes(1, initial_node);
+
+ // RecomputeNodeDirty might return new validation nodes that need to be
+ // checked for dirty state, keep a queue of nodes to visit.
+ while (!nodes.empty()) {
+ Node* node = nodes.front();
+ nodes.pop_front();
+
+ stack.clear();
+ new_validation_nodes.clear();
+
+ if (!RecomputeNodeDirty(node, &stack, &new_validation_nodes, err))
+ return false;
+ nodes.insert(nodes.end(), new_validation_nodes.begin(),
+ new_validation_nodes.end());
+ if (!new_validation_nodes.empty()) {
+ assert(validation_nodes &&
+ "validations require RecomputeDirty to be called with validation_nodes");
+ validation_nodes->insert(validation_nodes->end(),
+ new_validation_nodes.begin(),
+ new_validation_nodes.end());
+ }
+ }
+
+ return true;
}
-bool DependencyScan::RecomputeDirty(Node* node, vector<Node*>* stack,
- string* err) {
+bool DependencyScan::RecomputeNodeDirty(Node* node, std::vector<Node*>* stack,
+ std::vector<Node*>* validation_nodes,
+ string* err) {
Edge* edge = node->in_edge();
if (!edge) {
// If we already visited this leaf node then we are done.
@@ -96,7 +126,7 @@ bool DependencyScan::RecomputeDirty(Node* node, vector<Node*>* stack,
// Later during the build the dyndep file will become ready and be
// loaded to update this edge before it can possibly be scheduled.
if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
- if (!RecomputeDirty(edge->dyndep_, stack, err))
+ if (!RecomputeNodeDirty(edge->dyndep_, stack, validation_nodes, err))
return false;
if (!edge->dyndep_->in_edge() ||
@@ -127,12 +157,20 @@ bool DependencyScan::RecomputeDirty(Node* node, vector<Node*>* stack,
}
}
+ // Store any validation nodes from the edge for adding to the initial
+ // nodes. Don't recurse into them, that would trigger the dependency
+ // cycle detector if the validation node depends on this node.
+ // RecomputeDirty will add the validation nodes to the initial nodes
+ // and recurse into them.
+ validation_nodes->insert(validation_nodes->end(),
+ edge->validations_.begin(), edge->validations_.end());
+
// Visit all inputs; we're dirty if any of the inputs are dirty.
Node* most_recent_input = NULL;
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end(); ++i) {
// Visit this input.
- if (!RecomputeDirty(*i, stack, err))
+ if (!RecomputeNodeDirty(*i, stack, validation_nodes, err))
return false;
// If an input is not ready, neither are our outputs.
@@ -418,6 +456,28 @@ std::string EdgeEnv::MakePathList(const Node* const* const span,
return result;
}
+void Edge::CollectInputs(bool shell_escape,
+ std::vector<std::string>* out) const {
+ for (std::vector<Node*>::const_iterator it = inputs_.begin();
+ it != inputs_.end(); ++it) {
+ std::string path = (*it)->PathDecanonicalized();
+ if (shell_escape) {
+ std::string unescaped;
+ unescaped.swap(path);
+#ifdef _WIN32
+ GetWin32EscapedString(unescaped, &path);
+#else
+ GetShellEscapedString(unescaped, &path);
+#endif
+ }
+#if __cplusplus >= 201103L
+ out->push_back(std::move(path));
+#else
+ out->push_back(path);
+#endif
+ }
+}
+
std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
string command = GetBinding("command");
if (incl_rsp_file) {
@@ -463,6 +523,13 @@ void Edge::Dump(const char* prefix) const {
i != outputs_.end() && *i != NULL; ++i) {
printf("%s ", (*i)->path().c_str());
}
+ if (!validations_.empty()) {
+ printf(" validations ");
+ for (std::vector<Node*>::const_iterator i = validations_.begin();
+ i != validations_.end() && *i != NULL; ++i) {
+ printf("%s ", (*i)->path().c_str());
+ }
+ }
if (pool_) {
if (!pool_->name().empty()) {
printf("(in pool '%s')", pool_->name().c_str());
@@ -519,6 +586,13 @@ void Node::Dump(const char* prefix) const {
e != out_edges().end() && *e != NULL; ++e) {
(*e)->Dump(" +- ");
}
+ if (!validation_out_edges().empty()) {
+ printf(" validation out edges:\n");
+ for (std::vector<Edge*>::const_iterator e = validation_out_edges().begin();
+ e != validation_out_edges().end() && *e != NULL; ++e) {
+ (*e)->Dump(" +- ");
+ }
+ }
}
bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) {
diff --git a/src/graph.h b/src/graph.h
index fac8059..9de67d2 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -108,7 +108,9 @@ struct Node {
void set_id(int id) { id_ = id; }
const std::vector<Edge*>& out_edges() const { return out_edges_; }
+ const std::vector<Edge*>& validation_out_edges() const { return validation_out_edges_; }
void AddOutEdge(Edge* edge) { out_edges_.push_back(edge); }
+ void AddValidationOutEdge(Edge* edge) { validation_out_edges_.push_back(edge); }
void Dump(const char* prefix="") const;
@@ -151,6 +153,9 @@ private:
/// All Edges that use this Node as an input.
std::vector<Edge*> out_edges_;
+ /// All Edges that use this Node as a validation.
+ std::vector<Edge*> validation_out_edges_;
+
/// A dense integer id for the node, assigned and used by DepsLog.
int id_;
};
@@ -190,10 +195,14 @@ struct Edge {
void Dump(const char* prefix="") const;
+ // Append all edge explicit inputs to |*out|. Possibly with shell escaping.
+ void CollectInputs(bool shell_escape, std::vector<std::string>* out) const;
+
const Rule* rule_;
Pool* pool_;
std::vector<Node*> inputs_;
std::vector<Node*> outputs_;
+ std::vector<Node*> validations_;
Node* dyndep_;
BindingEnv* env_;
VisitMark mark_;
@@ -309,12 +318,14 @@ struct DependencyScan {
dep_loader_(state, deps_log, disk_interface, depfile_parser_options),
dyndep_loader_(state, disk_interface) {}
- /// Update the |dirty_| state of the given node by inspecting its input edge.
+ /// Update the |dirty_| state of the given nodes by transitively inspecting
+ /// their input edges.
/// Examine inputs, outputs, and command lines to judge whether an edge
/// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_|
/// state accordingly.
+ /// Appends any validation nodes found to the nodes parameter.
/// Returns false on failure.
- bool RecomputeDirty(Node* node, std::string* err);
+ bool RecomputeDirty(Node* node, std::vector<Node*>* validation_nodes, std::string* err);
/// Recompute whether any output of the edge is dirty, if so sets |*dirty|.
/// Returns false on failure.
@@ -340,7 +351,8 @@ struct DependencyScan {
bool LoadDyndeps(Node* node, DyndepFile* ddf, std::string* err) const;
private:
- bool RecomputeDirty(Node* node, std::vector<Node*>* stack, std::string* err);
+ bool RecomputeNodeDirty(Node* node, std::vector<Node*>* stack,
+ std::vector<Node*>* validation_nodes, std::string* err);
bool VerifyDAG(Node* node, std::vector<Node*>* stack, std::string* err);
/// Recompute whether a given single output should be marked dirty.
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 4f0de98..9dba8af 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -33,7 +33,7 @@ TEST_F(GraphTest, MissingImplicit) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
// A missing implicit dep *should* make the output dirty.
@@ -51,7 +51,7 @@ TEST_F(GraphTest, ModifiedImplicit) {
fs_.Create("implicit", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
// A modified implicit dep should make the output dirty.
@@ -71,7 +71,7 @@ TEST_F(GraphTest, FunkyMakefilePath) {
fs_.Create("implicit.h", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
// implicit.h has changed, though our depfile refers to it with a
@@ -94,7 +94,7 @@ TEST_F(GraphTest, ExplicitImplicit) {
fs_.Create("data", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
// We have both an implicit and an explicit dep on implicit.h.
@@ -122,7 +122,7 @@ TEST_F(GraphTest, ImplicitOutputMissing) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out")->dirty());
@@ -138,7 +138,7 @@ TEST_F(GraphTest, ImplicitOutputOutOfDate) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out")->dirty());
@@ -162,7 +162,7 @@ TEST_F(GraphTest, ImplicitOutputOnlyMissing) {
fs_.Create("in", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.imp")->dirty());
@@ -176,7 +176,7 @@ TEST_F(GraphTest, ImplicitOutputOnlyOutOfDate) {
fs_.Create("in", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.imp")->dirty());
@@ -193,7 +193,7 @@ TEST_F(GraphTest, PathWithCurrentDirectory) {
fs_.Create("out.o", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
@@ -215,6 +215,39 @@ TEST_F(GraphTest, RootNodes) {
}
}
+TEST_F(GraphTest, CollectInputs) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+ &state_,
+ "build out$ 1: cat in1 in2 in$ with$ space | implicit || order_only\n"));
+
+ std::vector<std::string> inputs;
+ Edge* edge = GetNode("out 1")->in_edge();
+
+ // Test without shell escaping.
+ inputs.clear();
+ edge->CollectInputs(false, &inputs);
+ EXPECT_EQ(5u, inputs.size());
+ EXPECT_EQ("in1", inputs[0]);
+ EXPECT_EQ("in2", inputs[1]);
+ EXPECT_EQ("in with space", inputs[2]);
+ EXPECT_EQ("implicit", inputs[3]);
+ EXPECT_EQ("order_only", inputs[4]);
+
+ // Test with shell escaping.
+ inputs.clear();
+ edge->CollectInputs(true, &inputs);
+ EXPECT_EQ(5u, inputs.size());
+ EXPECT_EQ("in1", inputs[0]);
+ EXPECT_EQ("in2", inputs[1]);
+#ifdef _WIN32
+ EXPECT_EQ("\"in with space\"", inputs[2]);
+#else
+ EXPECT_EQ("'in with space'", inputs[2]);
+#endif
+ EXPECT_EQ("implicit", inputs[3]);
+ EXPECT_EQ("order_only", inputs[4]);
+}
+
TEST_F(GraphTest, VarInOutPathEscaping) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build a$ b: cat no'space with$ space$$ no\"space2\n"));
@@ -241,7 +274,7 @@ TEST_F(GraphTest, DepfileWithCanonicalizablePath) {
fs_.Create("out.o", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
@@ -261,13 +294,13 @@ TEST_F(GraphTest, DepfileRemoved) {
fs_.Create("out.o", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("out.o")->dirty());
state_.Reset();
fs_.RemoveFile("out.o.d");
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("out.o")->dirty());
}
@@ -314,7 +347,7 @@ TEST_F(GraphTest, NestedPhonyPrintsDone) {
"build n2: phony n1\n"
);
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("n2"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("n2"), NULL, &err));
ASSERT_EQ("", err);
Plan plan_;
@@ -333,7 +366,7 @@ TEST_F(GraphTest, PhonySelfReferenceError) {
parser_opts);
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
ASSERT_EQ("dependency cycle: a -> a [-w phonycycle=err]", err);
}
@@ -345,7 +378,7 @@ TEST_F(GraphTest, DependencyCycle) {
"build pre: cat out\n");
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
}
@@ -353,7 +386,7 @@ TEST_F(GraphTest, CycleInEdgesButNotInNodes1) {
string err;
AssertParse(&state_,
"build a b: cat a\n");
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
ASSERT_EQ("dependency cycle: a -> a", err);
}
@@ -361,7 +394,7 @@ TEST_F(GraphTest, CycleInEdgesButNotInNodes2) {
string err;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build b a: cat a\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
ASSERT_EQ("dependency cycle: a -> a", err);
}
@@ -370,7 +403,7 @@ TEST_F(GraphTest, CycleInEdgesButNotInNodes3) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build a b: cat c\n"
"build c: cat a\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
ASSERT_EQ("dependency cycle: a -> c -> a", err);
}
@@ -382,7 +415,7 @@ TEST_F(GraphTest, CycleInEdgesButNotInNodes4) {
"build b: cat a\n"
"build a e: cat d\n"
"build f: cat e\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("f"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("f"), NULL, &err));
ASSERT_EQ("dependency cycle: a -> d -> c -> b -> a", err);
}
@@ -398,7 +431,7 @@ TEST_F(GraphTest, CycleWithLengthZeroFromDepfile) {
fs_.Create("dep.d", "a: b\n");
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
ASSERT_EQ("dependency cycle: b -> b", err);
// Despite the depfile causing edge to be a cycle (it has outputs a and b,
@@ -423,7 +456,7 @@ TEST_F(GraphTest, CycleWithLengthOneFromDepfile) {
fs_.Create("dep.d", "a: c\n");
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
ASSERT_EQ("dependency cycle: b -> c -> b", err);
// Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
@@ -450,7 +483,7 @@ TEST_F(GraphTest, CycleWithLengthOneFromDepfileOneHopAway) {
fs_.Create("dep.d", "a: c\n");
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("d"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("d"), NULL, &err));
ASSERT_EQ("dependency cycle: b -> c -> b", err);
// Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
@@ -705,7 +738,7 @@ TEST_F(GraphTest, DyndepFileMissing) {
);
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("loading 'dd': No such file or directory", err);
}
@@ -721,7 +754,7 @@ TEST_F(GraphTest, DyndepFileError) {
);
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("'out' not mentioned in its dyndep file 'dd'", err);
}
@@ -741,7 +774,7 @@ TEST_F(GraphTest, DyndepImplicitInputNewer) {
fs_.Create("in", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("in")->dirty());
@@ -769,7 +802,7 @@ TEST_F(GraphTest, DyndepFileReady) {
fs_.Create("in", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("in")->dirty());
@@ -794,7 +827,7 @@ TEST_F(GraphTest, DyndepFileNotClean) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("dd")->dirty());
@@ -820,7 +853,7 @@ TEST_F(GraphTest, DyndepFileNotReady) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_FALSE(GetNode("dd")->dirty());
@@ -848,7 +881,7 @@ TEST_F(GraphTest, DyndepFileSecondNotReady) {
fs_.Create("out", "");
string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
ASSERT_EQ("", err);
EXPECT_TRUE(GetNode("dd1")->dirty());
@@ -877,7 +910,7 @@ TEST_F(GraphTest, DyndepFileCircular) {
Edge* edge = GetNode("out")->in_edge();
string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), &err));
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
EXPECT_EQ("dependency cycle: circ -> in -> circ", err);
// Verify that "out.d" was loaded exactly once despite
@@ -890,6 +923,24 @@ TEST_F(GraphTest, DyndepFileCircular) {
EXPECT_EQ(1u, edge->order_only_deps_);
}
+TEST_F(GraphTest, Validation) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat in |@ validate\n"
+"build validate: cat in\n"));
+
+ fs_.Create("in", "");
+ string err;
+ std::vector<Node*> validation_nodes;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &validation_nodes, &err));
+ ASSERT_EQ("", err);
+
+ ASSERT_EQ(validation_nodes.size(), 1);
+ EXPECT_EQ(validation_nodes[0]->path(), "validate");
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+ EXPECT_TRUE(GetNode("validate")->dirty());
+}
+
// Check that phony's dependencies' mtimes are propagated.
TEST_F(GraphTest, PhonyDepsMtimes) {
string err;
@@ -904,7 +955,7 @@ TEST_F(GraphTest, PhonyDepsMtimes) {
Node* out1 = GetNode("out1");
Node* in1 = GetNode("in1");
- EXPECT_TRUE(scan_.RecomputeDirty(out1, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(out1, NULL, &err));
EXPECT_TRUE(!out1->dirty());
// Get the mtime of out1
@@ -921,7 +972,7 @@ TEST_F(GraphTest, PhonyDepsMtimes) {
ASSERT_TRUE(in1->Stat(&fs_, &err));
EXPECT_GT(in1->mtime(), in1Mtime1);
- EXPECT_TRUE(scan_.RecomputeDirty(out1, &err));
+ EXPECT_TRUE(scan_.RecomputeDirty(out1, NULL, &err));
EXPECT_GT(in1->mtime(), in1Mtime1);
EXPECT_EQ(out1->mtime(), out1Mtime1);
EXPECT_TRUE(out1->dirty());
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index 5d52943..081e364 100644
--- a/src/includes_normalize-win32.cc
+++ b/src/includes_normalize-win32.cc
@@ -48,7 +48,7 @@ bool IsPathSeparator(char c) {
}
// Return true if paths a and b are on the same windows drive.
-// Return false if this funcation cannot check
+// Return false if this function cannot check
// whether or not on the same windows drive.
bool SameDriveFast(StringPiece a, StringPiece b) {
if (a.size() < 3 || b.size() < 3) {
diff --git a/src/lexer.cc b/src/lexer.cc
index 6e4a470..e5729f0 100644
--- a/src/lexer.cc
+++ b/src/lexer.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 1.1.1 */
+/* Generated by re2c */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -85,6 +85,7 @@ const char* Lexer::TokenName(Token t) {
case NEWLINE: return "newline";
case PIPE2: return "'||'";
case PIPE: return "'|'";
+ case PIPEAT: return "'|@'";
case POOL: return "'pool'";
case RULE: return "'rule'";
case SUBNINJA: return "'subninja'";
@@ -291,7 +292,8 @@ yy25:
goto yy14;
yy26:
yych = *++p;
- if (yych == '|') goto yy42;
+ if (yych == '@') goto yy42;
+ if (yych == '|') goto yy44;
{ token = PIPE; break; }
yy28:
++p;
@@ -317,126 +319,129 @@ yy33:
{ continue; }
yy36:
yych = *++p;
- if (yych == 'i') goto yy44;
+ if (yych == 'i') goto yy46;
goto yy14;
yy37:
yych = *++p;
- if (yych == 'f') goto yy45;
+ if (yych == 'f') goto yy47;
goto yy14;
yy38:
yych = *++p;
- if (yych == 'c') goto yy46;
+ if (yych == 'c') goto yy48;
goto yy14;
yy39:
yych = *++p;
- if (yych == 'o') goto yy47;
+ if (yych == 'o') goto yy49;
goto yy14;
yy40:
yych = *++p;
- if (yych == 'l') goto yy48;
+ if (yych == 'l') goto yy50;
goto yy14;
yy41:
yych = *++p;
- if (yych == 'b') goto yy49;
+ if (yych == 'b') goto yy51;
goto yy14;
yy42:
++p;
- { token = PIPE2; break; }
+ { token = PIPEAT; break; }
yy44:
- yych = *++p;
- if (yych == 'l') goto yy50;
- goto yy14;
-yy45:
- yych = *++p;
- if (yych == 'a') goto yy51;
- goto yy14;
+ ++p;
+ { token = PIPE2; break; }
yy46:
yych = *++p;
if (yych == 'l') goto yy52;
goto yy14;
yy47:
yych = *++p;
- if (yych == 'l') goto yy53;
+ if (yych == 'a') goto yy53;
goto yy14;
yy48:
yych = *++p;
- if (yych == 'e') goto yy55;
+ if (yych == 'l') goto yy54;
goto yy14;
yy49:
yych = *++p;
- if (yych == 'n') goto yy57;
+ if (yych == 'l') goto yy55;
goto yy14;
yy50:
yych = *++p;
- if (yych == 'd') goto yy58;
+ if (yych == 'e') goto yy57;
goto yy14;
yy51:
yych = *++p;
- if (yych == 'u') goto yy60;
+ if (yych == 'n') goto yy59;
goto yy14;
yy52:
yych = *++p;
- if (yych == 'u') goto yy61;
+ if (yych == 'd') goto yy60;
goto yy14;
yy53:
yych = *++p;
+ if (yych == 'u') goto yy62;
+ goto yy14;
+yy54:
+ yych = *++p;
+ if (yych == 'u') goto yy63;
+ goto yy14;
+yy55:
+ yych = *++p;
if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = POOL; break; }
-yy55:
+yy57:
yych = *++p;
if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = RULE; break; }
-yy57:
+yy59:
yych = *++p;
- if (yych == 'i') goto yy62;
+ if (yych == 'i') goto yy64;
goto yy14;
-yy58:
+yy60:
yych = *++p;
if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = BUILD; break; }
-yy60:
- yych = *++p;
- if (yych == 'l') goto yy63;
- goto yy14;
-yy61:
- yych = *++p;
- if (yych == 'd') goto yy64;
- goto yy14;
yy62:
yych = *++p;
- if (yych == 'n') goto yy65;
+ if (yych == 'l') goto yy65;
goto yy14;
yy63:
yych = *++p;
- if (yych == 't') goto yy66;
+ if (yych == 'd') goto yy66;
goto yy14;
yy64:
yych = *++p;
- if (yych == 'e') goto yy68;
+ if (yych == 'n') goto yy67;
goto yy14;
yy65:
yych = *++p;
- if (yych == 'j') goto yy70;
+ if (yych == 't') goto yy68;
goto yy14;
yy66:
yych = *++p;
+ if (yych == 'e') goto yy70;
+ goto yy14;
+yy67:
+ yych = *++p;
+ if (yych == 'j') goto yy72;
+ goto yy14;
+yy68:
+ yych = *++p;
if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = DEFAULT; break; }
-yy68:
+yy70:
yych = *++p;
if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = INCLUDE; break; }
-yy70:
+yy72:
yych = *++p;
if (yych != 'a') goto yy14;
yych = *++p;
@@ -507,38 +512,38 @@ void Lexer::EatWhitespace() {
};
yych = *p;
if (yybm[0+yych] & 128) {
- goto yy79;
+ goto yy81;
}
- if (yych <= 0x00) goto yy75;
- if (yych == '$') goto yy82;
- goto yy77;
-yy75:
- ++p;
- { break; }
+ if (yych <= 0x00) goto yy77;
+ if (yych == '$') goto yy84;
+ goto yy79;
yy77:
++p;
-yy78:
{ break; }
yy79:
+ ++p;
+yy80:
+ { break; }
+yy81:
yych = *++p;
if (yybm[0+yych] & 128) {
- goto yy79;
+ goto yy81;
}
{ continue; }
-yy82:
+yy84:
yych = *(q = ++p);
- if (yych == '\n') goto yy83;
- if (yych == '\r') goto yy85;
- goto yy78;
-yy83:
+ if (yych == '\n') goto yy85;
+ if (yych == '\r') goto yy87;
+ goto yy80;
+yy85:
++p;
{ continue; }
-yy85:
+yy87:
yych = *++p;
- if (yych == '\n') goto yy87;
+ if (yych == '\n') goto yy89;
p = q;
- goto yy78;
-yy87:
+ goto yy80;
+yy89:
++p;
{ continue; }
}
@@ -590,17 +595,17 @@ bool Lexer::ReadIdent(string* out) {
};
yych = *p;
if (yybm[0+yych] & 128) {
- goto yy93;
+ goto yy95;
}
++p;
{
last_token_ = start;
return false;
}
-yy93:
+yy95:
yych = *++p;
if (yybm[0+yych] & 128) {
- goto yy93;
+ goto yy95;
}
{
out->assign(start, p - start);
@@ -660,33 +665,33 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) {
};
yych = *p;
if (yybm[0+yych] & 16) {
- goto yy100;
+ goto yy102;
}
if (yych <= '\r') {
- if (yych <= 0x00) goto yy98;
- if (yych <= '\n') goto yy103;
- goto yy105;
+ if (yych <= 0x00) goto yy100;
+ if (yych <= '\n') goto yy105;
+ goto yy107;
} else {
- if (yych <= ' ') goto yy103;
- if (yych <= '$') goto yy107;
- goto yy103;
+ if (yych <= ' ') goto yy105;
+ if (yych <= '$') goto yy109;
+ goto yy105;
}
-yy98:
+yy100:
++p;
{
last_token_ = start;
return Error("unexpected EOF", err);
}
-yy100:
+yy102:
yych = *++p;
if (yybm[0+yych] & 16) {
- goto yy100;
+ goto yy102;
}
{
eval->AddText(StringPiece(start, p - start));
continue;
}
-yy103:
+yy105:
++p;
{
if (path) {
@@ -699,112 +704,112 @@ yy103:
continue;
}
}
-yy105:
+yy107:
yych = *++p;
- if (yych == '\n') goto yy108;
+ if (yych == '\n') goto yy110;
{
last_token_ = start;
return Error(DescribeLastError(), err);
}
-yy107:
+yy109:
yych = *++p;
if (yybm[0+yych] & 64) {
- goto yy120;
+ goto yy122;
}
if (yych <= ' ') {
if (yych <= '\f') {
- if (yych == '\n') goto yy112;
- goto yy110;
+ if (yych == '\n') goto yy114;
+ goto yy112;
} else {
- if (yych <= '\r') goto yy115;
- if (yych <= 0x1F) goto yy110;
- goto yy116;
+ if (yych <= '\r') goto yy117;
+ if (yych <= 0x1F) goto yy112;
+ goto yy118;
}
} else {
if (yych <= '/') {
- if (yych == '$') goto yy118;
- goto yy110;
+ if (yych == '$') goto yy120;
+ goto yy112;
} else {
- if (yych <= ':') goto yy123;
- if (yych <= '`') goto yy110;
- if (yych <= '{') goto yy125;
- goto yy110;
+ if (yych <= ':') goto yy125;
+ if (yych <= '`') goto yy112;
+ if (yych <= '{') goto yy127;
+ goto yy112;
}
}
-yy108:
+yy110:
++p;
{
if (path)
p = start;
break;
}
-yy110:
+yy112:
++p;
-yy111:
+yy113:
{
last_token_ = start;
return Error("bad $-escape (literal $ must be written as $$)", err);
}
-yy112:
+yy114:
yych = *++p;
if (yybm[0+yych] & 32) {
- goto yy112;
+ goto yy114;
}
{
continue;
}
-yy115:
+yy117:
yych = *++p;
- if (yych == '\n') goto yy126;
- goto yy111;
-yy116:
+ if (yych == '\n') goto yy128;
+ goto yy113;
+yy118:
++p;
{
eval->AddText(StringPiece(" ", 1));
continue;
}
-yy118:
+yy120:
++p;
{
eval->AddText(StringPiece("$", 1));
continue;
}
-yy120:
+yy122:
yych = *++p;
if (yybm[0+yych] & 64) {
- goto yy120;
+ goto yy122;
}
{
eval->AddSpecial(StringPiece(start + 1, p - start - 1));
continue;
}
-yy123:
+yy125:
++p;
{
eval->AddText(StringPiece(":", 1));
continue;
}
-yy125:
+yy127:
yych = *(q = ++p);
if (yybm[0+yych] & 128) {
- goto yy129;
+ goto yy131;
}
- goto yy111;
-yy126:
+ goto yy113;
+yy128:
yych = *++p;
- if (yych == ' ') goto yy126;
+ if (yych == ' ') goto yy128;
{
continue;
}
-yy129:
+yy131:
yych = *++p;
if (yybm[0+yych] & 128) {
- goto yy129;
+ goto yy131;
}
- if (yych == '}') goto yy132;
+ if (yych == '}') goto yy134;
p = q;
- goto yy111;
-yy132:
+ goto yy113;
+yy134:
++p;
{
eval->AddSpecial(StringPiece(start + 2, p - start - 3));
diff --git a/src/lexer.h b/src/lexer.h
index 788d948..683fd6c 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -41,6 +41,7 @@ struct Lexer {
NEWLINE,
PIPE,
PIPE2,
+ PIPEAT,
POOL,
RULE,
SUBNINJA,
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
index 88007e7..6f1d8e7 100644
--- a/src/lexer.in.cc
+++ b/src/lexer.in.cc
@@ -84,6 +84,7 @@ const char* Lexer::TokenName(Token t) {
case NEWLINE: return "newline";
case PIPE2: return "'||'";
case PIPE: return "'|'";
+ case PIPEAT: return "'|@'";
case POOL: return "'pool'";
case RULE: return "'rule'";
case SUBNINJA: return "'subninja'";
@@ -142,6 +143,7 @@ Lexer::Token Lexer::ReadToken() {
"default" { token = DEFAULT; break; }
"=" { token = EQUALS; break; }
":" { token = COLON; break; }
+ "|@" { token = PIPEAT; break; }
"||" { token = PIPE2; break; }
"|" { token = PIPE; break; }
"include" { token = INCLUDE; break; }
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index 521edb4..8db6eb3 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -207,7 +207,7 @@ bool ManifestParser::ParseDefault(string* err) {
}
bool ManifestParser::ParseEdge(string* err) {
- vector<EvalString> ins, outs;
+ vector<EvalString> ins, outs, validations;
{
EvalString out;
@@ -288,6 +288,18 @@ bool ManifestParser::ParseEdge(string* err) {
}
}
+ // Add all validations, counting how many as we go.
+ if (lexer_.PeekToken(Lexer::PIPEAT)) {
+ for (;;) {
+ EvalString validation;
+ if (!lexer_.ReadPath(&validation, err))
+ return false;
+ if (validation.empty())
+ break;
+ validations.push_back(validation);
+ }
+ }
+
if (!ExpectToken(Lexer::NEWLINE, err))
return false;
@@ -338,6 +350,7 @@ bool ManifestParser::ParseEdge(string* err) {
}
}
}
+
if (edge->outputs_.empty()) {
// All outputs of the edge are already created by other edges. Don't add
// this edge. Do this check before input nodes are connected to the edge.
@@ -359,6 +372,17 @@ bool ManifestParser::ParseEdge(string* err) {
edge->implicit_deps_ = implicit;
edge->order_only_deps_ = order_only;
+ edge->validations_.reserve(validations.size());
+ for (std::vector<EvalString>::iterator v = validations.begin();
+ v != validations.end(); ++v) {
+ string path = v->Evaluate(env);
+ if (path.empty())
+ return lexer_.Error("empty path", err);
+ uint64_t slash_bits;
+ CanonicalizePath(&path, &slash_bits);
+ state_->AddValidation(edge, path, slash_bits);
+ }
+
if (options_.phony_cycle_action_ == kPhonyCycleActionWarn &&
edge->maybe_phonycycle_diagnostic()) {
// CMake 2.8.12.x and 3.0.x incorrectly write phony build statements
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index 5b0eddf..66b72e2 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -965,6 +965,16 @@ TEST_F(ParserTest, OrderOnly) {
ASSERT_TRUE(edge->is_order_only(1));
}
+TEST_F(ParserTest, Validations) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n command = cat $in > $out\n"
+"build foo: cat bar |@ baz\n"));
+
+ Edge* edge = state.LookupNode("foo")->in_edge();
+ ASSERT_EQ(edge->validations_.size(), 1);
+ EXPECT_EQ(edge->validations_[0]->path(), "baz");
+}
+
TEST_F(ParserTest, ImplicitOutput) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"rule cat\n"
diff --git a/src/missing_deps.cc b/src/missing_deps.cc
index 78feb49..de76620 100644
--- a/src/missing_deps.cc
+++ b/src/missing_deps.cc
@@ -116,7 +116,7 @@ void MissingDependencyScanner::ProcessNodeDeps(Node* node, Node** dep_nodes,
// rebuild this target when the build is reconfigured", but build.ninja is
// often generated by a configuration tool like cmake or gn. The rest of
// the build "implicitly" depends on the entire build being reconfigured,
- // so a missing dep path to build.ninja is not an actual missing dependecy
+ // so a missing dep path to build.ninja is not an actual missing dependency
// problem.
if (deplog_node->path() == "build.ninja")
return;
diff --git a/src/missing_deps_test.cc b/src/missing_deps_test.cc
index 7b62e6c..db66885 100644
--- a/src/missing_deps_test.cc
+++ b/src/missing_deps_test.cc
@@ -152,7 +152,7 @@ TEST_F(MissingDependencyScannerTest, CycleInGraph) {
CreateInitialState();
CreateGraphDependencyBetween("compiled_object", "generated_header");
CreateGraphDependencyBetween("generated_header", "compiled_object");
- // The missing-deps tool doesn't deal with cycles in the graph, beacuse
+ // The missing-deps tool doesn't deal with cycles in the graph, because
// there will be an error loading the graph before we get to the tool.
// This test is to illustrate that.
std::string err;
diff --git a/src/ninja.cc b/src/ninja.cc
index 2d43b1d..2b71eb1 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -17,6 +17,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+
+#include <algorithm>
#include <cstdlib>
#ifdef _WIN32
@@ -52,7 +54,7 @@
using namespace std;
-#ifdef _MSC_VER
+#ifdef _WIN32
// Defined in msvc_helper_main-win32.cc.
int MSVCHelperMain(int argc, char** argv);
@@ -127,6 +129,7 @@ struct NinjaMain : public BuildLogUser {
int ToolMSVC(const Options* options, int argc, char* argv[]);
int ToolTargets(const Options* options, int argc, char* argv[]);
int ToolCommands(const Options* options, int argc, char* argv[]);
+ int ToolInputs(const Options* options, int argc, char* argv[]);
int ToolClean(const Options* options, int argc, char* argv[]);
int ToolCleanDead(const Options* options, int argc, char* argv[]);
int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
@@ -404,6 +407,13 @@ int NinjaMain::ToolQuery(const Options* options, int argc, char* argv[]) {
label = "|| ";
printf(" %s%s\n", label, edge->inputs_[in]->path().c_str());
}
+ if (!edge->validations_.empty()) {
+ printf(" validations:\n");
+ for (std::vector<Node*>::iterator validation = edge->validations_.begin();
+ validation != edge->validations_.end(); ++validation) {
+ printf(" %s\n", (*validation)->path().c_str());
+ }
+ }
}
printf(" outputs:\n");
for (vector<Edge*>::const_iterator edge = node->out_edges().begin();
@@ -413,6 +423,17 @@ int NinjaMain::ToolQuery(const Options* options, int argc, char* argv[]) {
printf(" %s\n", (*out)->path().c_str());
}
}
+ const std::vector<Edge*> validation_edges = node->validation_out_edges();
+ if (!validation_edges.empty()) {
+ printf(" validation for:\n");
+ for (std::vector<Edge*>::const_iterator edge = validation_edges.begin();
+ edge != validation_edges.end(); ++edge) {
+ for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
+ out != (*edge)->outputs_.end(); ++out) {
+ printf(" %s\n", (*out)->path().c_str());
+ }
+ }
+ }
}
return 0;
}
@@ -430,7 +451,7 @@ int NinjaMain::ToolBrowse(const Options*, int, char**) {
}
#endif
-#if defined(_MSC_VER)
+#if defined(_WIN32)
int NinjaMain::ToolMSVC(const Options* options, int argc, char* argv[]) {
// Reset getopt: push one argument onto the front of argv, reset optind.
argc++;
@@ -684,7 +705,7 @@ void PrintCommands(Edge* edge, EdgeSet* seen, PrintCommandMode mode) {
}
int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
- // The clean tool uses getopt, and expects argv[0] to contain the name of
+ // The commands tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "commands".
++argc;
--argv;
@@ -725,6 +746,72 @@ int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
return 0;
}
+void CollectInputs(Edge* edge, std::set<Edge*>* seen,
+ std::vector<std::string>* result) {
+ if (!edge)
+ return;
+ if (!seen->insert(edge).second)
+ return;
+
+ for (vector<Node*>::iterator in = edge->inputs_.begin();
+ in != edge->inputs_.end(); ++in)
+ CollectInputs((*in)->in_edge(), seen, result);
+
+ if (!edge->is_phony()) {
+ edge->CollectInputs(true, result);
+ }
+}
+
+int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
+ // The inputs tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "inputs".
+ argc++;
+ argv--;
+ optind = 1;
+ int opt;
+ const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 } };
+ while ((opt = getopt_long(argc, argv, "h", kLongOptions, NULL)) != -1) {
+ switch (opt) {
+ case 'h':
+ default:
+ // clang-format off
+ printf(
+"Usage '-t inputs [options] [targets]\n"
+"\n"
+"List all inputs used for a set of targets. Note that this includes\n"
+"explicit, implicit and order-only inputs, but not validation ones.\n\n"
+"Options:\n"
+" -h, --help Print this message.\n");
+ // clang-format on
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ vector<Node*> nodes;
+ string err;
+ if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
+ Error("%s", err.c_str());
+ return 1;
+ }
+
+ std::set<Edge*> seen;
+ std::vector<std::string> result;
+ for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
+ CollectInputs((*in)->in_edge(), &seen, &result);
+
+ // Make output deterministic by sorting then removing duplicates.
+ std::sort(result.begin(), result.end());
+ result.erase(std::unique(result.begin(), result.end()), result.end());
+
+ for (size_t n = 0; n < result.size(); ++n)
+ puts(result[n].c_str());
+
+ return 0;
+}
+
int NinjaMain::ToolClean(const Options* options, int argc, char* argv[]) {
// The clean tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "clean".
@@ -995,14 +1082,16 @@ const Tool* ChooseTool(const string& tool_name) {
static const Tool kTools[] = {
{ "browse", "browse dependency graph in a web browser",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
-#if defined(_MSC_VER)
- { "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
+#ifdef _WIN32
+ { "msvc", "build helper for MSVC cl.exe (DEPRECATED)",
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
#endif
{ "clean", "clean built files",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolClean },
{ "commands", "list all commands required to rebuild given targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
+ { "inputs", "list all inputs required to rebuild given targets",
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolInputs},
{ "deps", "show dependencies stored in the deps log",
Tool::RUN_AFTER_LOGS, &NinjaMain::ToolDeps },
{ "missingdeps", "check deps log dependencies on generated files",
@@ -1455,17 +1544,6 @@ NORETURN void real_main(int argc, char** argv) {
exit((ninja.*options.tool->func)(&options, argc, argv));
}
-#ifdef WIN32
- // It'd be nice to use line buffering but MSDN says: "For some systems,
- // [_IOLBF] provides line buffering. However, for Win32, the behavior is the
- // same as _IOFBF - Full Buffering."
- // Buffering used to be disabled in the LinePrinter constructor but that
- // now disables it too early and breaks -t deps performance (see issue #2018)
- // so we disable it here instead, but only when not running a tool.
- if (!options.tool)
- setvbuf(stdout, NULL, _IONBF, 0);
-#endif
-
// Limit number of rebuilds, to prevent infinite loops.
const int kCycleLimit = 100;
for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
@@ -1516,7 +1594,7 @@ NORETURN void real_main(int argc, char** argv) {
exit(result);
}
- status->Error("manifest '%s' still dirty after %d tries",
+ status->Error("manifest '%s' still dirty after %d tries, perhaps system time is not set",
options.input_file, kCycleLimit);
exit(1);
}
diff --git a/src/ninja_test.cc b/src/ninja_test.cc
index b40e176..6720dec 100644
--- a/src/ninja_test.cc
+++ b/src/ninja_test.cc
@@ -67,7 +67,7 @@ void Usage() {
"usage: ninja_tests [options]\n"
"\n"
"options:\n"
-" --gtest_filter=POSTIVE_PATTERN[-NEGATIVE_PATTERN]\n"
+" --gtest_filter=POSITIVE_PATTERN[-NEGATIVE_PATTERN]\n"
" Run tests whose names match the positive but not the negative pattern.\n"
" '*' matches any substring. (gtest's ':', '?' are not implemented).\n");
}
diff --git a/src/state.cc b/src/state.cc
index fc37c8a..556b0d8 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -141,6 +141,12 @@ bool State::AddOut(Edge* edge, StringPiece path, uint64_t slash_bits) {
return true;
}
+void State::AddValidation(Edge* edge, StringPiece path, uint64_t slash_bits) {
+ Node* node = GetNode(path, slash_bits);
+ edge->validations_.push_back(node);
+ node->AddValidationOutEdge(edge);
+}
+
bool State::AddDefault(StringPiece path, string* err) {
Node* node = LookupNode(path);
if (!node) {
diff --git a/src/state.h b/src/state.h
index 72c5b33..878ac6d 100644
--- a/src/state.h
+++ b/src/state.h
@@ -107,6 +107,7 @@ struct State {
void AddIn(Edge* edge, StringPiece path, uint64_t slash_bits);
bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits);
+ void AddValidation(Edge* edge, StringPiece path, uint64_t slash_bits);
bool AddDefault(StringPiece path, std::string* error);
/// Reset state. Keeps all nodes and edges, but restores them to the
diff --git a/src/util.cc b/src/util.cc
index 287debc..483f4a6 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -353,7 +353,8 @@ int ReadFile(const string& path, string* contents, string* err) {
if (!::ReadFile(f, buf, sizeof(buf), &len, NULL)) {
err->assign(GetLastErrorString());
contents->clear();
- return -1;
+ ::CloseHandle(f);
+ return -EIO;
}
if (len == 0)
break;