summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml7
-rw-r--r--HACKING.md44
-rw-r--r--RELEASING17
-rw-r--r--appveyor.yml40
-rwxr-xr-xconfigure.py11
-rw-r--r--misc/ninja-mode.el2
-rw-r--r--misc/ninja.vim18
-rw-r--r--misc/ninja_syntax.py4
-rwxr-xr-xmisc/ninja_syntax_test.py4
-rwxr-xr-xmisc/output_test.py82
-rw-r--r--misc/zsh-completion2
-rw-r--r--src/build.cc53
-rw-r--r--src/build.h26
-rw-r--r--src/build_log.cc13
-rw-r--r--src/build_log_perftest.cc2
-rw-r--r--src/build_test.cc13
-rw-r--r--src/clean.cc24
-rw-r--r--src/deps_log.cc21
-rw-r--r--src/deps_log.h8
-rw-r--r--src/deps_log_test.cc4
-rw-r--r--src/disk_interface.cc36
-rw-r--r--src/disk_interface_test.cc11
-rw-r--r--src/graph.cc23
-rw-r--r--src/graph.h1
-rw-r--r--src/graph_test.cc12
-rw-r--r--src/hash_map.h3
-rw-r--r--src/includes_normalize-win32.cc6
-rw-r--r--src/lexer.cc19
-rw-r--r--src/lexer.in.cc19
-rw-r--r--src/line_printer.cc1
-rw-r--r--src/line_printer.h5
-rw-r--r--src/manifest_parser.cc27
-rw-r--r--src/manifest_parser.h17
-rw-r--r--src/manifest_parser_perftest.cc2
-rw-r--r--src/manifest_parser_test.cc142
-rw-r--r--src/minidump-win32.cc6
-rw-r--r--src/msvc_helper-win32.cc12
-rw-r--r--src/msvc_helper_main-win32.cc2
-rw-r--r--src/ninja.cc134
-rw-r--r--src/state.h2
-rw-r--r--src/subprocess-posix.cc66
-rw-r--r--src/subprocess-win32.cc11
-rw-r--r--src/subprocess_test.cc2
-rw-r--r--src/test.cc5
-rw-r--r--src/test.h6
-rw-r--r--src/timestamp.h15
-rw-r--r--src/util.cc11
-rw-r--r--src/util.h14
-rw-r--r--src/version.cc2
-rw-r--r--src/win32port.h8
51 files changed, 765 insertions, 253 deletions
diff --git a/.gitignore b/.gitignore
index a86205b..11150c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,6 @@ TAGS
# Ninja output
.ninja_deps
.ninja_log
+
+# Visual Studio Code project files
+/.vscode/
diff --git a/.travis.yml b/.travis.yml
index 093139b..70b1fd0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,4 +3,9 @@ language: cpp
compiler:
- gcc
- clang
-script: ./configure.py --bootstrap && ./ninja all && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots && ./misc/ninja_syntax_test.py
+script:
+ - ./configure.py --bootstrap
+ - ./ninja all
+ - ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
+ - ./misc/ninja_syntax_test.py
+ - ./misc/output_test.py
diff --git a/HACKING.md b/HACKING.md
index e7c91ef..5c2469b 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -13,14 +13,50 @@ run `ninja_test` when developing.
Ninja is built using itself. To bootstrap the first binary, run the
configure script as `./configure.py --bootstrap`. This first compiles
all non-test source files together, then re-builds Ninja using itself.
-You should end up with a `ninja` binary (or `ninja.exe`) in the source root.
+You should end up with a `ninja` binary (or `ninja.exe`) in the project root.
#### Windows
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 of Visual Studio.
+running `vcvarsall` in a normal command prompt).
+
+For other combinations such as gcc/clang you will need the compiler
+(gcc/cl) in your PATH and you will have to set the appropriate
+platform configuration script.
+
+See below if you want to use mingw or some other compiler instead of
+Visual Studio.
+
+##### Using Visual Studio
+Assuming that you now have python installed, then the steps for building under
+ Windows using Visual Studio are:
+
+Clone and checkout the latest release (or whatever branch you want). You
+can do this in either a command prompt or by opening a git bash prompt:
+
+```
+ $ git clone git://github.com/ninja-build/ninja.git && cd ninja
+ $ git checkout release
+```
+
+Then:
+
+1. Open a Windows command prompt in the folder where you checked out ninja.
+2. Select the Microsoft build environment by running
+`vcvarsall.bat` with the appropriate environment.
+3. Build ninja and test it.
+
+The steps for a Visual Studio 2015 64-bit build are outlined here:
+
+```
+ > "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
+ > python configure.py --bootstrap
+ > ninja --help
+```
+Copy the ninja executable to another location, if desired, e.g. C:\local\Ninja.
+
+Finally add the path where ninja.exe is to the PATH variable.
### Adjusting build flags
@@ -95,7 +131,7 @@ and run that directly on some representative input files.
Generally it's the [Google C++ coding style][], but in brief:
* Function name are camelcase.
-* Member methods are camelcase, expect for trivial getters which are
+* Member methods are camelcase, except for trivial getters which are
underscore separated.
* Local variables are underscore separated.
* Member variables are underscore separated and suffixed by an extra
diff --git a/RELEASING b/RELEASING
index 5f51b73..da4dbdd 100644
--- a/RELEASING
+++ b/RELEASING
@@ -1,19 +1,20 @@
Notes to myself on all the steps to make for a Ninja release.
Push new release branch:
-1. Consider sending a heads-up to the ninja-build mailing list first
-2. Make sure branches 'master' and 'release' are synced up locally
-3. update src/version.cc with new version (with ".git"), then
+1. Run afl-fuzz for a day or so (see HACKING.md) and run ninja_test
+2. Consider sending a heads-up to the ninja-build mailing list first
+3. Make sure branches 'master' and 'release' are synced up locally
+4. Update src/version.cc with new version (with ".git"), then
git commit -am 'mark this 1.5.0.git'
-4. git checkout release; git merge master
-5. fix version number in src/version.cc (it will likely conflict in the above)
-6. fix version in doc/manual.asciidoc (exists only on release branch)
-7. commit, tag, push (don't forget to push --tags)
+5. git checkout release; git merge master
+6. Fix version number in src/version.cc (it will likely conflict in the above)
+7. Fix version in doc/manual.asciidoc (exists only on release branch)
+8. commit, tag, push (don't forget to push --tags)
git commit -am v1.5.0; git push origin release
git tag v1.5.0; git push --tags
# Push the 1.5.0.git change on master too:
git checkout master; git push origin master
-8. construct release notes from prior notes
+9. Construct release notes from prior notes
credits: git shortlog -s --no-merges REV..
Release on github:
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..4c64f29
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,40 @@
+version: 1.0.{build}
+image: Visual Studio 2017
+
+environment:
+ CLICOLOR_FORCE: 1
+ CHERE_INVOKING: 1 # Tell Bash to inherit the current working directory
+ matrix:
+ - MSYSTEM: MINGW64
+ - MSYSTEM: MSVC
+
+for:
+ -
+ matrix:
+ only:
+ - MSYSTEM: MINGW64
+ build_script:
+ ps: "C:\\msys64\\usr\\bin\\bash -lc @\"\n
+ pacman -S --quiet --noconfirm --needed re2c 2>&1\n
+ sed -i 's|cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out|$ar crs $out $in|g' configure.py\n
+ ./configure.py --bootstrap --platform mingw 2>&1\n
+ ./ninja all\n
+ ./ninja_test 2>&1\n
+ ./misc/ninja_syntax_test.py 2>&1\n\"@"
+ -
+ matrix:
+ only:
+ - MSYSTEM: MSVC
+ build_script:
+ - cmd: >-
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
+
+ python configure.py --bootstrap
+
+ ninja.bootstrap.exe all
+
+ ninja_test
+
+ python misc/ninja_syntax_test.py
+
+test: off
diff --git a/configure.py b/configure.py
index 6eda66a..cfe27f2 100755
--- a/configure.py
+++ b/configure.py
@@ -256,7 +256,7 @@ configure_args = sys.argv[1:]
if '--bootstrap' in configure_args:
configure_args.remove('--bootstrap')
n.variable('configure_args', ' '.join(configure_args))
-env_keys = set(['CXX', 'AR', 'CFLAGS', 'LDFLAGS'])
+env_keys = set(['CXX', 'AR', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS'])
configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys)
if configure_env:
config_str = ' '.join([k + '=' + pipes.quote(configure_env[k])
@@ -356,6 +356,11 @@ else:
if platform.uses_usr_local():
cflags.append('-I/usr/local/include')
ldflags.append('-L/usr/local/lib')
+ if platform.is_aix():
+ # printf formats for int64_t, uint64_t; large file support
+ cflags.append('-D__STDC_FORMAT_MACROS')
+ cflags.append('-D_LARGE_FILES')
+
libs = []
@@ -397,6 +402,10 @@ def shell_escape(str):
if 'CFLAGS' in configure_env:
cflags.append(configure_env['CFLAGS'])
+ ldflags.append(configure_env['CFLAGS'])
+if 'CXXFLAGS' in configure_env:
+ cflags.append(configure_env['CXXFLAGS'])
+ ldflags.append(configure_env['CXXFLAGS'])
n.variable('cflags', ' '.join(shell_escape(flag) for flag in cflags))
if 'LDFLAGS' in configure_env:
ldflags.append(configure_env['LDFLAGS'])
diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el
index 639e537..8b975d5 100644
--- a/misc/ninja-mode.el
+++ b/misc/ninja-mode.el
@@ -56,7 +56,7 @@
(save-excursion
(goto-char (line-end-position 0))
(or
- ;; If we're continuting the previous line, it's not a
+ ;; If we're continuing the previous line, it's not a
;; comment.
(not (eq ?$ (char-before)))
;; Except if the previous line is a comment as well, as the
diff --git a/misc/ninja.vim b/misc/ninja.vim
index 190d9ce..c1ffd50 100644
--- a/misc/ninja.vim
+++ b/misc/ninja.vim
@@ -1,8 +1,8 @@
" ninja build file syntax.
" Language: ninja build file as described at
" http://ninja-build.org/manual.html
-" Version: 1.4
-" Last Change: 2014/05/13
+" Version: 1.5
+" Last Change: 2018/04/05
" Maintainer: Nicolas Weber <nicolasweber@gmx.de>
" Version 1.4 of this script is in the upstream vim repository and will be
" included in the next vim release. If you change this, please send your change
@@ -21,7 +21,10 @@ set cpo&vim
syn case match
-syn match ninjaComment /#.*/ contains=@Spell
+" Comments are only matched when the # is at the beginning of the line (with
+" optional whitespace), as long as the prior line didn't end with a $
+" continuation.
+syn match ninjaComment /\(\$\n\)\@<!\_^\s*#.*$/ contains=@Spell
" Toplevel statements are the ones listed here and
" toplevel variable assignments (ident '=' value).
@@ -38,12 +41,13 @@ syn match ninjaKeyword "^subninja\>"
" limited set of magic variables, 'build' allows general
" let assignments.
" manifest_parser.cc, ParseRule()
-syn region ninjaRule start="^rule" end="^\ze\S" contains=ALL transparent
-syn keyword ninjaRuleCommand contained command deps depfile description generator
+syn region ninjaRule start="^rule" end="^\ze\S" contains=TOP transparent
+syn keyword ninjaRuleCommand contained containedin=ninjaRule command
+ \ deps depfile description generator
\ pool restat rspfile rspfile_content
-syn region ninjaPool start="^pool" end="^\ze\S" contains=ALL transparent
-syn keyword ninjaPoolCommand contained depth
+syn region ninjaPool start="^pool" end="^\ze\S" contains=TOP transparent
+syn keyword ninjaPoolCommand contained containedin=ninjaPool depth
" Strings are parsed as follows:
" lexer.in.cc, ReadEvalString()
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index 5c52ea2..051bac1 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -60,7 +60,7 @@ class Writer(object):
self.variable('deps', deps, indent=1)
def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
- variables=None, implicit_outputs=None):
+ variables=None, implicit_outputs=None, pool=None):
outputs = as_list(outputs)
out_outputs = [escape_path(x) for x in outputs]
all_inputs = [escape_path(x) for x in as_list(inputs)]
@@ -81,6 +81,8 @@ class Writer(object):
self._line('build %s: %s' % (' '.join(out_outputs),
' '.join([rule] + all_inputs)))
+ if pool is not None:
+ self._line(' pool = %s' % pool)
if variables:
if isinstance(variables, dict):
diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py
index 07e3ed3..90ff9c6 100755
--- a/misc/ninja_syntax_test.py
+++ b/misc/ninja_syntax_test.py
@@ -46,13 +46,13 @@ class TestLineWordWrap(unittest.TestCase):
self.out.getvalue())
def test_comment_wrap(self):
- # Filenames shoud not be wrapped
+ # Filenames should 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.
+ # Test that indent is taking into account when breaking subsequent lines.
# The second line should not be ' to tree', as that's longer than the
# test layout width of 8.
self.n._line('line_one to tree')
diff --git a/misc/output_test.py b/misc/output_test.py
new file mode 100755
index 0000000..d19cebc
--- /dev/null
+++ b/misc/output_test.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+
+"""Runs ./ninja and checks if the output is correct.
+
+In order to simulate a smart terminal it uses the 'script' command.
+"""
+
+import subprocess
+import sys
+import tempfile
+import unittest
+
+def run(build_ninja, flags='', pipe=False):
+ with tempfile.NamedTemporaryFile('w') as f:
+ f.write(build_ninja)
+ f.flush()
+ ninja_cmd = './ninja {} -f {}'.format(flags, f.name)
+ try:
+ if pipe:
+ output = subprocess.check_output([ninja_cmd], shell=True)
+ else:
+ output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'])
+ except subprocess.CalledProcessError as err:
+ sys.stdout.buffer.write(err.output)
+ raise err
+ final_output = ''
+ for line in output.decode('utf-8').splitlines(True):
+ if len(line) > 0 and line[-1] == '\r':
+ continue
+ final_output += line.replace('\r', '')
+ return final_output
+
+class Output(unittest.TestCase):
+ def test_issue_1418(self):
+ self.assertEqual(run(
+'''rule echo
+ command = sleep 0.$delay && echo $out
+ description = echo $out
+
+build a: echo
+ delay = 3
+build b: echo
+ delay = 2
+build c: echo
+ delay = 1
+'''),
+'''[1/3] echo c\x1b[K
+c
+[2/3] echo b\x1b[K
+b
+[3/3] echo a\x1b[K
+a
+''')
+
+ def test_issue_1214(self):
+ print_red = '''rule echo
+ command = printf '\x1b[31mred\x1b[0m'
+ description = echo $out
+
+build a: echo
+'''
+ # Only strip color when ninja's output is piped.
+ self.assertEqual(run(print_red),
+'''[1/1] echo a\x1b[K
+\x1b[31mred\x1b[0m
+''')
+ self.assertEqual(run(print_red, pipe=True),
+'''[1/1] echo a
+red
+''')
+ # Even in verbose mode, colors should still only be stripped when piped.
+ self.assertEqual(run(print_red, flags='-v'),
+'''[1/1] printf '\x1b[31mred\x1b[0m'
+\x1b[31mred\x1b[0m
+''')
+ self.assertEqual(run(print_red, flags='-v', pipe=True),
+'''[1/1] printf '\x1b[31mred\x1b[0m'
+red
+''')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/misc/zsh-completion b/misc/zsh-completion
index bf23fac..4cee3b8 100644
--- a/misc/zsh-completion
+++ b/misc/zsh-completion
@@ -14,7 +14,7 @@
# limitations under the License.
# Add the following to your .zshrc to tab-complete ninja targets
-# . path/to/ninja/misc/zsh-completion
+# fpath=(path/to/ninja/misc/zsh-completion $fpath)
__get_targets() {
dir="."
diff --git a/src/build.cc b/src/build.cc
index 61ef0e8..6b33024 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -156,7 +156,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
// thousands of parallel compile commands.)
// TODO: There should be a flag to disable escape code stripping.
string final_output;
- if (!printer_.is_smart_terminal())
+ if (!printer_.supports_color())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;
@@ -318,18 +318,18 @@ bool Plan::AddSubTarget(Node* node, Node* dependent, string* err) {
return false; // Don't need to do anything.
// If an entry in want_ does not already exist for edge, create an entry which
- // maps to false, indicating that we do not want to build this entry itself.
- pair<map<Edge*, bool>::iterator, bool> want_ins =
- want_.insert(make_pair(edge, false));
- bool& want = want_ins.first->second;
+ // maps to kWantNothing, indicating that we do not want to build this entry itself.
+ pair<map<Edge*, Want>::iterator, bool> want_ins =
+ want_.insert(make_pair(edge, kWantNothing));
+ Want& want = want_ins.first->second;
// If we do need to build edge and we haven't already marked it as wanted,
// mark it now.
- if (node->dirty() && !want) {
- want = true;
+ if (node->dirty() && want == kWantNothing) {
+ want = kWantToStart;
++wanted_edges_;
if (edge->AllInputsReady())
- ScheduleWork(edge);
+ ScheduleWork(want_ins.first);
if (!edge->is_phony())
++command_edges_;
}
@@ -355,30 +355,32 @@ Edge* Plan::FindWork() {
return edge;
}
-void Plan::ScheduleWork(Edge* edge) {
- set<Edge*>::iterator e = ready_.lower_bound(edge);
- if (e != ready_.end() && !ready_.key_comp()(edge, *e)) {
+void Plan::ScheduleWork(map<Edge*, Want>::iterator want_e) {
+ if (want_e->second == kWantToFinish) {
// This edge has already been scheduled. We can get here again if an edge
// and one of its dependencies share an order-only input, or if a node
// duplicates an out edge (see https://github.com/ninja-build/ninja/pull/519).
// Avoid scheduling the work again.
return;
}
+ assert(want_e->second == kWantToStart);
+ want_e->second = kWantToFinish;
+ Edge* edge = want_e->first;
Pool* pool = edge->pool();
if (pool->ShouldDelayEdge()) {
pool->DelayEdge(edge);
pool->RetrieveReadyEdges(&ready_);
} else {
pool->EdgeScheduled(*edge);
- ready_.insert(e, edge);
+ ready_.insert(edge);
}
}
void Plan::EdgeFinished(Edge* edge, EdgeResult result) {
- map<Edge*, bool>::iterator e = want_.find(edge);
+ map<Edge*, Want>::iterator e = want_.find(edge);
assert(e != want_.end());
- bool directly_wanted = e->second;
+ bool directly_wanted = e->second != kWantNothing;
// See if this job frees up any delayed jobs.
if (directly_wanted)
@@ -405,14 +407,14 @@ void Plan::NodeFinished(Node* node) {
// See if we we want any edges from this node.
for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
oe != node->out_edges().end(); ++oe) {
- map<Edge*, bool>::iterator want_e = want_.find(*oe);
+ map<Edge*, Want>::iterator want_e = want_.find(*oe);
if (want_e == want_.end())
continue;
// See if the edge is now ready.
if ((*oe)->AllInputsReady()) {
- if (want_e->second) {
- ScheduleWork(*oe);
+ if (want_e->second != kWantNothing) {
+ ScheduleWork(want_e);
} else {
// We do not need to build this edge, but we might need to build one of
// its dependents.
@@ -428,8 +430,8 @@ bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
oe != node->out_edges().end(); ++oe) {
// Don't process edges that we don't actually want.
- map<Edge*, bool>::iterator want_e = want_.find(*oe);
- if (want_e == want_.end() || !want_e->second)
+ map<Edge*, Want>::iterator want_e = want_.find(*oe);
+ if (want_e == want_.end() || want_e->second == kWantNothing)
continue;
// Don't attempt to clean an edge if it failed to load deps.
@@ -441,7 +443,12 @@ bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
vector<Node*>::iterator
begin = (*oe)->inputs_.begin(),
end = (*oe)->inputs_.end() - (*oe)->order_only_deps_;
- if (find_if(begin, end, mem_fun(&Node::dirty)) == end) {
+#if __cplusplus < 201703L
+#define MEM_FN mem_fun
+#else
+#define MEM_FN mem_fn // mem_fun was removed in C++17.
+#endif
+ if (find_if(begin, end, MEM_FN(&Node::dirty)) == end) {
// Recompute most_recent_input.
Node* most_recent_input = NULL;
for (vector<Node*>::iterator i = begin; i != end; ++i) {
@@ -464,7 +471,7 @@ bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
return false;
}
- want_e->second = false;
+ want_e->second = kWantNothing;
--wanted_edges_;
if (!(*oe)->is_phony())
--command_edges_;
@@ -476,8 +483,8 @@ bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
void Plan::Dump() {
printf("pending: %d\n", (int)want_.size());
- for (map<Edge*, bool>::iterator e = want_.begin(); e != want_.end(); ++e) {
- if (e->second)
+ for (map<Edge*, Want>::iterator e = want_.begin(); e != want_.end(); ++e) {
+ if (e->second != kWantNothing)
printf("want ");
e->first->Dump();
}
diff --git a/src/build.h b/src/build.h
index 43786f1..9b90e8a 100644
--- a/src/build.h
+++ b/src/build.h
@@ -78,17 +78,29 @@ private:
bool AddSubTarget(Node* node, Node* dependent, string* err);
void NodeFinished(Node* node);
+ /// Enumerate possible steps we want for an edge.
+ enum Want
+ {
+ /// We do not want to build the edge, but we might want to build one of
+ /// its dependents.
+ kWantNothing,
+ /// We want to build the edge, but have not yet scheduled it.
+ kWantToStart,
+ /// We want to build the edge, have scheduled it, and are waiting
+ /// for it to complete.
+ kWantToFinish
+ };
+
/// Submits a ready edge as a candidate for execution.
/// The edge may be delayed from running, for example if it's a member of a
/// currently-full pool.
- void ScheduleWork(Edge* edge);
+ void ScheduleWork(map<Edge*, Want>::iterator want_e);
/// Keep track of which edges we want to build in this plan. If this map does
/// not contain an entry for an edge, we do not want to build the entry or its
- /// dependents. If an entry maps to false, we do not want to build it, but we
- /// might want to build one of its dependents. If the entry maps to true, we
- /// want to build it.
- map<Edge*, bool> want_;
+ /// dependents. If it does contain an entry, the enumeration indicates what
+ /// we want for the edge.
+ map<Edge*, Want> want_;
set<Edge*> ready_;
@@ -178,7 +190,11 @@ struct Builder {
State* state_;
const BuildConfig& config_;
Plan plan_;
+#if __cplusplus < 201703L
auto_ptr<CommandRunner> command_runner_;
+#else
+ unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
+#endif
BuildStatus* status_;
private:
diff --git a/src/build_log.cc b/src/build_log.cc
index 333915a..2a65f9d 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -76,11 +76,17 @@ uint64_t MurmurHash64A(const void* key, size_t len) {
switch (len & 7)
{
case 7: h ^= uint64_t(data[6]) << 48;
+ NINJA_FALLTHROUGH;
case 6: h ^= uint64_t(data[5]) << 40;
+ NINJA_FALLTHROUGH;
case 5: h ^= uint64_t(data[4]) << 32;
+ NINJA_FALLTHROUGH;
case 4: h ^= uint64_t(data[3]) << 24;
+ NINJA_FALLTHROUGH;
case 3: h ^= uint64_t(data[2]) << 16;
+ NINJA_FALLTHROUGH;
case 2: h ^= uint64_t(data[1]) << 8;
+ NINJA_FALLTHROUGH;
case 1: h ^= uint64_t(data[0]);
h *= m;
};
@@ -167,6 +173,9 @@ bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
if (log_file_) {
if (!WriteEntry(log_file_, *log_entry))
return false;
+ if (fflush(log_file_) != 0) {
+ return false;
+ }
}
}
return true;
@@ -290,7 +299,7 @@ bool BuildLog::Load(const string& path, string* err) {
if (!end)
continue;
*end = 0;
- restat_mtime = atol(start);
+ restat_mtime = strtoll(start, NULL, 10);
start = end + 1;
end = (char*)memchr(start, kFieldSeparator, line_end - start);
@@ -353,7 +362,7 @@ BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
}
bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) {
- return fprintf(f, "%d\t%d\t%d\t%s\t%" PRIx64 "\n",
+ return fprintf(f, "%d\t%d\t%" PRId64 "\t%s\t%" PRIx64 "\n",
entry.start_time, entry.end_time, entry.mtime,
entry.output.c_str(), entry.command_hash) > 0;
}
diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc
index b4efb1d..e471d13 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, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, NULL);
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 a0f898f..46ab33e 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -1068,6 +1068,19 @@ TEST_F(BuildTest, PhonyNoWork) {
EXPECT_TRUE(builder_.AlreadyUpToDate());
}
+// Test a self-referencing phony. Ideally this should not work, but
+// ninja 1.7 and below tolerated and CMake 2.8.12.x and 3.0.x both
+// incorrectly produce it. We tolerate it for compatibility.
+TEST_F(BuildTest, PhonySelfReference) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build a: phony a\n"));
+
+ EXPECT_TRUE(builder_.AddTarget("a", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AlreadyUpToDate());
+}
+
TEST_F(BuildTest, Fail) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule fail\n"
diff --git a/src/clean.cc b/src/clean.cc
index 1d6ba9e..ce6a575 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -101,6 +101,7 @@ void Cleaner::PrintHeader() {
printf("\n");
else
printf(" ");
+ fflush(stdout);
}
void Cleaner::PrintFooter() {
@@ -180,15 +181,22 @@ int Cleaner::CleanTargets(int target_count, char* targets[]) {
Reset();
PrintHeader();
for (int i = 0; i < target_count; ++i) {
- const char* target_name = targets[i];
- Node* target = state_->LookupNode(target_name);
- if (target) {
- if (IsVerbose())
- printf("Target %s\n", target_name);
- DoCleanTarget(target);
- } else {
- Error("unknown target '%s'", target_name);
+ string target_name = targets[i];
+ uint64_t slash_bits;
+ string err;
+ if (!CanonicalizePath(&target_name, &slash_bits, &err)) {
+ Error("failed to canonicalize '%s': %s", target_name.c_str(), err.c_str());
status_ = 1;
+ } else {
+ Node* target = state_->LookupNode(target_name);
+ if (target) {
+ if (IsVerbose())
+ printf("Target %s\n", target_name.c_str());
+ DoCleanTarget(target);
+ } else {
+ Error("unknown target '%s'", target_name.c_str());
+ status_ = 1;
+ }
}
}
PrintFooter();
diff --git a/src/deps_log.cc b/src/deps_log.cc
index 89c6023..eb81a37 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -30,7 +30,7 @@
// The version is stored as 4 bytes after the signature and also serves as a
// byte order mark. Signature and version combined are 16 bytes long.
const char kFileSignature[] = "# ninjadeps\n";
-const int kCurrentVersion = 3;
+const int kCurrentVersion = 4;
// Record size is currently limited to less than the full 32 bit, due to
// internal buffers having to have this size.
@@ -124,7 +124,7 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime,
return true;
// Update on-disk representation.
- unsigned size = 4 * (1 + 1 + node_count);
+ unsigned size = 4 * (1 + 2 + node_count);
if (size > kMaxRecordSize) {
errno = ERANGE;
return false;
@@ -135,8 +135,11 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime,
int id = node->id();
if (fwrite(&id, 4, 1, file_) < 1)
return false;
- int timestamp = mtime;
- if (fwrite(&timestamp, 4, 1, file_) < 1)
+ uint32_t mtime_part = static_cast<uint32_t>(mtime & 0xffffffff);
+ if (fwrite(&mtime_part, 4, 1, file_) < 1)
+ return false;
+ mtime_part = static_cast<uint32_t>((mtime >> 32) & 0xffffffff);
+ if (fwrite(&mtime_part, 4, 1, file_) < 1)
return false;
for (int i = 0; i < node_count; ++i) {
id = nodes[i]->id();
@@ -209,7 +212,7 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
bool is_deps = (size >> 31) != 0;
size = size & 0x7FFFFFFF;
- if (fread(buf, size, 1, f) < 1 || size > kMaxRecordSize) {
+ if (size > kMaxRecordSize || fread(buf, size, 1, f) < 1) {
read_failed = true;
break;
}
@@ -218,9 +221,11 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
assert(size % 4 == 0);
int* deps_data = reinterpret_cast<int*>(buf);
int out_id = deps_data[0];
- int mtime = deps_data[1];
- deps_data += 2;
- int deps_count = (size / 4) - 2;
+ TimeStamp mtime;
+ mtime = (TimeStamp)(((uint64_t)(unsigned int)deps_data[2] << 32) |
+ (uint64_t)(unsigned int)deps_data[1]);
+ deps_data += 3;
+ int deps_count = (size / 4) - 3;
Deps* deps = new Deps(mtime, deps_count);
for (int i = 0; i < deps_count; ++i) {
diff --git a/src/deps_log.h b/src/deps_log.h
index cec0257..3812a28 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -57,7 +57,9 @@ struct State;
/// one's complement of the expected index of the record (to detect
/// concurrent writes of multiple ninja processes to the log).
/// dependency records are an array of 4-byte integers
-/// [output path id, output path mtime, input path id, input path id...]
+/// [output path id,
+/// output path mtime (lower 4 bytes), output path mtime (upper 4 bytes),
+/// input path id, input path id...]
/// (The mtime is compared against the on-disk output path mtime
/// to verify the stored data is up-to-date.)
/// If two records reference the same output the latter one in the file
@@ -75,10 +77,10 @@ struct DepsLog {
// Reading (startup-time) interface.
struct Deps {
- Deps(int mtime, int node_count)
+ Deps(int64_t mtime, int node_count)
: mtime(mtime), node_count(node_count), nodes(new Node*[node_count]) {}
~Deps() { delete [] nodes; }
- int mtime;
+ TimeStamp mtime;
int node_count;
Node** nodes;
};
diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc
index 89f7be1..0cdeb45 100644
--- a/src/deps_log_test.cc
+++ b/src/deps_log_test.cc
@@ -143,7 +143,7 @@ TEST_F(DepsLogTest, DoubleEntry) {
ASSERT_GT(file_size, 0);
}
- // Now reload the file, and readd the same deps.
+ // Now reload the file, and read the same deps.
{
State state;
DepsLog log;
@@ -203,7 +203,7 @@ TEST_F(DepsLogTest, Recompact) {
ASSERT_GT(file_size, 0);
}
- // Now reload the file, and add slighly different deps.
+ // Now reload the file, and add slightly different deps.
int file_size_2;
{
State state;
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 28530b1..7351715 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -61,17 +61,16 @@ int MakeDir(const string& path) {
TimeStamp TimeStampFromFileTime(const FILETIME& filetime) {
// FILETIME is in 100-nanosecond increments since the Windows epoch.
// We don't much care about epoch correctness but we do want the
- // resulting value to fit in an integer.
+ // resulting value to fit in a 64-bit integer.
uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) |
((uint64_t)filetime.dwLowDateTime);
- mtime /= 1000000000LL / 100; // 100ns -> s.
- mtime -= 12622770400LL; // 1600 epoch -> 2000 epoch (subtract 400 years).
- return (TimeStamp)mtime;
+ // 1600 epoch -> 2000 epoch (subtract 400 years).
+ return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100);
}
TimeStamp StatSingleFile(const string& path, string* err) {
WIN32_FILE_ATTRIBUTE_DATA attrs;
- if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &attrs)) {
+ if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) {
DWORD win_err = GetLastError();
if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND)
return 0;
@@ -113,6 +112,11 @@ bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps,
}
do {
string lowername = ffd.cFileName;
+ if (lowername == "..") {
+ // Seems to just copy the timestamp for ".." from ".", which is wrong.
+ // This is the case at least on NTFS under Windows 7.
+ continue;
+ }
transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower);
stamps->insert(make_pair(lowername,
TimeStampFromFileTime(ffd.ftLastWriteTime)));
@@ -165,6 +169,11 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
string dir = DirName(path);
string base(path.substr(dir.size() ? dir.size() + 1 : 0));
+ if (base == "..") {
+ // StatAllFilesInDir does not report any information for base = "..".
+ base = ".";
+ dir = path;
+ }
transform(dir.begin(), dir.end(), dir.begin(), ::tolower);
transform(base.begin(), base.end(), base.begin(), ::tolower);
@@ -192,7 +201,22 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
// that it doesn't exist.
if (st.st_mtime == 0)
return 1;
- return st.st_mtime;
+#if defined(__APPLE__) && !defined(_POSIX_C_SOURCE)
+ return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
+ st.st_mtimespec.tv_nsec);
+#elif (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700 || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \
+ defined(__BIONIC__) || (defined (__SVR4) && defined (__sun)) || defined(__FreeBSD__))
+ // For glibc, see "Timestamp files" in the Notes of http://www.kernel.org/doc/man-pages/online/pages/man2/stat.2.html
+ // newlib, uClibc and musl follow the kernel (or Cygwin) headers and define the right macro values above.
+ // For bsd, see https://github.com/freebsd/freebsd/blob/master/sys/sys/stat.h and similar
+ // For bionic, C and POSIX API is always enabled.
+ // For solaris, see https://docs.oracle.com/cd/E88353_01/html/E37841/stat-2.html.
+ return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
+#elif defined(_AIX)
+ return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
+#else
+ return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
+#endif
#endif
}
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index d7fb8f8..81aa63a 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -87,6 +87,8 @@ TEST_F(DiskInterfaceTest, StatExistingDir) {
string err;
ASSERT_TRUE(disk_.MakeDir("subdir"));
ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir"));
+ EXPECT_GT(disk_.Stat("..", &err), 1);
+ EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat(".", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("subdir", &err), 1);
@@ -105,7 +107,6 @@ TEST_F(DiskInterfaceTest, StatExistingDir) {
#ifdef _WIN32
TEST_F(DiskInterfaceTest, StatCache) {
string err;
- disk_.AllowStatCache(true);
ASSERT_TRUE(Touch("file1"));
ASSERT_TRUE(Touch("fiLE2"));
@@ -115,6 +116,10 @@ TEST_F(DiskInterfaceTest, StatCache) {
ASSERT_TRUE(Touch("subdir\\SUBFILE2"));
ASSERT_TRUE(Touch("subdir\\SUBFILE3"));
+ disk_.AllowStatCache(false);
+ TimeStamp parent_stat_uncached = disk_.Stat("..", &err);
+ disk_.AllowStatCache(true);
+
EXPECT_GT(disk_.Stat("FIle1", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("file1", &err), 1);
@@ -125,6 +130,8 @@ TEST_F(DiskInterfaceTest, StatCache) {
EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1);
EXPECT_EQ("", err);
+ EXPECT_GT(disk_.Stat("..", &err), 1);
+ EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat(".", &err), 1);
EXPECT_EQ("", err);
EXPECT_GT(disk_.Stat("subdir", &err), 1);
@@ -138,6 +145,8 @@ TEST_F(DiskInterfaceTest, StatCache) {
EXPECT_EQ(disk_.Stat("subdir", &err),
disk_.Stat("subdir/subsubdir/..", &err));
EXPECT_EQ("", err);
+ EXPECT_EQ(disk_.Stat("..", &err), parent_stat_uncached);
+ EXPECT_EQ("", err);
EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err),
disk_.Stat("subdir/subsubdir/.", &err));
EXPECT_EQ("", err);
diff --git a/src/graph.cc b/src/graph.cc
index 7dd9491..b41c247 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -170,6 +170,13 @@ bool DependencyScan::VerifyDAG(Node* node, vector<Node*>* stack, string* err) {
err->append(" -> ");
}
err->append((*start)->path());
+
+ if ((start + 1) == stack->end() && edge->maybe_phonycycle_diagnostic()) {
+ // The manifest parser would have filtered out the self-referencing
+ // input if it were not configured to allow the error.
+ err->append(" [-w phonycycle=err]");
+ }
+
return false;
}
@@ -226,7 +233,7 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge,
if (output_mtime < most_recent_input->mtime()) {
EXPLAIN("%soutput %s older than most recent input %s "
- "(%d vs %d)",
+ "(%" PRId64 " vs %" PRId64 ")",
used_restat ? "restat of " : "", output->path().c_str(),
most_recent_input->path().c_str(),
output_mtime, most_recent_input->mtime());
@@ -250,7 +257,7 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge,
// mtime of the most recent input. This can occur even when the mtime
// on disk is newer if a previous run wrote to the output file but
// exited with an error or was interrupted.
- EXPLAIN("recorded mtime of %s older than most recent input %s (%d vs %d)",
+ EXPLAIN("recorded mtime of %s older than most recent input %s (%" PRId64 " vs %" PRId64 ")",
output->path().c_str(), most_recent_input->path().c_str(),
entry->mtime, most_recent_input->mtime());
return true;
@@ -410,6 +417,14 @@ bool Edge::use_console() const {
return pool() == &State::kConsolePool;
}
+bool Edge::maybe_phonycycle_diagnostic() const {
+ // CMake 2.8.12.x and 3.0.x produced self-referencing phony rules
+ // of the form "build a: phony ... a ...". Restrict our
+ // "phonycycle" diagnostic option to the form it used.
+ return is_phony() && outputs_.size() == 1 && implicit_outs_ == 0 &&
+ implicit_deps_ == 0;
+}
+
// static
string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
string result = path;
@@ -426,7 +441,7 @@ string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
}
void Node::Dump(const char* prefix) const {
- printf("%s <%s 0x%p> mtime: %d%s, (:%s), ",
+ printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ",
prefix, path().c_str(), this,
mtime(), mtime() ? "" : " (:missing)",
dirty() ? " dirty" : " clean");
@@ -532,7 +547,7 @@ bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) {
// Deps are invalid if the output is newer than the deps.
if (output->mtime() > deps->mtime) {
- EXPLAIN("stored deps info out of date for '%s' (%d vs %d)",
+ EXPLAIN("stored deps info out of date for '%s' (%" PRId64 " vs %" PRId64 ")",
output->path().c_str(), deps->mtime, output->mtime());
return false;
}
diff --git a/src/graph.h b/src/graph.h
index 586c588..a8f0641 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -201,6 +201,7 @@ struct Edge {
bool is_phony() const;
bool use_console() const;
+ bool maybe_phonycycle_diagnostic() const;
};
diff --git a/src/graph_test.cc b/src/graph_test.cc
index d4d2824..422bc9a 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -323,6 +323,18 @@ TEST_F(GraphTest, NestedPhonyPrintsDone) {
ASSERT_FALSE(plan_.more_to_do());
}
+TEST_F(GraphTest, PhonySelfReferenceError) {
+ ManifestParserOptions parser_opts;
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ AssertParse(&state_,
+"build a: phony a\n",
+ parser_opts);
+
+ string err;
+ EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), &err));
+ ASSERT_EQ("dependency cycle: a -> a [-w phonycycle=err]", err);
+}
+
TEST_F(GraphTest, DependencyCycle) {
AssertParse(&state_,
"build out: cat mid\n"
diff --git a/src/hash_map.h b/src/hash_map.h
index a91aeb9..55d2c9d 100644
--- a/src/hash_map.h
+++ b/src/hash_map.h
@@ -18,6 +18,7 @@
#include <algorithm>
#include <string.h>
#include "string_piece.h"
+#include "util.h"
// MurmurHash2, by Austin Appleby
static inline
@@ -40,7 +41,9 @@ unsigned int MurmurHash2(const void* key, size_t len) {
}
switch (len) {
case 3: h ^= data[2] << 16;
+ NINJA_FALLTHROUGH;
case 2: h ^= data[1] << 8;
+ NINJA_FALLTHROUGH;
case 1: h ^= data[0];
h *= m;
};
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index 459329b..795542b 100644
--- a/src/includes_normalize-win32.cc
+++ b/src/includes_normalize-win32.cc
@@ -61,8 +61,8 @@ bool SameDrive(StringPiece a, StringPiece b) {
char a_absolute[_MAX_PATH];
char b_absolute[_MAX_PATH];
- GetFullPathName(a.AsString().c_str(), sizeof(a_absolute), a_absolute, NULL);
- GetFullPathName(b.AsString().c_str(), sizeof(b_absolute), b_absolute, NULL);
+ GetFullPathNameA(a.AsString().c_str(), sizeof(a_absolute), a_absolute, NULL);
+ GetFullPathNameA(b.AsString().c_str(), sizeof(b_absolute), b_absolute, NULL);
char a_drive[_MAX_DIR];
char b_drive[_MAX_DIR];
_splitpath(a_absolute, a_drive, NULL, NULL, NULL);
@@ -122,7 +122,7 @@ string IncludesNormalize::AbsPath(StringPiece s) {
}
char result[_MAX_PATH];
- GetFullPathName(s.AsString().c_str(), sizeof(result), result, NULL);
+ GetFullPathNameA(s.AsString().c_str(), sizeof(result), result, NULL);
for (char* c = result; *c; ++c)
if (*c == '\\')
*c = '/';
diff --git a/src/lexer.cc b/src/lexer.cc
index 37b8678..3c6e70e 100644
--- a/src/lexer.cc
+++ b/src/lexer.cc
@@ -23,14 +23,14 @@
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
- const char* context = input_.str_;
+ const char* line_start = input_.str_;
for (const char* p = input_.str_; p < last_token_; ++p) {
if (*p == '\n') {
++line;
- context = p + 1;
+ line_start = p + 1;
}
}
- int col = last_token_ ? (int)(last_token_ - context) : 0;
+ int col = last_token_ ? (int)(last_token_ - line_start) : 0;
char buf[1024];
snprintf(buf, sizeof(buf), "%s:%d: ", filename_.AsString().c_str(), line);
@@ -43,12 +43,12 @@ bool Lexer::Error(const string& message, string* err) {
int len;
bool truncated = true;
for (len = 0; len < kTruncateColumn; ++len) {
- if (context[len] == 0 || context[len] == '\n') {
+ if (line_start[len] == 0 || line_start[len] == '\n') {
truncated = false;
break;
}
}
- *err += string(context, len);
+ *err += string(line_start, len);
if (truncated)
*err += "...";
*err += "\n";
@@ -537,8 +537,9 @@ yy92:
bool Lexer::ReadIdent(string* out) {
const char* p = ofs_;
+ const char* start;
for (;;) {
- const char* start = p;
+ start = p;
{
unsigned char yych;
@@ -604,7 +605,10 @@ yy96:
}
yy97:
++p;
- { return false; }
+ {
+ last_token_ = start;
+ return false;
+ }
yy99:
++p;
yych = *p;
@@ -616,6 +620,7 @@ yy100:
}
}
+ last_token_ = start;
ofs_ = p;
EatWhitespace();
return true;
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
index f861239..c1fb822 100644
--- a/src/lexer.in.cc
+++ b/src/lexer.in.cc
@@ -22,14 +22,14 @@
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
- const char* context = input_.str_;
+ const char* line_start = input_.str_;
for (const char* p = input_.str_; p < last_token_; ++p) {
if (*p == '\n') {
++line;
- context = p + 1;
+ line_start = p + 1;
}
}
- int col = last_token_ ? (int)(last_token_ - context) : 0;
+ int col = last_token_ ? (int)(last_token_ - line_start) : 0;
char buf[1024];
snprintf(buf, sizeof(buf), "%s:%d: ", filename_.AsString().c_str(), line);
@@ -42,12 +42,12 @@ bool Lexer::Error(const string& message, string* err) {
int len;
bool truncated = true;
for (len = 0; len < kTruncateColumn; ++len) {
- if (context[len] == 0 || context[len] == '\n') {
+ if (line_start[len] == 0 || line_start[len] == '\n') {
truncated = false;
break;
}
}
- *err += string(context, len);
+ *err += string(line_start, len);
if (truncated)
*err += "...";
*err += "\n";
@@ -182,16 +182,21 @@ void Lexer::EatWhitespace() {
bool Lexer::ReadIdent(string* out) {
const char* p = ofs_;
+ const char* start;
for (;;) {
- const char* start = p;
+ start = p;
/*!re2c
varname {
out->assign(start, p - start);
break;
}
- [^] { return false; }
+ [^] {
+ last_token_ = start;
+ return false;
+ }
*/
}
+ last_token_ = start;
ofs_ = p;
EatWhitespace();
return true;
diff --git a/src/line_printer.cc b/src/line_printer.cc
index 2cd3e17..cfc1f19 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -41,6 +41,7 @@ LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
#endif
+ supports_color_ = smart_terminal_;
}
void LinePrinter::Print(string to_print, LineType type) {
diff --git a/src/line_printer.h b/src/line_printer.h
index 55225e5..92d4dc4 100644
--- a/src/line_printer.h
+++ b/src/line_printer.h
@@ -27,6 +27,8 @@ struct LinePrinter {
bool is_smart_terminal() const { return smart_terminal_; }
void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
+ bool supports_color() const { return supports_color_; }
+
enum LineType {
FULL,
ELIDE
@@ -46,6 +48,9 @@ struct LinePrinter {
/// Whether we can do fancy terminal control codes.
bool smart_terminal_;
+ /// Whether we can use ISO 6429 (ANSI) color sequences.
+ bool supports_color_;
+
/// Whether the caret is at the beginning of a blank line.
bool have_blank_line_;
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index 2164921..27c423b 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -26,9 +26,9 @@
#include "version.h"
ManifestParser::ManifestParser(State* state, FileReader* file_reader,
- DupeEdgeAction dupe_edge_action)
+ ManifestParserOptions options)
: state_(state), file_reader_(file_reader),
- dupe_edge_action_(dupe_edge_action), quiet_(false) {
+ options_(options), quiet_(false) {
env_ = &state->bindings_;
}
@@ -346,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_action_ == kDupeEdgeActionError) {
+ if (options_.dupe_edge_action_ == kDupeEdgeActionError) {
lexer_.Error("multiple rules generate " + path + " [-w dupbuild=err]",
err);
return false;
@@ -383,6 +383,25 @@ bool ManifestParser::ParseEdge(string* err) {
edge->implicit_deps_ = implicit;
edge->order_only_deps_ = order_only;
+ if (options_.phony_cycle_action_ == kPhonyCycleActionWarn &&
+ edge->maybe_phonycycle_diagnostic()) {
+ // CMake 2.8.12.x and 3.0.x incorrectly write phony build statements
+ // that reference themselves. Ninja used to tolerate these in the
+ // build graph but that has since been fixed. Filter them out to
+ // support users of those old CMake versions.
+ Node* out = edge->outputs_[0];
+ vector<Node*>::iterator new_end =
+ remove(edge->inputs_.begin(), edge->inputs_.end(), out);
+ if (new_end != edge->inputs_.end()) {
+ edge->inputs_.erase(new_end, edge->inputs_.end());
+ if (!quiet_) {
+ Warning("phony target '%s' names itself as an input; "
+ "ignoring [-w phonycycle=warn]",
+ out->path().c_str());
+ }
+ }
+ }
+
// Multiple outputs aren't (yet?) supported with depslog.
string deps_type = edge->GetBinding("deps");
if (!deps_type.empty() && edge->outputs_.size() > 1) {
@@ -400,7 +419,7 @@ bool ManifestParser::ParseFileInclude(bool new_scope, string* err) {
return false;
string path = eval.Evaluate(env_);
- ManifestParser subparser(state_, file_reader_, dupe_edge_action_);
+ ManifestParser subparser(state_, file_reader_, options_);
if (new_scope) {
subparser.env_ = new BindingEnv(env_);
} else {
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
index 043e4b2..2136018 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -31,10 +31,23 @@ enum DupeEdgeAction {
kDupeEdgeActionError,
};
+enum PhonyCycleAction {
+ kPhonyCycleActionWarn,
+ kPhonyCycleActionError,
+};
+
+struct ManifestParserOptions {
+ ManifestParserOptions()
+ : dupe_edge_action_(kDupeEdgeActionWarn),
+ phony_cycle_action_(kPhonyCycleActionWarn) {}
+ DupeEdgeAction dupe_edge_action_;
+ PhonyCycleAction phony_cycle_action_;
+};
+
/// Parses .ninja files.
struct ManifestParser {
ManifestParser(State* state, FileReader* file_reader,
- DupeEdgeAction dupe_edge_action);
+ ManifestParserOptions options = ManifestParserOptions());
/// Load and parse a file.
bool Load(const string& filename, string* err, Lexer* parent = NULL);
@@ -67,7 +80,7 @@ private:
BindingEnv* env_;
FileReader* file_reader_;
Lexer lexer_;
- DupeEdgeAction dupe_edge_action_;
+ ManifestParserOptions options_;
bool quiet_;
};
diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc
index 60c2054..67d11f9 100644
--- a/src/manifest_parser_perftest.cc
+++ b/src/manifest_parser_perftest.cc
@@ -56,7 +56,7 @@ int LoadManifests(bool measure_command_evaluation) {
string err;
RealDiskInterface disk_interface;
State state;
- ManifestParser parser(&state, &disk_interface, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &disk_interface);
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 3c82dc5..c91d8d1 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -23,7 +23,7 @@
struct ParserTest : public testing::Test {
void AssertParse(const char* input) {
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest(input, &err));
ASSERT_EQ("", err);
@@ -358,7 +358,9 @@ TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputsError) {
"build out1 out2: cat in1\n"
"build out1: cat in2\n"
"build final: cat out1\n";
- ManifestParser parser(&state, &fs_, kDupeEdgeActionError);
+ ManifestParserOptions parser_opts;
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
string err;
EXPECT_FALSE(parser.ParseTest(kInput, &err));
EXPECT_EQ("input:5: multiple rules generate out1 [-w dupbuild=err]\n", err);
@@ -373,13 +375,41 @@ TEST_F(ParserTest, DuplicateEdgeInIncludedFile) {
"build final: cat out1\n");
const char kInput[] =
"subninja sub.ninja\n";
- ManifestParser parser(&state, &fs_, kDupeEdgeActionError);
+ ManifestParserOptions parser_opts;
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
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, PhonySelfReferenceIgnored) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"build a: phony a\n"
+));
+
+ Node* node = state.LookupNode("a");
+ Edge* edge = node->in_edge();
+ ASSERT_TRUE(edge->inputs_.empty());
+}
+
+TEST_F(ParserTest, PhonySelfReferenceKept) {
+ const char kInput[] =
+"build a: phony a\n";
+ ManifestParserOptions parser_opts;
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ ManifestParser parser(&state, &fs_, parser_opts);
+ string err;
+ EXPECT_TRUE(parser.ParseTest(kInput, &err));
+ EXPECT_EQ("", err);
+
+ Node* node = state.LookupNode("a");
+ Edge* edge = node->in_edge();
+ ASSERT_EQ(edge->inputs_.size(), 1);
+ ASSERT_EQ(edge->inputs_[0], node);
+}
+
TEST_F(ParserTest, ReservedWords) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"rule build\n"
@@ -391,7 +421,7 @@ TEST_F(ParserTest, ReservedWords) {
TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest(string("subn", 4), &err));
EXPECT_EQ("input:1: expected '=', got eof\n"
@@ -402,7 +432,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("foobar", &err));
EXPECT_EQ("input:1: expected '=', got eof\n"
@@ -413,7 +443,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x 3", &err));
EXPECT_EQ("input:1: expected '=', got identifier\n"
@@ -424,7 +454,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = 3", &err));
EXPECT_EQ("input:1: unexpected EOF\n"
@@ -435,7 +465,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = 3\ny 2", &err));
EXPECT_EQ("input:2: expected '=', got identifier\n"
@@ -446,7 +476,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = $", &err));
EXPECT_EQ("input:1: bad $-escape (literal $ must be written as $$)\n"
@@ -457,7 +487,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = $\n $[\n", &err));
EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n"
@@ -468,7 +498,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("x = a$\n b$\n $\n", &err));
EXPECT_EQ("input:4: unexpected EOF\n"
@@ -477,7 +507,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build\n", &err));
EXPECT_EQ("input:1: expected path\n"
@@ -488,29 +518,29 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build x: y z\n", &err));
EXPECT_EQ("input:1: unknown build rule 'y'\n"
"build x: y z\n"
- " ^ near here"
+ " ^ near here"
, err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("build x:: y z\n", &err));
EXPECT_EQ("input:1: expected build command name\n"
"build x:: y z\n"
- " ^ near here"
+ " ^ near here"
, err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n command = cat ok\n"
"build x: cat $\n :\n",
@@ -523,7 +553,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n",
&err));
@@ -532,7 +562,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = echo\n"
@@ -546,7 +576,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = echo\n"
@@ -558,7 +588,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = ${fafsd\n"
@@ -573,7 +603,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -588,7 +618,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -602,16 +632,19 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule %foo\n",
&err));
- EXPECT_EQ("input:1: expected rule name\n", err);
+ EXPECT_EQ("input:1: expected rule name\n"
+ "rule %foo\n"
+ " ^ near here",
+ err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n"
" command = foo\n"
@@ -625,7 +658,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
"build $.: cc bar.cc\n",
@@ -638,16 +671,19 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n && bar",
&err));
- EXPECT_EQ("input:3: expected variable name\n", err);
+ EXPECT_EQ("input:3: expected variable name\n"
+ " && bar\n"
+ " ^ near here",
+ err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
"build $: cc bar.cc\n",
@@ -660,7 +696,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default\n",
&err));
@@ -672,7 +708,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default nonexistent\n",
&err));
@@ -684,7 +720,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule r\n command = r\n"
"build b: r\n"
@@ -698,7 +734,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("default $a\n", &err));
EXPECT_EQ("input:1: empty path\n"
@@ -709,7 +745,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule r\n"
" command = r\n"
@@ -721,7 +757,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
// the indented blank line must terminate the rule
// this also verifies that "unexpected (token)" errors are correct
@@ -734,15 +770,17 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool\n", &err));
- EXPECT_EQ("input:1: expected pool name\n", err);
+ EXPECT_EQ("input:1: expected pool name\n"
+ "pool\n"
+ " ^ near here", err);
}
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n", &err));
EXPECT_EQ("input:2: expected 'depth =' line\n", err);
@@ -750,7 +788,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" depth = 4\n"
@@ -763,7 +801,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" depth = -1\n", &err));
@@ -775,7 +813,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("pool foo\n"
" bar = 1\n", &err));
@@ -787,7 +825,7 @@ TEST_F(ParserTest, Errors) {
{
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
// Pool names are dereferenced at edge parsing time.
EXPECT_FALSE(parser.ParseTest("rule run\n"
@@ -800,7 +838,7 @@ TEST_F(ParserTest, Errors) {
TEST_F(ParserTest, MissingInput) {
State local_state;
- ManifestParser parser(&local_state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, &fs_);
string err;
EXPECT_FALSE(parser.Load("build.ninja", &err));
EXPECT_EQ("loading 'build.ninja': No such file or directory", err);
@@ -808,7 +846,7 @@ TEST_F(ParserTest, MissingInput) {
TEST_F(ParserTest, MultipleOutputs) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest("rule cc\n command = foo\n depfile = bar\n"
"build a.o b.o: cc c.cc\n",
@@ -818,7 +856,7 @@ TEST_F(ParserTest, MultipleOutputs) {
TEST_F(ParserTest, MultipleOutputsWithDeps) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n"
"build a.o b.o: cc c.cc\n",
@@ -853,7 +891,7 @@ TEST_F(ParserTest, SubNinja) {
}
TEST_F(ParserTest, MissingSubNinja) {
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_FALSE(parser.ParseTest("subninja foo.ninja\n", &err));
EXPECT_EQ("input:1: loading 'foo.ninja': No such file or directory\n"
@@ -866,7 +904,7 @@ TEST_F(ParserTest, DuplicateRuleInDifferentSubninjas) {
// Test that rules are scoped to subninjas.
fs_.Create("test.ninja", "rule cat\n"
" command = cat\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest("rule cat\n"
" command = cat\n"
@@ -879,7 +917,7 @@ TEST_F(ParserTest, DuplicateRuleInDifferentSubninjasWithInclude) {
" command = cat\n");
fs_.Create("test.ninja", "include rules.ninja\n"
"build x : cat\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_TRUE(parser.ParseTest("include rules.ninja\n"
"subninja test.ninja\n"
@@ -899,7 +937,7 @@ TEST_F(ParserTest, Include) {
TEST_F(ParserTest, BrokenInclude) {
fs_.Create("include.ninja", "build\n");
- ManifestParser parser(&state, &fs_, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, &fs_);
string err;
EXPECT_FALSE(parser.ParseTest("include include.ninja\n", &err));
EXPECT_EQ("include.ninja:1: expected path\n"
@@ -974,7 +1012,7 @@ TEST_F(ParserTest, ImplicitOutputDupes) {
}
TEST_F(ParserTest, NoExplicitOutput) {
- ManifestParser parser(&state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest(
"rule cat\n"
@@ -1034,7 +1072,7 @@ TEST_F(ParserTest, UTF8) {
TEST_F(ParserTest, CRLF) {
State local_state;
- ManifestParser parser(&local_state, NULL, kDupeEdgeActionWarn);
+ ManifestParser parser(&local_state, NULL);
string err;
EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err));
diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc
index 1efb085..ca93638 100644
--- a/src/minidump-win32.cc
+++ b/src/minidump-win32.cc
@@ -32,17 +32,17 @@ typedef BOOL (WINAPI *MiniDumpWriteDumpFunc) (
/// Creates a windows minidump in temp folder.
void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) {
char temp_path[MAX_PATH];
- GetTempPath(sizeof(temp_path), temp_path);
+ GetTempPathA(sizeof(temp_path), temp_path);
char temp_file[MAX_PATH];
sprintf(temp_file, "%s\\ninja_crash_dump_%lu.dmp",
temp_path, GetCurrentProcessId());
// Delete any previous minidump of the same name.
- DeleteFile(temp_file);
+ DeleteFileA(temp_file);
// Load DbgHelp.dll dynamically, as library is not present on all
// Windows versions.
- HMODULE dbghelp = LoadLibrary("dbghelp.dll");
+ HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
if (dbghelp == NULL) {
Error("failed to create minidump: LoadLibrary('dbghelp.dll'): %s",
GetLastErrorString().c_str());
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
index e37a26e..de6147a 100644
--- a/src/msvc_helper-win32.cc
+++ b/src/msvc_helper-win32.cc
@@ -43,10 +43,10 @@ int CLWrapper::Run(const string& command, string* output) {
security_attributes.bInheritHandle = TRUE;
// Must be inheritable so subprocesses can dup to children.
- HANDLE nul = CreateFile("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE |
- FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
+ HANDLE nul =
+ CreateFileA("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ &security_attributes, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE)
Fatal("couldn't open nul");
@@ -58,8 +58,8 @@ int CLWrapper::Run(const string& command, string* output) {
Win32Fatal("SetHandleInformation");
PROCESS_INFORMATION process_info = {};
- STARTUPINFO startup_info = {};
- startup_info.cb = sizeof(STARTUPINFO);
+ STARTUPINFOA startup_info = {};
+ startup_info.cb = sizeof(STARTUPINFOA);
startup_info.hStdInput = nul;
startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
startup_info.hStdOutput = stdout_write;
diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc
index e419cd7..644b2a2 100644
--- a/src/msvc_helper_main-win32.cc
+++ b/src/msvc_helper_main-win32.cc
@@ -113,7 +113,7 @@ int MSVCHelperMain(int argc, char** argv) {
PushPathIntoEnvironment(env);
}
- char* command = GetCommandLine();
+ char* command = GetCommandLineA();
command = strstr(command, " -- ");
if (!command) {
Fatal("expected command line to end with \" -- command args\"");
diff --git a/src/ninja.cc b/src/ninja.cc
index 54de7b9..8108f21 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -70,6 +70,9 @@ struct Options {
/// Whether duplicate rules for one target should warn or print an error.
bool dupe_edges_should_err;
+
+ /// Whether phony cycles should warn or print an error.
+ bool phony_cycle_should_err;
};
/// The Ninja main() loads up a series of data structures; various tools need
@@ -151,7 +154,7 @@ struct NinjaMain : public BuildLogUser {
// Just checking n isn't enough: If an old output is both in the build log
// and in the deps log, it will have a Node object in state_. (It will also
// have an in edge if one of its inputs is another output that's in the deps
- // log, but having a deps edge product an output thats input to another deps
+ // log, but having a deps edge product an output that's input to another deps
// edge is rare, and the first recompaction will delete all old outputs from
// the deps log, and then a second recompaction will clear the build log,
// which seems good enough for this corner case.)
@@ -204,15 +207,15 @@ void Usage(const BuildConfig& config) {
" -f FILE specify input build file [default=build.ninja]\n"
"\n"
" -j N run N jobs in parallel [default=%d, derived from CPUs available]\n"
-" -k N keep going until N jobs fail [default=1]\n"
+" -k N keep going until N jobs fail (0 means infinity) [default=1]\n"
" -l N do not start new jobs if the load average is greater than N\n"
" -n dry run (don't run commands but act like they succeeded)\n"
" -v show all command lines while building\n"
"\n"
-" -d MODE enable debugging (use -d list to list modes)\n"
-" -t TOOL run a subtool (use -t list to list subtools)\n"
+" -d MODE enable debugging (use '-d list' to list modes)\n"
+" -t TOOL run a subtool (use '-t list' to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
-" -w FLAG adjust warnings (use -w list to list warnings)\n",
+" -w FLAG adjust warnings (use '-w list' to list warnings)\n",
kNinjaVersion, config.parallelism);
}
@@ -491,7 +494,7 @@ int NinjaMain::ToolDeps(const Options* options, int argc, char** argv) {
TimeStamp mtime = disk_interface.Stat((*it)->path(), &err);
if (mtime == -1)
Error("%s", err.c_str()); // Log and ignore Stat() errors;
- printf("%s: #deps %d, deps mtime %d (%s)\n",
+ printf("%s: #deps %d, deps mtime %" PRId64 " (%s)\n",
(*it)->path().c_str(), deps->node_count, deps->mtime,
(!mtime || mtime > deps->mtime ? "STALE":"VALID"));
for (int i = 0; i < deps->node_count; ++i)
@@ -659,7 +662,65 @@ void EncodeJSONString(const char *str) {
}
}
-int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, char* argv[]) {
+enum EvaluateCommandMode {
+ ECM_NORMAL,
+ ECM_EXPAND_RSPFILE
+};
+string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) {
+ string command = edge->EvaluateCommand();
+ if (mode == ECM_NORMAL)
+ return command;
+
+ string rspfile = edge->GetUnescapedRspfile();
+ if (rspfile.empty())
+ return command;
+
+ size_t index = command.find(rspfile);
+ if (index == 0 || index == string::npos || command[index - 1] != '@')
+ return command;
+
+ string rspfile_content = edge->GetBinding("rspfile_content");
+ size_t newline_index = 0;
+ while ((newline_index = rspfile_content.find('\n', newline_index)) !=
+ string::npos) {
+ rspfile_content.replace(newline_index, 1, 1, ' ');
+ ++newline_index;
+ }
+ command.replace(index - 1, rspfile.length() + 1, rspfile_content);
+ return command;
+}
+
+int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
+ char* argv[]) {
+ // The compdb tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "compdb".
+ argc++;
+ argv--;
+
+ EvaluateCommandMode eval_mode = ECM_NORMAL;
+
+ optind = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, const_cast<char*>("hx"))) != -1) {
+ switch(opt) {
+ case 'x':
+ eval_mode = ECM_EXPAND_RSPFILE;
+ break;
+
+ case 'h':
+ default:
+ printf(
+ "usage: ninja -t compdb [options] [rules]\n"
+ "\n"
+ "options:\n"
+ " -x expand @rspfile style response file invocations\n"
+ );
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
bool first = true;
vector<char> cwd;
@@ -685,9 +746,11 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, char* a
printf("\n {\n \"directory\": \"");
EncodeJSONString(&cwd[0]);
printf("\",\n \"command\": \"");
- EncodeJSONString((*e)->EvaluateCommand().c_str());
+ EncodeJSONString(EvaluateCommandWithRspfile(*e, eval_mode).c_str());
printf("\",\n \"file\": \"");
EncodeJSONString((*e)->inputs_[0]->path().c_str());
+ printf("\",\n \"output\": \"");
+ EncodeJSONString((*e)->outputs_[0]->path().c_str());
printf("\"\n }");
first = false;
@@ -845,7 +908,8 @@ bool DebugEnable(const string& name) {
bool WarningEnable(const string& name, Options* options) {
if (name == "list") {
printf("warning flags:\n"
-" dupbuild={err,warn} multiple build lines for one target\n");
+" dupbuild={err,warn} multiple build lines for one target\n"
+" phonycycle={err,warn} phony build statement references itself\n");
return false;
} else if (name == "dupbuild=err") {
options->dupe_edges_should_err = true;
@@ -853,9 +917,16 @@ bool WarningEnable(const string& name, Options* options) {
} else if (name == "dupbuild=warn") {
options->dupe_edges_should_err = false;
return true;
+ } else if (name == "phonycycle=err") {
+ options->phony_cycle_should_err = true;
+ return true;
+ } else if (name == "phonycycle=warn") {
+ options->phony_cycle_should_err = false;
+ return true;
} else {
const char* suggestion =
- SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn", NULL);
+ SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
+ "phonycycle=err", "phonycycle=warn", NULL);
if (suggestion) {
Error("unknown warning flag '%s', did you mean '%s'?",
name.c_str(), suggestion);
@@ -1107,17 +1178,20 @@ int ReadFlags(int* argc, char*** argv,
return -1;
}
-int real_main(int argc, char** argv) {
+NORETURN void real_main(int argc, char** argv) {
+ // Use exit() instead of return in this function to avoid potentially
+ // expensive cleanup when destructing NinjaMain.
BuildConfig config;
Options options = {};
options.input_file = "build.ninja";
+ options.dupe_edges_should_err = true;
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
const char* ninja_command = argv[0];
int exit_code = ReadFlags(&argc, &argv, &options, &config);
if (exit_code >= 0)
- return exit_code;
+ exit(exit_code);
if (options.working_dir) {
// The formatting of this string, complete with funny quotes, is
@@ -1136,7 +1210,7 @@ int real_main(int argc, char** argv) {
// None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed
// by other tools.
NinjaMain ninja(ninja_command, config);
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, argv));
}
// Limit number of rebuilds, to prevent infinite loops.
@@ -1144,50 +1218,54 @@ int real_main(int argc, char** argv) {
for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
NinjaMain ninja(ninja_command, config);
- ManifestParser parser(&ninja.state_, &ninja.disk_interface_,
- options.dupe_edges_should_err
- ? kDupeEdgeActionError
- : kDupeEdgeActionWarn);
+ ManifestParserOptions parser_opts;
+ if (options.dupe_edges_should_err) {
+ parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
+ }
+ if (options.phony_cycle_should_err) {
+ parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
+ }
+ ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts);
string err;
if (!parser.Load(options.input_file, &err)) {
Error("%s", err.c_str());
- return 1;
+ exit(1);
}
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, argv));
if (!ninja.EnsureBuildDirExists())
- return 1;
+ exit(1);
if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog())
- return 1;
+ exit(1);
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
- return (ninja.*options.tool->func)(&options, argc, argv);
+ exit((ninja.*options.tool->func)(&options, argc, 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;
+ exit(0);
// Start the build over with the new manifest.
continue;
} else if (!err.empty()) {
Error("rebuilding '%s': %s", options.input_file, err.c_str());
- return 1;
+ exit(1);
}
int result = ninja.RunBuild(argc, argv);
if (g_metrics)
ninja.DumpMetrics();
- return result;
+ exit(result);
}
Error("manifest '%s' still dirty after %d tries\n",
options.input_file, kCycleLimit);
- return 1;
+ exit(1);
}
} // anonymous namespace
@@ -1200,7 +1278,7 @@ int main(int argc, char** argv) {
__try {
// Running inside __try ... __except suppresses any Windows error
// dialogs for errors such as bad_alloc.
- return real_main(argc, argv);
+ real_main(argc, argv);
}
__except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
// Common error situations return exitCode=1. 2 was chosen to
@@ -1208,6 +1286,6 @@ int main(int argc, char** argv) {
return 2;
}
#else
- return real_main(argc, argv);
+ real_main(argc, argv);
#endif
}
diff --git a/src/state.h b/src/state.h
index 54e9dc5..6fe886c 100644
--- a/src/state.h
+++ b/src/state.h
@@ -33,7 +33,7 @@ struct Rule;
/// Pools are scoped to a State. Edges within a State will share Pools. A Pool
/// will keep a count of the total 'weight' of the currently scheduled edges. If
/// a Plan attempts to schedule an Edge which would cause the total weight to
-/// exceed the depth of the Pool, the Pool will enque the Edge instead of
+/// exceed the depth of the Pool, the Pool will enqueue the Edge instead of
/// allowing the Plan to schedule it. The Pool will relinquish queued Edges when
/// the total scheduled weight diminishes enough (i.e. when a scheduled edge
/// completes).
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 001fdf1..fc5543e 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -55,21 +55,25 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
SetCloseOnExec(fd_);
posix_spawn_file_actions_t action;
- if (posix_spawn_file_actions_init(&action) != 0)
- Fatal("posix_spawn_file_actions_init: %s", strerror(errno));
+ int err = posix_spawn_file_actions_init(&action);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_init: %s", strerror(err));
- if (posix_spawn_file_actions_addclose(&action, output_pipe[0]) != 0)
- Fatal("posix_spawn_file_actions_addclose: %s", strerror(errno));
+ err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
posix_spawnattr_t attr;
- if (posix_spawnattr_init(&attr) != 0)
- Fatal("posix_spawnattr_init: %s", strerror(errno));
+ err = posix_spawnattr_init(&attr);
+ if (err != 0)
+ Fatal("posix_spawnattr_init: %s", strerror(err));
short flags = 0;
flags |= POSIX_SPAWN_SETSIGMASK;
- if (posix_spawnattr_setsigmask(&attr, &set->old_mask_) != 0)
- Fatal("posix_spawnattr_setsigmask: %s", strerror(errno));
+ err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
+ if (err != 0)
+ Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
// Signals which are set to be caught in the calling process image are set to
// default action in the new process image, so no explicit
// POSIX_SPAWN_SETSIGDEF parameter is needed.
@@ -80,17 +84,21 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
// No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
// Open /dev/null over stdin.
- if (posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
- 0) != 0) {
- Fatal("posix_spawn_file_actions_addopen: %s", strerror(errno));
+ err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
+ 0);
+ if (err != 0) {
+ Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
}
- if (posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1) != 0)
- Fatal("posix_spawn_file_actions_adddup2: %s", strerror(errno));
- if (posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2) != 0)
- Fatal("posix_spawn_file_actions_adddup2: %s", strerror(errno));
- if (posix_spawn_file_actions_addclose(&action, output_pipe[1]) != 0)
- Fatal("posix_spawn_file_actions_addclose: %s", strerror(errno));
+ err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
+ err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
+ err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
// In the console case, output_pipe is still inherited by the child and
// closed when the subprocess finishes, which then notifies ninja.
}
@@ -98,18 +106,22 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
flags |= POSIX_SPAWN_USEVFORK;
#endif
- if (posix_spawnattr_setflags(&attr, flags) != 0)
- Fatal("posix_spawnattr_setflags: %s", strerror(errno));
+ err = posix_spawnattr_setflags(&attr, flags);
+ if (err != 0)
+ Fatal("posix_spawnattr_setflags: %s", strerror(err));
const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
- if (posix_spawn(&pid_, "/bin/sh", &action, &attr,
- const_cast<char**>(spawned_args), environ) != 0)
- Fatal("posix_spawn: %s", strerror(errno));
-
- if (posix_spawnattr_destroy(&attr) != 0)
- Fatal("posix_spawnattr_destroy: %s", strerror(errno));
- if (posix_spawn_file_actions_destroy(&action) != 0)
- Fatal("posix_spawn_file_actions_destroy: %s", strerror(errno));
+ err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
+ const_cast<char**>(spawned_args), environ);
+ if (err != 0)
+ Fatal("posix_spawn: %s", strerror(err));
+
+ err = posix_spawnattr_destroy(&attr);
+ if (err != 0)
+ Fatal("posix_spawnattr_destroy: %s", strerror(err));
+ err = posix_spawn_file_actions_destroy(&action);
+ if (err != 0)
+ Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
close(output_pipe[1]);
return true;
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index 4bab719..5982b06 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -59,8 +59,8 @@ HANDLE Subprocess::SetupPipe(HANDLE ioport) {
}
// Get the write end of the pipe as a handle inheritable across processes.
- HANDLE output_write_handle = CreateFile(pipe_name, GENERIC_WRITE, 0,
- NULL, OPEN_EXISTING, 0, NULL);
+ HANDLE output_write_handle =
+ CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE output_write_child;
if (!DuplicateHandle(GetCurrentProcess(), output_write_handle,
GetCurrentProcess(), &output_write_child,
@@ -80,9 +80,10 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = TRUE;
// Must be inheritable so subprocesses can dup to children.
- HANDLE nul = CreateFile("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
+ HANDLE nul =
+ CreateFileA("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ &security_attributes, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE)
Fatal("couldn't open nul");
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 0a8c206..6e487db 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -182,7 +182,7 @@ TEST_F(SubprocessTest, SetWithMulti) {
"cmd /c echo hi",
"cmd /c time /t",
#else
- "whoami",
+ "id -u",
"pwd",
#endif
};
diff --git a/src/test.cc b/src/test.cc
index 51882f0..a9816bc 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -95,8 +95,9 @@ Node* StateTestWithBuiltinRules::GetNode(const string& path) {
return state_.GetNode(path, 0);
}
-void AssertParse(State* state, const char* input) {
- ManifestParser parser(state, NULL, kDupeEdgeActionWarn);
+void AssertParse(State* state, const char* input,
+ ManifestParserOptions opts) {
+ ManifestParser parser(state, NULL, opts);
string err;
EXPECT_TRUE(parser.ParseTest(input, &err));
ASSERT_EQ("", err);
diff --git a/src/test.h b/src/test.h
index 02ed929..6af17b3 100644
--- a/src/test.h
+++ b/src/test.h
@@ -16,6 +16,7 @@
#define NINJA_TEST_H_
#include "disk_interface.h"
+#include "manifest_parser.h"
#include "state.h"
#include "util.h"
@@ -103,7 +104,7 @@ extern testing::Test* g_current_test;
} \
}
-// Support utilites for tests.
+// Support utilities for tests.
struct Node;
@@ -122,7 +123,8 @@ struct StateTestWithBuiltinRules : public testing::Test {
State state_;
};
-void AssertParse(State* state, const char* input);
+void AssertParse(State* state, const char* input,
+ ManifestParserOptions = ManifestParserOptions());
void AssertHash(const char* expected, uint64_t actual);
void VerifyGraph(const State& state);
diff --git a/src/timestamp.h b/src/timestamp.h
index cee7ba8..6a7ccd0 100644
--- a/src/timestamp.h
+++ b/src/timestamp.h
@@ -15,10 +15,19 @@
#ifndef NINJA_TIMESTAMP_H_
#define NINJA_TIMESTAMP_H_
+#ifdef _WIN32
+#include "win32port.h"
+#else
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+#endif
+
// When considering file modification times we only care to compare
// them against one another -- we never convert them to an absolute
-// real time. On POSIX we use time_t (seconds since epoch) and on
-// Windows we use a different value. Both fit in an int.
-typedef int TimeStamp;
+// real time. On POSIX we use timespec (seconds&nanoseconds since epoch)
+// and on Windows we use a different value. Both fit in an int64.
+typedef int64_t TimeStamp;
#endif // NINJA_TIMESTAMP_H_
diff --git a/src/util.cc b/src/util.cc
index ae94d34..760bc23 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -197,7 +197,7 @@ bool CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits,
case '\\':
bits |= bits_mask;
*c = '/';
- // Intentional fallthrough.
+ NINJA_FALLTHROUGH;
case '/':
bits_mask <<= 1;
}
@@ -318,13 +318,8 @@ int ReadFile(const string& path, string* contents, string* err) {
// This makes a ninja run on a set of 1500 manifest files about 4% faster
// than using the generic fopen code below.
err->clear();
- HANDLE f = ::CreateFile(path.c_str(),
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- FILE_FLAG_SEQUENTIAL_SCAN,
- NULL);
+ HANDLE f = ::CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (f == INVALID_HANDLE_VALUE) {
err->assign(GetLastErrorString());
return -ENOENT;
diff --git a/src/util.h b/src/util.h
index 4ee41a5..1b4227c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -34,6 +34,20 @@ using namespace std;
/// Log a fatal message and exit.
NORETURN void Fatal(const char* msg, ...);
+// Have a generic fall-through for different versions of C/C++.
+#if defined(__cplusplus) && __cplusplus >= 201703L
+#define NINJA_FALLTHROUGH [[fallthrough]]
+#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__clang__)
+#define NINJA_FALLTHROUGH [[clang::fallthrough]]
+#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__GNUC__) && \
+ __GNUC__ >= 7
+#define NINJA_FALLTHROUGH [[gnu::fallthrough]]
+#elif defined(__GNUC__) && __GNUC__ >= 7 // gcc 7
+#define NINJA_FALLTHROUGH __attribute__ ((fallthrough))
+#else // C++11 on gcc 6, and all other cases
+#define NINJA_FALLTHROUGH
+#endif
+
/// Log a warning message.
void Warning(const char* msg, ...);
diff --git a/src/version.cc b/src/version.cc
index 3ee95b2..1b6cfac 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,7 @@
#include "util.h"
-const char* kNinjaVersion = "1.8.1.git";
+const char* kNinjaVersion = "1.8.2.git";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');
diff --git a/src/win32port.h b/src/win32port.h
index ce3c949..e542536 100644
--- a/src/win32port.h
+++ b/src/win32port.h
@@ -15,6 +15,13 @@
#ifndef NINJA_WIN32PORT_H_
#define NINJA_WIN32PORT_H_
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+#endif
+
typedef signed short int16_t;
typedef unsigned short uint16_t;
/// A 64-bit integer type
@@ -23,6 +30,7 @@ typedef unsigned long long uint64_t;
// printf format specifier for uint64_t, from C99.
#ifndef PRIu64
+#define PRId64 "I64d"
#define PRIu64 "I64u"
#define PRIx64 "I64x"
#endif