diff options
37 files changed, 486 insertions, 228 deletions
diff --git a/.travis.yml b/.travis.yml index 544db6f..216b8b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: cpp compiler: - gcc @@ -20,7 +20,7 @@ You should end up with a `ninja` binary (or `ninja.exe`) in the source root. On Windows, you'll need to install Python to run `configure.py`, and run everything under a Visual Studio Tools Command Prompt (or after running `vcvarsall` in a normal command prompt). See below if you -want to use mingw or some other compiler instead using Visual Studio. +want to use mingw or some other compiler instead of Visual Studio. ### Adjusting build flags @@ -1,7 +1,7 @@ Ninja is a small build system with a focus on speed. -http://ninja-build.org/ +https://ninja-build.org/ -See the manual -- http://ninja-build.org/manual.html or +See the manual -- https://ninja-build.org/manual.html or doc/manual.asciidoc included in the distribution -- for background and more details. diff --git a/configure.py b/configure.py index b9c5f57..f0e452f 100755 --- a/configure.py +++ b/configure.py @@ -83,7 +83,7 @@ class Platform(object): stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = popen.communicate() - return b'/FS ' in out + return b'/FS' in out def is_windows(self): return self.is_mingw() or self.is_msvc() @@ -267,11 +267,11 @@ if platform.is_msvc(): objext = '.obj' def src(filename): - return os.path.join('$sourcedir', 'src', filename) + return os.path.join('$root', 'src', filename) def built(filename): return os.path.join('$builddir', filename) def doc(filename): - return os.path.join('$sourcedir', 'doc', filename) + return os.path.join('$root', 'doc', filename) def cc(name, **kwargs): return n.build(built(name + objext), 'cxx', src(name + '.c'), **kwargs) def cxx(name, **kwargs): @@ -283,7 +283,12 @@ def binary(name): return exe return name -n.variable('sourcedir', sourcedir) +root = sourcedir +if root == os.getcwd(): + # In the common case where we're building directly in the source + # tree, simplify all the paths to just be cwd-relative. + root = '.' +n.variable('root', root) n.variable('builddir', 'build') n.variable('cxx', CXX) if platform.is_msvc(): @@ -299,6 +304,8 @@ if platform.is_msvc(): '/WX', # Warnings as errors. '/wd4530', '/wd4100', '/wd4706', '/wd4512', '/wd4800', '/wd4702', '/wd4819', + # Disable warnings about constant conditional expressions. + '/wd4127', # Disable warnings about passing "this" during initialization. '/wd4355', # Disable warnings about ignored typedef in DbgHelp.h @@ -319,11 +326,11 @@ if platform.is_msvc(): else: cflags = ['-g', '-Wall', '-Wextra', '-Wno-deprecated', + '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-fno-rtti', '-fno-exceptions', '-fvisibility=hidden', '-pipe', - '-Wno-missing-field-initializers', '-DNINJA_PYTHON="%s"' % options.with_python] if options.debug: cflags += ['-D_GLIBCXX_DEBUG', '-D_GLIBCXX_DEBUG_PEDANTIC'] @@ -590,13 +597,19 @@ n.rule('asciidoc', n.rule('xsltproc', command='xsltproc --nonet doc/docbook.xsl $in > $out', description='XSLTPROC $out') -xml = n.build(built('manual.xml'), 'asciidoc', doc('manual.asciidoc')) -manual = n.build(doc('manual.html'), 'xsltproc', xml, - implicit=doc('style.css')) +docbookxml = n.build(built('manual.xml'), 'asciidoc', doc('manual.asciidoc')) +manual = n.build(doc('manual.html'), 'xsltproc', docbookxml, + implicit=[doc('style.css'), doc('docbook.xsl')]) n.build('manual', 'phony', order_only=manual) n.newline() +n.rule('dblatex', + command='dblatex -q -o $out -p doc/dblatex.xsl $in', + description='DBLATEX $out') +n.build(doc('manual.pdf'), 'dblatex', docbookxml, + implicit=[doc('dblatex.xsl')]) + n.comment('Generate Doxygen.') n.rule('doxygen', command='doxygen $in', @@ -616,12 +629,12 @@ n.newline() if not host.is_mingw(): n.comment('Regenerate build files if build script changes.') n.rule('configure', - command='${configure_env}%s $sourcedir/configure.py $configure_args' % + command='${configure_env}%s $root/configure.py $configure_args' % options.with_python, generator=True) n.build('build.ninja', 'configure', - implicit=['$sourcedir/configure.py', - os.path.normpath('$sourcedir/misc/ninja_syntax.py')]) + implicit=['$root/configure.py', + os.path.normpath('$root/misc/ninja_syntax.py')]) n.newline() n.default(ninja) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..6afe5d4 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,11 @@ +This directory contains the Ninja manual and support files used in +building it. Here's a brief overview of how it works. + +The source text, `manual.asciidoc`, is written in the AsciiDoc format. +AsciiDoc can generate HTML but it doesn't look great; instead, we use +AsciiDoc to generate the Docbook XML format and then provide our own +Docbook XSL tweaks to produce HTML from that. + +In theory using AsciiDoc and DocBook allows us to produce nice PDF +documentation etc. In reality it's not clear anyone wants that, but the +build rules are in place to generate it if you install dblatex. diff --git a/doc/dblatex.xsl b/doc/dblatex.xsl new file mode 100644 index 0000000..c0da212 --- /dev/null +++ b/doc/dblatex.xsl @@ -0,0 +1,7 @@ +<!-- This custom XSL tweaks the dblatex XML settings. --> +<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'> + <!-- These parameters disable the list of collaborators and revisions. + Together remove a useless page from the front matter. --> + <xsl:param name='doc.collab.show'>0</xsl:param> + <xsl:param name='latex.output.revhistory'>0</xsl:param> +</xsl:stylesheet> diff --git a/doc/docbook.xsl b/doc/docbook.xsl index 8afdc8c..19cc126 100644 --- a/doc/docbook.xsl +++ b/doc/docbook.xsl @@ -1,15 +1,29 @@ -<!-- This soup of XML is the minimum customization necessary to make the - autogenerated manual look ok. --> +<!-- This custom XSL tweaks the DocBook XML -> HTML settings to produce + an OK-looking manual. --> <!DOCTYPE xsl:stylesheet [ <!ENTITY css SYSTEM "style.css"> ]> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/> + + <!-- Embed our stylesheet as the user-provided <head> content. --> <xsl:template name="user.head.content"><style>&css;</style></xsl:template> + + <!-- Remove the body.attributes block, which specifies a bunch of + useless bgcolor etc. attrs on the <body> tag. --> <xsl:template name="body.attributes"></xsl:template> - <xsl:param name="generate.toc" select="'book toc'"/> - <xsl:param name="chapter.autolabel" select="0" /> + + <!-- Specify that in "book" form (which we're using), we only want a + single table of contents at the beginning of the document. --> + <xsl:param name="generate.toc">book toc</xsl:param> + + <!-- Don't put the "Chapter 1." prefix on the "chapters". --> + <xsl:param name="chapter.autolabel">0</xsl:param> + + <!-- Use <ul> for the table of contents. By default DocBook uses a + <dl>, which makes no semantic sense. I imagine they just did + it because it looks nice? --> <xsl:param name="toc.list.type">ul</xsl:param> <xsl:output method="html" encoding="utf-8" indent="no" diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 3c193f1..0d842f9 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -1,5 +1,5 @@ -Ninja -===== +The Ninja build system +====================== Introduction @@ -158,17 +158,13 @@ http://code.google.com/p/gyp/[gyp]:: The meta-build system used to generate build files for Google Chrome and related projects (v8, node.js). gyp can generate Ninja files for all platforms supported by Chrome. See the -http://code.google.com/p/chromium/wiki/NinjaBuild[Chromium Ninja -documentation for more details]. +https://chromium.googlesource.com/chromium/src/+/master/docs/ninja_build.md[Chromium Ninja documentation for more details]. -http://www.cmake.org/[CMake]:: A widely used meta-build system that -can generate Ninja files on Linux as of CMake version 2.8.8. (There -is some Mac and Windows support -- http://www.reactos.org[ReactOS] -uses Ninja on Windows for their buildbots, but those platforms are not -yet officially supported by CMake as the full test suite doesn't -pass.) +https://cmake.org/[CMake]:: A widely used meta-build system that +can generate Ninja files on Linux as of CMake version 2.8.8. Newer versions +of CMake support generating Ninja files on Windows and Mac OS X too. -others:: Ninja ought to fit perfectly into other meta-build software +https://github.com/ninja-build/ninja/wiki/List-of-generators-producing-ninja-build-files[others]:: Ninja ought to fit perfectly into other meta-build software like http://industriousone.com/premake[premake]. If you do this work, please let us know! @@ -689,6 +685,10 @@ 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>>.) ++ +Implicit outputs _(available since Ninja 1.7)_ may be added before +the `:` with +| _output1_ _output2_+ and do not appear in `$out`. +(See <<ref_outputs,the reference on output types>>.) 3. Variable declarations, which look like +_variable_ = _value_+. @@ -717,7 +717,6 @@ spaces within a token must be escaped. There is only one escape character, `$`, and it has the following behaviors: -[horizontal] `$` followed by a newline:: escape the newline (continue the current line across a line break). @@ -782,11 +781,9 @@ A `rule` block contains a list of `key = value` declarations that affect the processing of the rule. Here is a full list of special keys. -`command` (_required_):: the command line to run. This string (after - $variables are expanded) is passed directly to `sh -c` without - interpretation by Ninja. Each `rule` may have only one `command` - declaration. To specify multiple commands use `&&` (or similar) to - concatenate operations. +`command` (_required_):: the command line to run. Each `rule` may + have only one `command` declaration. See <<ref_rule_command,the next + section>> for more details on quoting and executing multiple commands. `depfile`:: path to an optional `Makefile` that contains extra _implicit dependencies_ (see <<ref_dependencies,the reference on @@ -854,6 +851,48 @@ rule link build myapp.exe: link a.obj b.obj [possibly many other .obj files] ---- +[[ref_rule_command]] +Interpretation of the `command` variable +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Fundamentally, command lines behave differently on Unixes and Windows. + +On Unixes, commands are arrays of arguments. The Ninja `command` +variable is passed directly to `sh -c`, which is then responsible for +interpreting that string into an argv array. Therefore the quoting +rules are those of the shell, and you can use all the normal shell +operators, like `&&` to chain multiple commands, or `VAR=value cmd` to +set environment variables. + +On Windows, commands are strings, so Ninja passes the `command` string +directly to `CreateProcess`. (In the common case of simply executing +a compiler this means there is less overhead.) Consequently the +quoting rules are deterimined by the called program, which on Windows +are usually provided by the C library. If you need shell +interpretation of the command (such as the use of `&&` to chain +multiple commands), make the command execute the Windows shell by +prefixing the command with `cmd /c`. + +[[ref_outputs]] +Build outputs +~~~~~~~~~~~~~ + +There are two types of build outputs which are subtly different. + +1. _Explicit outputs_, as listed in a build line. These are + available as the `$out` variable in the rule. ++ +This is the standard form of output to be used for e.g. the +object file of a compile command. + +2. _Implicit outputs_, as listed in a build line with the syntax +| + _out1_ _out2_+ + before the `:` of a build line _(available since + Ninja 1.7)_. The semantics are identical to explicit outputs, + the only difference is that implicit outputs don't show up in the + `$out` variable. ++ +This is for expressing outputs that don't show up on the +command line of the command. + [[ref_dependencies]] Build dependencies ~~~~~~~~~~~~~~~~~~ @@ -865,7 +904,7 @@ There are three types of build dependencies which are subtly different. cause the output to be rebuilt; if these file are missing and Ninja doesn't know how to build them, the build is aborted. + -This is the standard form of dependency to be used for e.g. the +This is the standard form of dependency to be used e.g. for the source file of a compile command. 2. _Implicit dependencies_, either as picked up from @@ -954,4 +993,3 @@ Variable declarations indented in a `build` block are scoped to the 5. Variables from the file that included that file using the `subninja` keyword. - diff --git a/doc/style.css b/doc/style.css index 5d14a1c..9976c03 100644 --- a/doc/style.css +++ b/doc/style.css @@ -24,12 +24,6 @@ div.chapter { margin-top: 4em; border-top: solid 2px black; } -.section .title { - font-size: 1.3em; -} -.section .section .title { - font-size: 1.2em; -} p { margin-top: 0; } diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index 8673518..73d2209 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -21,8 +21,9 @@ class Writer(object): def newline(self): self.output.write('\n') - def comment(self, text): - for line in textwrap.wrap(text, self.width - 2): + def comment(self, text, has_path=False): + for line in textwrap.wrap(text, self.width - 2, break_long_words=False, + break_on_hyphens=False): self.output.write('# ' + line + '\n') def variable(self, key, value, indent=0): diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py index 36b2e7b..c9755b8 100755 --- a/misc/ninja_syntax_test.py +++ b/misc/ninja_syntax_test.py @@ -45,6 +45,12 @@ class TestLineWordWrap(unittest.TestCase): INDENT + 'y']) + '\n', self.out.getvalue()) + def test_comment_wrap(self): + # Filenames shoud not be wrapped + self.n.comment('Hello /usr/local/build-tools/bin') + self.assertEqual('# Hello\n# /usr/local/build-tools/bin\n', + self.out.getvalue()) + def test_short_words_indented(self): # Test that indent is taking into acount when breaking subsequent lines. # The second line should not be ' to tree', as that's longer than the diff --git a/misc/zsh-completion b/misc/zsh-completion index ad7b87f..446e269 100644 --- a/misc/zsh-completion +++ b/misc/zsh-completion @@ -22,8 +22,8 @@ __get_targets() { then eval dir="${opt_args[-C]}" fi - targets_command="ninja -C \"${dir}\" -t targets" - eval ${targets_command} 2>/dev/null | sed "s/^\(.*\): .*$/\1/" + targets_command="ninja -C \"${dir}\" -t targets all" + eval ${targets_command} 2>/dev/null | cut -d: -f1 } __get_tools() { @@ -65,4 +65,3 @@ _arguments \ '-d+[Enable debugging (use -d list to list modes)]:modes:__modes' \ '-t+[Run a subtool (use -t list to list subtools)]:tools:__tools' \ '*::targets:__targets' - diff --git a/src/build.cc b/src/build.cc index 0e9a399..e33a007 100644 --- a/src/build.cc +++ b/src/build.cc @@ -126,8 +126,15 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, PrintStatus(edge); // Print the command that is spewing before printing its output. - if (!success) - printer_.PrintOnNewLine("FAILED: " + edge->EvaluateCommand() + "\n"); + if (!success) { + string outputs; + for (vector<Node*>::const_iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) + outputs += (*o)->path() + " "; + + printer_.PrintOnNewLine("FAILED: " + outputs + "\n"); + printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n"); + } if (!output.empty()) { // ninja sets stdout and stderr of subprocesses to a pipe, to be able to @@ -871,9 +878,17 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, return false; } - string content = disk_interface_->ReadFile(depfile, err); - if (!err->empty()) + // Read depfile content. Treat a missing depfile as empty. + string content; + switch (disk_interface_->ReadFile(depfile, &content, err)) { + case DiskInterface::Okay: + break; + case DiskInterface::NotFound: + err->clear(); + break; + case DiskInterface::OtherError: return false; + } if (content.empty()) return true; @@ -892,9 +907,11 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, deps_nodes->push_back(state_->GetNode(*i, slash_bits)); } - if (disk_interface_->RemoveFile(depfile) < 0) { - *err = string("deleting depfile: ") + strerror(errno) + string("\n"); - return false; + if (!g_keep_depfile) { + if (disk_interface_->RemoveFile(depfile) < 0) { + *err = string("deleting depfile: ") + strerror(errno) + string("\n"); + return false; + } } } else { Fatal("unknown deps type '%s'", deps_type.c_str()); diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc index 810c065..185c512 100644 --- a/src/build_log_perftest.cc +++ b/src/build_log_perftest.cc @@ -71,7 +71,7 @@ bool WriteTestData(string* err) { long_rule_command += "$in -o $out\n"; State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); if (!parser.ParseTest("rule cxx\n command = " + long_rule_command, err)) return false; diff --git a/src/build_test.cc b/src/build_test.cc index 20fb664..7c6060d 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -717,6 +717,22 @@ TEST_F(BuildTest, TwoOutputs) { EXPECT_EQ("touch out1 out2", command_runner_.commands_ran_[0]); } +TEST_F(BuildTest, ImplicitOutput) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule touch\n" +" command = touch $out $out.imp\n" +"build out | out.imp: touch in.txt\n")); + fs_.Create("in.txt", ""); + + string err; + EXPECT_TRUE(builder_.AddTarget("out.imp", &err)); + ASSERT_EQ("", err); + EXPECT_TRUE(builder_.Build(&err)); + EXPECT_EQ("", err); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[0]); +} + // Test case from // https://github.com/ninja-build/ninja/issues/148 TEST_F(BuildTest, MultiOutIn) { diff --git a/src/debug_flags.cc b/src/debug_flags.cc index 8065001..44b14c4 100644 --- a/src/debug_flags.cc +++ b/src/debug_flags.cc @@ -14,6 +14,8 @@ bool g_explaining = false; +bool g_keep_depfile = false; + bool g_keep_rsp = false; bool g_experimental_statcache = true; diff --git a/src/debug_flags.h b/src/debug_flags.h index 7965585..e08a43b 100644 --- a/src/debug_flags.h +++ b/src/debug_flags.h @@ -24,6 +24,8 @@ extern bool g_explaining; +extern bool g_keep_depfile; + extern bool g_keep_rsp; extern bool g_experimental_statcache; diff --git a/src/disk_interface.cc b/src/disk_interface.cc index 70282c0..451a9b4 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -229,14 +229,14 @@ bool RealDiskInterface::MakeDir(const string& path) { return true; } -string RealDiskInterface::ReadFile(const string& path, string* err) { - string contents; - int ret = ::ReadFile(path, &contents, err); - if (ret == -ENOENT) { - // Swallow ENOENT. - err->clear(); +FileReader::Status RealDiskInterface::ReadFile(const string& path, + string* contents, + string* err) { + switch (::ReadFile(path, contents, err)) { + case 0: return Okay; + case -ENOENT: return NotFound; + default: return OtherError; } - return contents; } int RealDiskInterface::RemoveFile(const string& path) { diff --git a/src/disk_interface.h b/src/disk_interface.h index b61d192..145e089 100644 --- a/src/disk_interface.h +++ b/src/disk_interface.h @@ -21,13 +21,29 @@ using namespace std; #include "timestamp.h" +/// Interface for reading files from disk. See DiskInterface for details. +/// This base offers the minimum interface needed just to read files. +struct FileReader { + virtual ~FileReader() {} + + /// Result of ReadFile. + enum Status { + Okay, + NotFound, + OtherError + }; + + /// Read and store in given string. On success, return Okay. + /// On error, return another Status and fill |err|. + virtual Status ReadFile(const string& path, string* contents, + string* err) = 0; +}; + /// Interface for accessing the disk. /// /// Abstract so it can be mocked out for tests. The real implementation /// is RealDiskInterface. -struct DiskInterface { - virtual ~DiskInterface() {} - +struct DiskInterface: public FileReader { /// stat() a file, returning the mtime, or 0 if missing and -1 on /// other errors. virtual TimeStamp Stat(const string& path, string* err) const = 0; @@ -39,9 +55,6 @@ struct DiskInterface { /// Returns true on success, false on failure virtual bool WriteFile(const string& path, const string& contents) = 0; - /// Read a file to a string. Fill in |err| on error. - virtual string ReadFile(const string& path, string* err) = 0; - /// Remove the file named @a path. It behaves like 'rm -f path' so no errors /// are reported if it does not exists. /// @returns 0 if the file has been removed, @@ -65,7 +78,7 @@ struct RealDiskInterface : public DiskInterface { virtual TimeStamp Stat(const string& path, string* err) const; virtual bool MakeDir(const string& path); virtual bool WriteFile(const string& path, const string& contents); - virtual string ReadFile(const string& path, string* err); + virtual Status ReadFile(const string& path, string* contents, string* err); virtual int RemoveFile(const string& path); /// Whether stat information can be cached. Only has an effect on Windows. diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index 9d210b4..7187bdf 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -157,8 +157,12 @@ TEST_F(DiskInterfaceTest, StatCache) { TEST_F(DiskInterfaceTest, ReadFile) { string err; - EXPECT_EQ("", disk_.ReadFile("foobar", &err)); - EXPECT_EQ("", err); + std::string content; + ASSERT_EQ(DiskInterface::NotFound, + disk_.ReadFile("foobar", &content, &err)); + EXPECT_EQ("", content); + EXPECT_NE("", err); // actual value is platform-specific + err.clear(); const char* kTestFile = "testfile"; FILE* f = fopen(kTestFile, "wb"); @@ -167,7 +171,9 @@ TEST_F(DiskInterfaceTest, ReadFile) { fprintf(f, "%s", kTestContent); ASSERT_EQ(0, fclose(f)); - EXPECT_EQ(kTestContent, disk_.ReadFile(kTestFile, &err)); + ASSERT_EQ(DiskInterface::Okay, + disk_.ReadFile(kTestFile, &content, &err)); + EXPECT_EQ(kTestContent, content); EXPECT_EQ("", err); } @@ -208,9 +214,9 @@ struct StatTest : public StateTestWithBuiltinRules, assert(false); return false; } - virtual string ReadFile(const string& path, string* err) { + virtual Status ReadFile(const string& path, string* contents, string* err) { assert(false); - return ""; + return NotFound; } virtual int RemoveFile(const string& path) { assert(false); diff --git a/src/eval_env.cc b/src/eval_env.cc index e991d21..8817a87 100644 --- a/src/eval_env.cc +++ b/src/eval_env.cc @@ -55,7 +55,7 @@ void Rule::AddBinding(const string& key, const EvalString& val) { } const EvalString* Rule::GetBinding(const string& key) const { - map<string, EvalString>::const_iterator i = bindings_.find(key); + Bindings::const_iterator i = bindings_.find(key); if (i == bindings_.end()) return NULL; return &i->second; @@ -71,7 +71,8 @@ bool Rule::IsReservedBinding(const string& var) { var == "pool" || var == "restat" || var == "rspfile" || - var == "rspfile_content"; + var == "rspfile_content" || + var == "msvc_deps_prefix"; } const map<string, const Rule*>& BindingEnv::GetRules() const { diff --git a/src/eval_env.h b/src/eval_env.h index 28c4d16..999ce42 100644 --- a/src/eval_env.h +++ b/src/eval_env.h @@ -57,7 +57,6 @@ struct Rule { const string& name() const { return name_; } - typedef map<string, EvalString> Bindings; void AddBinding(const string& key, const EvalString& val); static bool IsReservedBinding(const string& var); @@ -69,7 +68,8 @@ struct Rule { friend struct ManifestParser; string name_; - map<string, EvalString> bindings_; + typedef map<string, EvalString> Bindings; + Bindings bindings_; }; /// An Env which contains a mapping of variables to values diff --git a/src/graph.cc b/src/graph.cc index 9e65675..98a2c18 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -241,8 +241,9 @@ string EdgeEnv::LookupVariable(const string& var) { edge_->inputs_.begin() + explicit_deps_count, var == "in" ? ' ' : '\n'); } else if (var == "out") { + int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_; return MakePathList(edge_->outputs_.begin(), - edge_->outputs_.end(), + edge_->outputs_.begin() + explicit_outs_count, ' '); } @@ -395,8 +396,15 @@ bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, string* err) { METRIC_RECORD("depfile load"); - string content = disk_interface_->ReadFile(path, err); - if (!err->empty()) { + // Read depfile content. Treat a missing depfile as empty. + string content; + switch (disk_interface_->ReadFile(path, &content, err)) { + case DiskInterface::Okay: + break; + case DiskInterface::NotFound: + err->clear(); + break; + case DiskInterface::OtherError: *err = "loading '" + path + "': " + *err; return false; } diff --git a/src/graph.h b/src/graph.h index cf15123..add8d3d 100644 --- a/src/graph.h +++ b/src/graph.h @@ -129,7 +129,7 @@ private: struct Edge { Edge() : rule_(NULL), pool_(NULL), env_(NULL), outputs_ready_(false), deps_missing_(false), - implicit_deps_(0), order_only_deps_(0) {} + implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {} /// Return true if all inputs' in-edges are ready. bool AllInputsReady() const; @@ -181,6 +181,16 @@ struct Edge { return index >= inputs_.size() - order_only_deps_; } + // There are two types of outputs. + // 1) explicit outs, which show up as $out on the command line; + // 2) implicit outs, which the target generates but are not part of $out. + // These are stored in outputs_ in that order, and we keep a count of + // #2 to use when we need to access the various subsets. + int implicit_outs_; + bool is_implicit_out(size_t index) const { + return index >= outputs_.size() - implicit_outs_; + } + bool is_phony() const; bool use_console() const; }; diff --git a/src/graph_test.cc b/src/graph_test.cc index 44be8a5..723e8ea 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -105,6 +105,50 @@ TEST_F(GraphTest, ExplicitImplicit) { EXPECT_TRUE(GetNode("out.o")->dirty()); } +TEST_F(GraphTest, ImplicitOutputParse) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build out | out.imp: cat in\n")); + + Edge* edge = GetNode("out")->in_edge(); + EXPECT_EQ(2, edge->outputs_.size()); + EXPECT_EQ("out", edge->outputs_[0]->path()); + EXPECT_EQ("out.imp", edge->outputs_[1]->path()); + EXPECT_EQ(1, edge->implicit_outs_); + EXPECT_EQ(edge, GetNode("out.imp")->in_edge()); +} + +TEST_F(GraphTest, ImplicitOutputMissing) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build out | out.imp: cat in\n")); + fs_.Create("in", ""); + fs_.Create("out", ""); + + Edge* edge = GetNode("out")->in_edge(); + string err; + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + EXPECT_TRUE(GetNode("out")->dirty()); + EXPECT_TRUE(GetNode("out.imp")->dirty()); +} + +TEST_F(GraphTest, ImplicitOutputOutOfDate) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build out | out.imp: cat in\n")); + fs_.Create("out.imp", ""); + fs_.Tick(); + fs_.Create("in", ""); + fs_.Create("out", ""); + + Edge* edge = GetNode("out")->in_edge(); + string err; + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + EXPECT_TRUE(GetNode("out")->dirty()); + EXPECT_TRUE(GetNode("out.imp")->dirty()); +} + TEST_F(GraphTest, PathWithCurrentDirectory) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc index aba25d0..f18795c 100644 --- a/src/includes_normalize_test.cc +++ b/src/includes_normalize_test.cc @@ -30,15 +30,15 @@ string GetCurDir() { return parts[parts.size() - 1]; } -string NormalizeAndCheckNoError(const std::string& input) { +string NormalizeAndCheckNoError(const string& input) { string result, err; EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), NULL, &result, &err)); EXPECT_EQ("", err); return result; } -string NormalizeRelativeAndCheckNoError(const std::string& input, - const std::string& relative_to) { +string NormalizeRelativeAndCheckNoError(const string& input, + const string& relative_to) { string result, err; EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), relative_to.c_str(), &result, &err)); @@ -160,8 +160,8 @@ TEST(IncludesNormalize, LongInvalidPath) { "012345678\\" "012345678\\" "0123456789"; - std::string forward_slashes(kExactlyMaxPath); - std::replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/'); + string forward_slashes(kExactlyMaxPath); + replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/'); // Make sure a path that's exactly _MAX_PATH long is canonicalized. EXPECT_EQ(forward_slashes, NormalizeAndCheckNoError(kExactlyMaxPath)); diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index e8c0436..be267a3 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -18,6 +18,7 @@ #include <stdlib.h> #include <vector> +#include "disk_interface.h" #include "graph.h" #include "metrics.h" #include "state.h" @@ -25,9 +26,9 @@ #include "version.h" ManifestParser::ManifestParser(State* state, FileReader* file_reader, - bool dupe_edge_should_err) + DupeEdgeAction dupe_edge_action) : state_(state), file_reader_(file_reader), - dupe_edge_should_err_(dupe_edge_should_err), quiet_(false) { + dupe_edge_action_(dupe_edge_action), quiet_(false) { env_ = &state->bindings_; } @@ -35,7 +36,7 @@ bool ManifestParser::Load(const string& filename, string* err, Lexer* parent) { METRIC_RECORD(".ninja parse"); string contents; string read_err; - if (!file_reader_->ReadFile(filename, &contents, &read_err)) { + if (file_reader_->ReadFile(filename, &contents, &read_err) != FileReader::Okay) { *err = "loading '" + filename + "': " + read_err; if (parent) parent->Error(string(*err), err); @@ -247,6 +248,20 @@ bool ManifestParser::ParseEdge(string* err) { } while (!out.empty()); } + // Add all implicit outs, counting how many as we go. + int implicit_outs = 0; + if (lexer_.PeekToken(Lexer::PIPE)) { + for (;;) { + EvalString out; + if (!lexer_.ReadPath(&out, err)) + return err; + if (out.empty()) + break; + outs.push_back(out); + ++implicit_outs; + } + } + if (!ExpectToken(Lexer::COLON, err)) return false; @@ -331,7 +346,7 @@ bool ManifestParser::ParseEdge(string* err) { if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); if (!state_->AddOut(edge, path, slash_bits)) { - if (dupe_edge_should_err_) { + if (dupe_edge_action_ == kDupeEdgeActionError) { lexer_.Error("multiple rules generate " + path + " [-w dupbuild=err]", err); return false; @@ -350,6 +365,7 @@ bool ManifestParser::ParseEdge(string* err) { delete edge; return true; } + edge->implicit_outs_ = implicit_outs; edge->inputs_.reserve(ins.size()); for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) { @@ -380,7 +396,7 @@ bool ManifestParser::ParseFileInclude(bool new_scope, string* err) { return false; string path = eval.Evaluate(env_); - ManifestParser subparser(state_, file_reader_); + ManifestParser subparser(state_, file_reader_, dupe_edge_action_); if (new_scope) { subparser.env_ = new BindingEnv(env_); } else { diff --git a/src/manifest_parser.h b/src/manifest_parser.h index f72cd6f..043e4b2 100644 --- a/src/manifest_parser.h +++ b/src/manifest_parser.h @@ -23,17 +23,18 @@ using namespace std; struct BindingEnv; struct EvalString; +struct FileReader; struct State; +enum DupeEdgeAction { + kDupeEdgeActionWarn, + kDupeEdgeActionError, +}; + /// Parses .ninja files. struct ManifestParser { - struct FileReader { - virtual ~FileReader() {} - virtual bool ReadFile(const string& path, string* content, string* err) = 0; - }; - ManifestParser(State* state, FileReader* file_reader, - bool dupe_edge_should_err = false); + DupeEdgeAction dupe_edge_action); /// Load and parse a file. bool Load(const string& filename, string* err, Lexer* parent = NULL); @@ -66,7 +67,7 @@ private: BindingEnv* env_; FileReader* file_reader_; Lexer lexer_; - bool dupe_edge_should_err_; + DupeEdgeAction dupe_edge_action_; bool quiet_; }; diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc index 6b56ab0..572e2d5 100644 --- a/src/manifest_parser_perftest.cc +++ b/src/manifest_parser_perftest.cc @@ -36,12 +36,6 @@ #include "state.h" #include "util.h" -struct RealFileReader : public ManifestParser::FileReader { - virtual bool ReadFile(const string& path, string* content, string* err) { - return ::ReadFile(path, content, err) == 0; - } -}; - bool WriteFakeManifests(const string& dir, string* err) { RealDiskInterface disk_interface; TimeStamp mtime = disk_interface.Stat(dir + "/build.ninja", err); @@ -59,9 +53,9 @@ bool WriteFakeManifests(const string& dir, string* err) { int LoadManifests(bool measure_command_evaluation) { string err; - RealFileReader file_reader; + RealDiskInterface disk_interface; State state; - ManifestParser parser(&state, &file_reader); + ManifestParser parser(&state, &disk_interface, kDupeEdgeActionWarn); if (!parser.Load("build.ninja", &err)) { fprintf(stderr, "Failed to read test data: %s\n", err.c_str()); exit(1); diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 8f7b575..e8c1907 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -21,30 +21,17 @@ #include "state.h" #include "test.h" -struct ParserTest : public testing::Test, - public ManifestParser::FileReader { +struct ParserTest : public testing::Test { void AssertParse(const char* input) { - ManifestParser parser(&state, this); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); VerifyGraph(state); } - virtual bool ReadFile(const string& path, string* content, string* err) { - files_read_.push_back(path); - map<string, string>::iterator i = files_.find(path); - if (i == files_.end()) { - *err = "No such file or directory"; // Match strerror() for ENOENT. - return false; - } - *content = i->second; - return true; - } - State state; - map<string, string> files_; - vector<string> files_read_; + VirtualFileSystem fs_; }; TEST_F(ParserTest, Empty) { @@ -371,12 +358,28 @@ TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputsError) { "build out1 out2: cat in1\n" "build out1: cat in2\n" "build final: cat out1\n"; - ManifestParser parser(&state, this, /*dupe_edges_should_err=*/true); + ManifestParser parser(&state, &fs_, kDupeEdgeActionError); string err; EXPECT_FALSE(parser.ParseTest(kInput, &err)); EXPECT_EQ("input:5: multiple rules generate out1 [-w dupbuild=err]\n", err); } +TEST_F(ParserTest, DuplicateEdgeInIncludedFile) { + fs_.Create("sub.ninja", + "rule cat\n" + " command = cat $in > $out\n" + "build out1 out2: cat in1\n" + "build out1: cat in2\n" + "build final: cat out1\n"); + const char kInput[] = + "subninja sub.ninja\n"; + ManifestParser parser(&state, &fs_, kDupeEdgeActionError); + string err; + EXPECT_FALSE(parser.ParseTest(kInput, &err)); + EXPECT_EQ("sub.ninja:5: multiple rules generate out1 [-w dupbuild=err]\n", + err); +} + TEST_F(ParserTest, ReservedWords) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule build\n" @@ -388,7 +391,7 @@ TEST_F(ParserTest, ReservedWords) { TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest(string("subn", 4), &err)); EXPECT_EQ("input:1: expected '=', got eof\n" @@ -399,7 +402,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("foobar", &err)); EXPECT_EQ("input:1: expected '=', got eof\n" @@ -410,7 +413,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x 3", &err)); EXPECT_EQ("input:1: expected '=', got identifier\n" @@ -421,7 +424,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x = 3", &err)); EXPECT_EQ("input:1: unexpected EOF\n" @@ -432,7 +435,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x = 3\ny 2", &err)); EXPECT_EQ("input:2: expected '=', got identifier\n" @@ -443,7 +446,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x = $", &err)); EXPECT_EQ("input:1: bad $-escape (literal $ must be written as $$)\n" @@ -454,7 +457,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x = $\n $[\n", &err)); EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n" @@ -465,7 +468,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("x = a$\n b$\n $\n", &err)); EXPECT_EQ("input:4: unexpected EOF\n" @@ -474,7 +477,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("build\n", &err)); EXPECT_EQ("input:1: expected path\n" @@ -485,7 +488,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("build x: y z\n", &err)); EXPECT_EQ("input:1: unknown build rule 'y'\n" @@ -496,7 +499,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("build x:: y z\n", &err)); EXPECT_EQ("input:1: expected build command name\n" @@ -507,7 +510,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n command = cat ok\n" "build x: cat $\n :\n", @@ -520,7 +523,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n", &err)); @@ -529,7 +532,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n" " command = echo\n" @@ -543,7 +546,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n" " command = echo\n" @@ -555,7 +558,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n" " command = ${fafsd\n" @@ -570,7 +573,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n" " command = cat\n" @@ -585,7 +588,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cat\n" " command = cat\n" @@ -599,7 +602,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule %foo\n", &err)); @@ -608,7 +611,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cc\n" " command = foo\n" @@ -622,7 +625,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n" "build $.: cc bar.cc\n", @@ -635,7 +638,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n && bar", &err)); @@ -644,7 +647,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n" "build $: cc bar.cc\n", @@ -657,7 +660,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("default\n", &err)); @@ -669,7 +672,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("default nonexistent\n", &err)); @@ -681,7 +684,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule r\n command = r\n" "build b: r\n" @@ -695,7 +698,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("default $a\n", &err)); EXPECT_EQ("input:1: empty path\n" @@ -706,7 +709,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule r\n" " command = r\n" @@ -718,7 +721,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; // the indented blank line must terminate the rule // this also verifies that "unexpected (token)" errors are correct @@ -731,7 +734,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("pool\n", &err)); EXPECT_EQ("input:1: expected pool name\n", err); @@ -739,7 +742,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("pool foo\n", &err)); EXPECT_EQ("input:2: expected 'depth =' line\n", err); @@ -747,7 +750,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("pool foo\n" " depth = 4\n" @@ -760,7 +763,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("pool foo\n" " depth = -1\n", &err)); @@ -772,7 +775,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("pool foo\n" " bar = 1\n", &err)); @@ -784,7 +787,7 @@ TEST_F(ParserTest, Errors) { { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; // Pool names are dereferenced at edge parsing time. EXPECT_FALSE(parser.ParseTest("rule run\n" @@ -797,7 +800,7 @@ TEST_F(ParserTest, Errors) { TEST_F(ParserTest, MissingInput) { State state; - ManifestParser parser(&state, this); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.Load("build.ninja", &err)); EXPECT_EQ("loading 'build.ninja': No such file or directory", err); @@ -805,7 +808,7 @@ TEST_F(ParserTest, MissingInput) { TEST_F(ParserTest, MultipleOutputs) { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest("rule cc\n command = foo\n depfile = bar\n" "build a.o b.o: cc c.cc\n", @@ -815,7 +818,7 @@ TEST_F(ParserTest, MultipleOutputs) { TEST_F(ParserTest, MultipleOutputsWithDeps) { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n" "build a.o b.o: cc c.cc\n", @@ -825,9 +828,9 @@ TEST_F(ParserTest, MultipleOutputsWithDeps) { } TEST_F(ParserTest, SubNinja) { - files_["test.ninja"] = + fs_.Create("test.ninja", "var = inner\n" - "build $builddir/inner: varref\n"; + "build $builddir/inner: varref\n"); ASSERT_NO_FATAL_FAILURE(AssertParse( "builddir = some_dir/\n" "rule varref\n" @@ -836,9 +839,9 @@ TEST_F(ParserTest, SubNinja) { "build $builddir/outer: varref\n" "subninja test.ninja\n" "build $builddir/outer2: varref\n")); - ASSERT_EQ(1u, files_read_.size()); + ASSERT_EQ(1u, fs_.files_read_.size()); - EXPECT_EQ("test.ninja", files_read_[0]); + EXPECT_EQ("test.ninja", fs_.files_read_[0]); EXPECT_TRUE(state.LookupNode("some_dir/outer")); // Verify our builddir setting is inherited. EXPECT_TRUE(state.LookupNode("some_dir/inner")); @@ -850,7 +853,7 @@ TEST_F(ParserTest, SubNinja) { } TEST_F(ParserTest, MissingSubNinja) { - ManifestParser parser(&state, this); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("subninja foo.ninja\n", &err)); EXPECT_EQ("input:1: loading 'foo.ninja': No such file or directory\n" @@ -861,9 +864,9 @@ TEST_F(ParserTest, MissingSubNinja) { TEST_F(ParserTest, DuplicateRuleInDifferentSubninjas) { // Test that rules are scoped to subninjas. - files_["test.ninja"] = "rule cat\n" - " command = cat\n"; - ManifestParser parser(&state, this); + fs_.Create("test.ninja", "rule cat\n" + " command = cat\n"); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest("rule cat\n" " command = cat\n" @@ -872,11 +875,11 @@ TEST_F(ParserTest, DuplicateRuleInDifferentSubninjas) { TEST_F(ParserTest, DuplicateRuleInDifferentSubninjasWithInclude) { // Test that rules are scoped to subninjas even with includes. - files_["rules.ninja"] = "rule cat\n" - " command = cat\n"; - files_["test.ninja"] = "include rules.ninja\n" - "build x : cat\n"; - ManifestParser parser(&state, this); + fs_.Create("rules.ninja", "rule cat\n" + " command = cat\n"); + fs_.Create("test.ninja", "include rules.ninja\n" + "build x : cat\n"); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest("include rules.ninja\n" "subninja test.ninja\n" @@ -884,19 +887,19 @@ TEST_F(ParserTest, DuplicateRuleInDifferentSubninjasWithInclude) { } TEST_F(ParserTest, Include) { - files_["include.ninja"] = "var = inner\n"; + fs_.Create("include.ninja", "var = inner\n"); ASSERT_NO_FATAL_FAILURE(AssertParse( "var = outer\n" "include include.ninja\n")); - ASSERT_EQ(1u, files_read_.size()); - EXPECT_EQ("include.ninja", files_read_[0]); + ASSERT_EQ(1u, fs_.files_read_.size()); + EXPECT_EQ("include.ninja", fs_.files_read_[0]); EXPECT_EQ("inner", state.bindings_.LookupVariable("var")); } TEST_F(ParserTest, BrokenInclude) { - files_["include.ninja"] = "build\n"; - ManifestParser parser(&state, this); + fs_.Create("include.ninja", "build\n"); + ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn); string err; EXPECT_FALSE(parser.ParseTest("include include.ninja\n", &err)); EXPECT_EQ("include.ninja:1: expected path\n" @@ -924,6 +927,40 @@ TEST_F(ParserTest, OrderOnly) { ASSERT_TRUE(edge->is_order_only(1)); } +TEST_F(ParserTest, ImplicitOutput) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build foo | imp: cat bar\n")); + + Edge* edge = state.LookupNode("imp")->in_edge(); + ASSERT_EQ(edge->outputs_.size(), 2); + EXPECT_TRUE(edge->is_implicit_out(1)); +} + +TEST_F(ParserTest, ImplicitOutputEmpty) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build foo | : cat bar\n")); + + Edge* edge = state.LookupNode("foo")->in_edge(); + ASSERT_EQ(edge->outputs_.size(), 1); + EXPECT_FALSE(edge->is_implicit_out(0)); +} + +TEST_F(ParserTest, NoExplicitOutput) { + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); + string err; + EXPECT_FALSE(parser.ParseTest( +"rule cat\n" +" command = cat $in > $out\n" +"build | imp : cat bar\n", &err)); + ASSERT_EQ("input:3: expected path\n" + "build | imp : cat bar\n" + " ^ near here", err); +} + TEST_F(ParserTest, DefaultDefault) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n command = cat $in > $out\n" @@ -976,7 +1013,7 @@ TEST_F(ParserTest, UTF8) { TEST_F(ParserTest, CRLF) { State state; - ManifestParser parser(&state, NULL); + ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err)); diff --git a/src/ninja.cc b/src/ninja.cc index 21dede6..a3f1be0 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -229,14 +229,6 @@ int GuessParallelism() { } } -/// An implementation of ManifestParser::FileReader that actually reads -/// the file. -struct RealFileReader : public ManifestParser::FileReader { - virtual bool ReadFile(const string& path, string* content, string* err) { - return ::ReadFile(path, content, err) == 0; - } -}; - /// Rebuild the build manifest, if necessary. /// Returns true if the manifest was rebuilt. bool NinjaMain::RebuildManifest(const char* input_file, string* err) { @@ -701,7 +693,7 @@ int NinjaMain::ToolUrtle(int argc, char** argv) { if ('0' <= *p && *p <= '9') { count = count*10 + *p - '0'; } else { - for (int i = 0; i < std::max(count, 1); ++i) + for (int i = 0; i < max(count, 1); ++i) printf("%c", *p); count = 0; } @@ -774,9 +766,10 @@ const Tool* ChooseTool(const string& tool_name) { bool DebugEnable(const string& name) { if (name == "list") { printf("debugging modes:\n" -" stats print operation counts/timing info\n" -" explain explain what caused a command to execute\n" -" keeprsp don't delete @response files on success\n" +" stats print operation counts/timing info\n" +" explain explain what caused a command to execute\n" +" keepdepfile don't delete depfiles after they're read by ninja\n" +" keeprsp don't delete @response files on success\n" #ifdef _WIN32 " nostatcache don't batch stat() calls per directory and cache them\n" #endif @@ -788,6 +781,9 @@ bool DebugEnable(const string& name) { } else if (name == "explain") { g_explaining = true; return true; + } else if (name == "keepdepfile") { + g_keep_depfile = true; + return true; } else if (name == "keeprsp") { g_keep_rsp = true; return true; @@ -796,8 +792,9 @@ bool DebugEnable(const string& name) { return true; } else { const char* suggestion = - SpellcheckString(name.c_str(), "stats", "explain", "keeprsp", - "nostatcache", NULL); + SpellcheckString(name.c_str(), + "stats", "explain", "keepdepfile", "keeprsp", + "nostatcache", NULL); if (suggestion) { Error("unknown debug setting '%s', did you mean '%s'?", name.c_str(), suggestion); @@ -1112,9 +1109,10 @@ int real_main(int argc, char** argv) { for (int cycle = 1; cycle <= kCycleLimit; ++cycle) { NinjaMain ninja(ninja_command, config); - RealFileReader file_reader; - ManifestParser parser(&ninja.state_, &file_reader, - options.dupe_edges_should_err); + ManifestParser parser(&ninja.state_, &ninja.disk_interface_, + options.dupe_edges_should_err + ? kDupeEdgeActionError + : kDupeEdgeActionWarn); string err; if (!parser.Load(options.input_file, &err)) { Error("%s", err.c_str()); @@ -1135,6 +1133,10 @@ int real_main(int argc, char** argv) { // Attempt to rebuild the manifest before building anything else if (ninja.RebuildManifest(options.input_file, &err)) { + // In dry_run mode the regeneration will succeed without changing the + // manifest forever. Better to return immediately. + if (config.dry_run) + return 0; // Start the build over with the new manifest. continue; } else if (!err.empty()) { @@ -1159,7 +1161,7 @@ int main(int argc, char** argv) { #if defined(_MSC_VER) // Set a handler to catch crashes not caught by the __try..__except // block (e.g. an exception in a stack-unwind-block). - std::set_terminate(TerminateHandler); + set_terminate(TerminateHandler); __try { // Running inside __try ... __except suppresses any Windows error // dialogs for errors such as bad_alloc. diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc index fad66e8..4bab719 100644 --- a/src/subprocess-win32.cc +++ b/src/subprocess-win32.cc @@ -255,7 +255,7 @@ bool SubprocessSet::DoWork() { if (subproc->Done()) { vector<Subprocess*>::iterator end = - std::remove(running_.begin(), running_.end(), subproc); + remove(running_.begin(), running_.end(), subproc); if (running_.end() != end) { finished_.push(subproc); running_.resize(end - running_.begin()); diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 066bbb7..2fe4bce 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -159,8 +159,8 @@ TEST_F(SubprocessTest, Console) { // Test that stdin, stdout and stderr are a terminal. // Also check that the current process is connected to a terminal. Subprocess* subproc = - subprocs_.Add(std::string("test -t 0 -a -t 1 -a -t 2 && ") + - std::string(kIsConnectedToTerminal), + subprocs_.Add(string("test -t 0 -a -t 1 -a -t 2 && ") + + string(kIsConnectedToTerminal), /*use_console=*/true); ASSERT_NE((Subprocess*)0, subproc); diff --git a/src/test.cc b/src/test.cc index aed8db7..53bfc48 100644 --- a/src/test.cc +++ b/src/test.cc @@ -95,7 +95,7 @@ Node* StateTestWithBuiltinRules::GetNode(const string& path) { } void AssertParse(State* state, const char* input) { - ManifestParser parser(state, NULL); + ManifestParser parser(state, NULL, kDupeEdgeActionWarn); string err; EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); @@ -115,7 +115,7 @@ void VerifyGraph(const State& state) { for (vector<Node*>::const_iterator in_node = (*e)->inputs_.begin(); in_node != (*e)->inputs_.end(); ++in_node) { const vector<Edge*>& out_edges = (*in_node)->out_edges(); - EXPECT_NE(std::find(out_edges.begin(), out_edges.end(), *e), + EXPECT_NE(find(out_edges.begin(), out_edges.end(), *e), out_edges.end()); } // Check that the edge's outputs have the edge as in-edge. @@ -164,12 +164,17 @@ bool VirtualFileSystem::MakeDir(const string& path) { return true; // success } -string VirtualFileSystem::ReadFile(const string& path, string* err) { +FileReader::Status VirtualFileSystem::ReadFile(const string& path, + string* contents, + string* err) { files_read_.push_back(path); FileMap::iterator i = files_.find(path); - if (i != files_.end()) - return i->second.contents; - return ""; + if (i != files_.end()) { + *contents = i->second.contents; + return Okay; + } + *err = strerror(ENOENT); + return NotFound; } int VirtualFileSystem::RemoveFile(const string& path) { @@ -145,7 +145,7 @@ struct VirtualFileSystem : public DiskInterface { virtual TimeStamp Stat(const string& path, string* err) const; virtual bool WriteFile(const string& path, const string& contents); virtual bool MakeDir(const string& path); - virtual string ReadFile(const string& path, string* err); + virtual Status ReadFile(const string& path, string* contents, string* err); virtual int RemoveFile(const string& path); /// An entry for a single in-memory file. diff --git a/src/util.cc b/src/util.cc index d150fe2..e31fd1f 100644 --- a/src/util.cc +++ b/src/util.cc @@ -226,8 +226,8 @@ bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits, } if (dst == start) { - *err = "path canonicalizes to the empty path"; - return false; + *dst++ = '.'; + *dst++ = '\0'; } *len = dst - start - 1; diff --git a/src/util_test.cc b/src/util_test.cc index 8ca7f56..33a4107 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -91,6 +91,22 @@ TEST(CanonicalizePath, PathSamples) { path = "/"; EXPECT_TRUE(CanonicalizePath(&path, &err)); EXPECT_EQ("", path); + + path = "/foo/.."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("", path); + + path = "."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ(".", path); + + path = "./."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ(".", path); + + path = "foo/.."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ(".", path); } #ifdef _WIN32 @@ -288,22 +304,6 @@ TEST(CanonicalizePath, TooManyComponents) { } #endif -TEST(CanonicalizePath, EmptyResult) { - string path; - string err; - - EXPECT_FALSE(CanonicalizePath(&path, &err)); - EXPECT_EQ("empty path", err); - - path = "."; - EXPECT_FALSE(CanonicalizePath(&path, &err)); - EXPECT_EQ("path canonicalizes to the empty path", err); - - path = "./."; - EXPECT_FALSE(CanonicalizePath(&path, &err)); - EXPECT_EQ("path canonicalizes to the empty path", err); -} - TEST(CanonicalizePath, UpDir) { string path, err; path = "../../foo/bar.h"; |