summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--HACKING.md2
-rw-r--r--README4
-rwxr-xr-xconfigure.py35
-rw-r--r--doc/README.md11
-rw-r--r--doc/dblatex.xsl7
-rw-r--r--doc/docbook.xsl22
-rw-r--r--doc/manual.asciidoc76
-rw-r--r--doc/style.css6
-rw-r--r--misc/ninja_syntax.py5
-rwxr-xr-xmisc/ninja_syntax_test.py6
-rw-r--r--misc/zsh-completion5
-rw-r--r--src/build.cc31
-rw-r--r--src/build_log_perftest.cc2
-rw-r--r--src/build_test.cc16
-rw-r--r--src/debug_flags.cc2
-rw-r--r--src/debug_flags.h2
-rw-r--r--src/disk_interface.cc14
-rw-r--r--src/disk_interface.h27
-rw-r--r--src/disk_interface_test.cc16
-rw-r--r--src/eval_env.cc5
-rw-r--r--src/eval_env.h4
-rw-r--r--src/graph.cc14
-rw-r--r--src/graph.h12
-rw-r--r--src/graph_test.cc44
-rw-r--r--src/includes_normalize_test.cc10
-rw-r--r--src/manifest_parser.cc26
-rw-r--r--src/manifest_parser.h15
-rw-r--r--src/manifest_parser_perftest.cc10
-rw-r--r--src/manifest_parser_test.cc185
-rw-r--r--src/ninja.cc38
-rw-r--r--src/subprocess-win32.cc2
-rw-r--r--src/subprocess_test.cc4
-rw-r--r--src/test.cc17
-rw-r--r--src/test.h2
-rw-r--r--src/util.cc4
-rw-r--r--src/util_test.cc32
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
diff --git a/HACKING.md b/HACKING.md
index d8cb2a2..c544615 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -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
diff --git a/README b/README
index 59d7ff9..a1535ff 100644
--- a/README
+++ b/README
@@ -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) {
diff --git a/src/test.h b/src/test.h
index 156e68a..488c243 100644
--- a/src/test.h
+++ b/src/test.h
@@ -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";