diff options
-rw-r--r-- | HACKING.md | 2 | ||||
-rw-r--r-- | appveyor.yml | 14 | ||||
-rwxr-xr-x | configure.py | 6 | ||||
-rw-r--r-- | misc/ninja-mode.el | 2 | ||||
-rw-r--r-- | misc/ninja.vim | 18 | ||||
-rw-r--r-- | misc/ninja_syntax.py | 4 | ||||
-rwxr-xr-x | misc/ninja_syntax_test.py | 4 | ||||
-rw-r--r-- | src/build.cc | 64 | ||||
-rw-r--r-- | src/build.h | 27 | ||||
-rw-r--r-- | src/build_log.cc | 9 | ||||
-rw-r--r-- | src/clean.cc | 23 | ||||
-rw-r--r-- | src/deps_log_test.cc | 4 | ||||
-rw-r--r-- | src/disk_interface.cc | 15 | ||||
-rw-r--r-- | src/disk_interface_test.cc | 11 | ||||
-rw-r--r-- | src/hash_map.h | 3 | ||||
-rw-r--r-- | src/includes_normalize-win32.cc | 6 | ||||
-rw-r--r-- | src/lexer.cc | 19 | ||||
-rw-r--r-- | src/lexer.in.cc | 19 | ||||
-rw-r--r-- | src/manifest_parser_test.cc | 18 | ||||
-rw-r--r-- | src/minidump-win32.cc | 6 | ||||
-rw-r--r-- | src/msvc_helper-win32.cc | 12 | ||||
-rw-r--r-- | src/msvc_helper_main-win32.cc | 2 | ||||
-rw-r--r-- | src/ninja.cc | 99 | ||||
-rw-r--r-- | src/state.h | 2 | ||||
-rw-r--r-- | src/subprocess-win32.cc | 11 | ||||
-rw-r--r-- | src/test.h | 2 | ||||
-rw-r--r-- | src/util.cc | 11 | ||||
-rw-r--r-- | src/util.h | 14 |
28 files changed, 309 insertions, 118 deletions
@@ -95,7 +95,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/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..c8e1a9d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,14 @@ +version: 1.0.{build} +image: Visual Studio 2017 +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 a443748..9e23a5a 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]) @@ -397,6 +397,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/src/build.cc b/src/build.cc index 61ef0e8..c24d6a9 100644 --- a/src/build.cc +++ b/src/build.cc @@ -16,6 +16,7 @@ #include <assert.h> #include <errno.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <functional> @@ -130,6 +131,18 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (!edge->use_console()) PrintStatus(edge, kEdgeFinished); + if (printer_.is_smart_terminal()) { + int oldest_start = INT_MAX; + Edge* oldest = NULL; + for (i = running_edges_.begin(); i != running_edges_.end(); i++) { + if (i->second < oldest_start) { + oldest_start = i->second; + oldest = i->first; + } + } + if (oldest) + PrintStatus(oldest, kEdgeRunning); + } // Print the command that is spewing before printing its output. if (!success) { @@ -318,18 +331,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 +368,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 +420,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 +443,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 +456,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 +484,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 +496,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..ac7f951 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: @@ -206,6 +222,7 @@ struct BuildStatus { enum EdgeStatus { kEdgeStarted, + kEdgeRunning, kEdgeFinished, }; diff --git a/src/build_log.cc b/src/build_log.cc index 648617c..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; diff --git a/src/clean.cc b/src/clean.cc index 1d6ba9e..4f31a03 100644 --- a/src/clean.cc +++ b/src/clean.cc @@ -180,15 +180,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_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 4b4c4c7..504c679 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -70,7 +70,7 @@ TimeStamp TimeStampFromFileTime(const FILETIME& filetime) { 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; @@ -112,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))); @@ -164,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); @@ -195,11 +205,12 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { 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(__BIONIC__) || (defined (__SVR4) && defined (__sun))) // 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; #else return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec; 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/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/manifest_parser_test.cc b/src/manifest_parser_test.cc index 39ed810..c91d8d1 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -523,7 +523,7 @@ TEST_F(ParserTest, Errors) { 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); } @@ -534,7 +534,7 @@ TEST_F(ParserTest, Errors) { 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); } @@ -636,7 +636,10 @@ TEST_F(ParserTest, Errors) { 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); } { @@ -672,7 +675,10 @@ TEST_F(ParserTest, Errors) { 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); } { @@ -767,7 +773,9 @@ TEST_F(ParserTest, Errors) { 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); } { 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 30f89c2..8108f21 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -154,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.) @@ -207,7 +207,7 @@ 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" @@ -662,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; @@ -688,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; @@ -1118,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 @@ -1147,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. @@ -1166,43 +1229,43 @@ int real_main(int argc, char** argv) { 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 @@ -1215,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 @@ -1223,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-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"); @@ -104,7 +104,7 @@ extern testing::Test* g_current_test; } \ } -// Support utilites for tests. +// Support utilities for tests. struct Node; 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; @@ -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, ...); |