diff options
author | Nico Weber <nicolasweber@gmx.de> | 2016-11-07 18:34:46 (GMT) |
---|---|---|
committer | Nico Weber <nicolasweber@gmx.de> | 2016-11-07 18:34:46 (GMT) |
commit | 717b7b4a31db6027207588c0fb89c3ead384747b (patch) | |
tree | f95fbdb0340abd28dba6ad67aa19d6e12083450a | |
parent | b49b0fc01bb052b6ac856b1e72be9391e962398e (diff) | |
parent | b0cce09f563d0942fcf1c1256db679cebcbd6bea (diff) | |
download | Ninja-1.7.2.zip Ninja-1.7.2.tar.gz Ninja-1.7.2.tar.bz2 |
v1.7.2v1.7.2
-rwxr-xr-x | configure.py | 2 | ||||
-rwxr-xr-x | misc/measure.py | 12 | ||||
-rw-r--r-- | misc/write_fake_manifests.py | 89 | ||||
-rwxr-xr-x | src/browse.py | 16 | ||||
-rw-r--r-- | src/build.cc | 36 | ||||
-rw-r--r-- | src/build.h | 20 | ||||
-rw-r--r-- | src/build_test.cc | 14 | ||||
-rw-r--r-- | src/graph_test.cc | 39 | ||||
-rw-r--r-- | src/manifest_parser.cc | 10 | ||||
-rw-r--r-- | src/manifest_parser_test.cc | 5 | ||||
-rw-r--r-- | src/state.cc | 9 | ||||
-rw-r--r-- | src/state.h | 4 | ||||
-rw-r--r-- | src/subprocess.h | 8 | ||||
-rw-r--r-- | src/subprocess_test.cc | 4 | ||||
-rw-r--r-- | src/version.cc | 2 |
15 files changed, 201 insertions, 69 deletions
diff --git a/configure.py b/configure.py index 84218b9..9ec368f 100755 --- a/configure.py +++ b/configure.py @@ -98,7 +98,7 @@ class Platform(object): return self._platform in ('freebsd', 'openbsd', 'bitrig') def supports_ppoll(self): - return self._platform in ('linux', 'openbsd', 'bitrig') + return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig') def supports_ninja_browse(self): return (not self.is_windows() diff --git a/misc/measure.py b/misc/measure.py index 1323fc6..8ce95e6 100755 --- a/misc/measure.py +++ b/misc/measure.py @@ -17,6 +17,8 @@ """measure the runtime of a command by repeatedly running it. """ +from __future__ import print_function + import time import subprocess import sys @@ -24,7 +26,7 @@ import sys devnull = open('/dev/null', 'w') def run(cmd, repeat=10): - print 'sampling:', + print('sampling:', end=' ') sys.stdout.flush() samples = [] @@ -33,10 +35,10 @@ def run(cmd, repeat=10): subprocess.call(cmd, stdout=devnull, stderr=devnull) end = time.time() dt = (end - start) * 1000 - print '%dms' % int(dt), + print('%dms' % int(dt), end=' ') sys.stdout.flush() samples.append(dt) - print + print() # We're interested in the 'pure' runtime of the code, which is # conceptually the smallest time we'd see if we ran it enough times @@ -45,10 +47,10 @@ def run(cmd, repeat=10): # Also print how varied the outputs were in an attempt to make it # more obvious if something has gone terribly wrong. err = sum(s - best for s in samples) / float(len(samples)) - print 'estimate: %dms (mean err %.1fms)' % (best, err) + print('estimate: %dms (mean err %.1fms)' % (best, err)) if __name__ == '__main__': if len(sys.argv) < 2: - print 'usage: measure.py command args...' + print('usage: measure.py command args...') sys.exit(1) run(cmd=sys.argv[1:]) diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py index ca49535..b3594de 100644 --- a/misc/write_fake_manifests.py +++ b/misc/write_fake_manifests.py @@ -50,9 +50,10 @@ def moar(avg_options, p_suffix): class GenRandom(object): - def __init__(self): + def __init__(self, src_dir): self.seen_names = set([None]) self.seen_defines = set([None]) + self.src_dir = src_dir def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1): s = None @@ -76,7 +77,7 @@ class GenRandom(object): def src_obj_pairs(self, path, name): num_sources = paretoint(55, alpha=2) + 1 - return [(os.path.join('..', '..', path, s + '.cc'), + return [(os.path.join(self.src_dir, path, s + '.cc'), os.path.join('obj', path, '%s.%s.o' % (name, s))) for s in self._n_unique_strings(num_sources)] @@ -103,12 +104,8 @@ class Target(object): self.kind = kind self.has_compile_depends = random.random() < 0.4 - @property - def includes(self): - return ['-I' + dep.dir_path for dep in self.deps] - -def write_target_ninja(ninja, target): +def write_target_ninja(ninja, target, src_dir): compile_depends = None if target.has_compile_depends: compile_depends = os.path.join( @@ -117,8 +114,7 @@ def write_target_ninja(ninja, target): ninja.newline() ninja.variable('defines', target.defines) - if target.deps: - ninja.variable('includes', target.includes) + ninja.variable('includes', '-I' + src_dir) ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions']) ninja.newline() @@ -129,17 +125,63 @@ def write_target_ninja(ninja, target): deps = [dep.output for dep in target.deps] libs = [dep.output for dep in target.deps if dep.kind == LIB] if target.kind == EXE: - ninja.variable('ldflags', '-Wl,pie') ninja.variable('libs', libs) + if sys.platform == "darwin": + ninja.variable('ldflags', '-Wl,-pie') link = { LIB: 'alink', EXE: 'link'}[target.kind] ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs], implicit=deps) +def write_sources(target, root_dir): + need_main = target.kind == EXE + + includes = [] + + # Include siblings. + for cc_filename, _ in target.src_obj_pairs: + h_filename = os.path.basename(os.path.splitext(cc_filename)[0] + '.h') + includes.append(h_filename) + + # Include deps. + for dep in target.deps: + for cc_filename, _ in dep.src_obj_pairs: + h_filename = os.path.basename( + os.path.splitext(cc_filename)[0] + '.h') + includes.append("%s/%s" % (dep.dir_path, h_filename)) + + for cc_filename, _ in target.src_obj_pairs: + cc_path = os.path.join(root_dir, cc_filename) + h_path = os.path.splitext(cc_path)[0] + '.h' + namespace = os.path.basename(target.dir_path) + class_ = os.path.splitext(os.path.basename(cc_filename))[0] + try: + os.makedirs(os.path.dirname(cc_path)) + except OSError: + pass + + with open(h_path, 'w') as f: + f.write('namespace %s { struct %s { %s(); }; }' % (namespace, + class_, class_)) + with open(cc_path, 'w') as f: + for include in includes: + f.write('#include "%s"\n' % include) + f.write('\n') + f.write('namespace %s { %s::%s() {} }' % (namespace, + class_, class_)) + + if need_main: + f.write('int main(int argc, char **argv) {}\n') + need_main = False + def write_master_ninja(master_ninja, targets): """Writes master build.ninja file, referencing all given subninjas.""" master_ninja.variable('cxx', 'c++') master_ninja.variable('ld', '$cxx') + if sys.platform == 'darwin': + master_ninja.variable('alink', 'libtool -static') + else: + master_ninja.variable('alink', 'ar rcs') master_ninja.newline() master_ninja.pool('link_pool', depth=4) @@ -148,8 +190,8 @@ def write_master_ninja(master_ninja, targets): master_ninja.rule('cxx', description='CXX $out', command='$cxx -MMD -MF $out.d $defines $includes $cflags -c $in -o $out', depfile='$out.d', deps='gcc') - master_ninja.rule('alink', description='LIBTOOL-STATIC $out', - command='rm -f $out && libtool -static -o $out $in') + master_ninja.rule('alink', description='ARCHIVE $out', + command='rm -f $out && $alink -o $out $in') master_ninja.rule('link', description='LINK $out', pool='link_pool', command='$ld $ldflags -o $out $in $libs') master_ninja.rule('stamp', description='STAMP $out', command='touch $out') @@ -181,9 +223,8 @@ def FileWriter(path): f.close() -def random_targets(): - num_targets = 1500 - gen = GenRandom() +def random_targets(num_targets, src_dir): + gen = GenRandom(src_dir) # N-1 static libraries, and 1 executable depending on all of them. targets = [Target(gen, LIB) for i in xrange(num_targets - 1)] @@ -199,16 +240,28 @@ def random_targets(): def main(): parser = argparse.ArgumentParser() + parser.add_argument('-s', '--sources', nargs="?", const="src", + help='write sources to directory (relative to output directory)') + parser.add_argument('-t', '--targets', type=int, default=1500, + help='number of targets (default: 1500)') + parser.add_argument('-S', '--seed', type=int, help='random seed', + default=12345) parser.add_argument('outdir', help='output directory') args = parser.parse_args() root_dir = args.outdir - random.seed(12345) + random.seed(args.seed) - targets = random_targets() + do_write_sources = args.sources is not None + src_dir = args.sources if do_write_sources else "src" + + targets = random_targets(args.targets, src_dir) for target in targets: with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n: - write_target_ninja(n, target) + write_target_ninja(n, target, src_dir) + + if do_write_sources: + write_sources(target, root_dir) with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja: master_ninja.width = 120 diff --git a/src/browse.py b/src/browse.py index 32792f3..4b4faa8 100755 --- a/src/browse.py +++ b/src/browse.py @@ -27,6 +27,7 @@ try: except ImportError: import BaseHTTPServer as httpserver import argparse +import cgi import os import socket import subprocess @@ -58,6 +59,9 @@ def match_strip(line, prefix): return (False, line) return (True, line[len(prefix):]) +def html_escape(text): + return cgi.escape(text, quote=True) + def parse(text): lines = iter(text.split('\n')) @@ -124,19 +128,19 @@ tt { ''' + body def generate_html(node): - document = ['<h1><tt>%s</tt></h1>' % node.target] + document = ['<h1><tt>%s</tt></h1>' % html_escape(node.target)] if node.inputs: document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' % - node.rule) + html_escape(node.rule)) if len(node.inputs) > 0: document.append('<div class=filelist>') for input, type in sorted(node.inputs): extra = '' if type: - extra = ' (%s)' % type + extra = ' (%s)' % html_escape(type) document.append('<tt><a href="?%s">%s</a>%s</tt><br>' % - (input, input, extra)) + (html_escape(input), html_escape(input), extra)) document.append('</div>') if node.outputs: @@ -144,7 +148,7 @@ def generate_html(node): document.append('<div class=filelist>') for output in sorted(node.outputs): document.append('<tt><a href="?%s">%s</a></tt><br>' % - (output, output)) + (html_escape(output), html_escape(output))) document.append('</div>') return '\n'.join(document) @@ -177,7 +181,7 @@ class RequestHandler(httpserver.BaseHTTPRequestHandler): page_body = generate_html(parse(ninja_output.strip())) else: # Relay ninja's error message. - page_body = '<h1><tt>%s</tt></h1>' % ninja_error + page_body = '<h1><tt>%s</tt></h1>' % html_escape(ninja_error) self.send_response(200) self.end_headers() diff --git a/src/build.cc b/src/build.cc index 8c0fbf8..64710dd 100644 --- a/src/build.cc +++ b/src/build.cc @@ -97,7 +97,7 @@ void BuildStatus::BuildEdgeStarted(Edge* edge) { ++started_edges_; if (edge->use_console() || printer_.is_smart_terminal()) - PrintStatus(edge); + PrintStatus(edge, kEdgeStarted); if (edge->use_console()) printer_.SetConsoleLocked(true); @@ -109,6 +109,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, int* start_time, int* end_time) { int64_t now = GetTimeMillis(); + ++finished_edges_; RunningEdgeMap::iterator i = running_edges_.find(edge); @@ -123,7 +124,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, return; if (!edge->use_console()) - PrintStatus(edge); + PrintStatus(edge, kEdgeFinished); // Print the command that is spewing before printing its output. if (!success) { @@ -158,13 +159,18 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, } } +void BuildStatus::BuildStarted() { + overall_rate_.Restart(); + current_rate_.Restart(); +} + void BuildStatus::BuildFinished() { printer_.SetConsoleLocked(false); printer_.PrintOnNewLine(""); } string BuildStatus::FormatProgressStatus( - const char* progress_status_format) const { + const char* progress_status_format, EdgeStatus status) const { string out; char buf[32]; int percent; @@ -189,10 +195,15 @@ string BuildStatus::FormatProgressStatus( break; // Running edges. - case 'r': - snprintf(buf, sizeof(buf), "%d", started_edges_ - finished_edges_); + case 'r': { + int running_edges = started_edges_ - finished_edges_; + // count the edge that just finished as a running edge + if (status == kEdgeFinished) + running_edges++; + snprintf(buf, sizeof(buf), "%d", running_edges); out += buf; break; + } // Unstarted edges. case 'u': @@ -209,14 +220,14 @@ string BuildStatus::FormatProgressStatus( // Overall finished edges per second. case 'o': overall_rate_.UpdateRate(finished_edges_); - snprinfRate(overall_rate_.rate(), buf, "%.1f"); + SnprintfRate(overall_rate_.rate(), buf, "%.1f"); out += buf; break; // Current rate, average over the last '-j' jobs. case 'c': current_rate_.UpdateRate(finished_edges_); - snprinfRate(current_rate_.rate(), buf, "%.1f"); + SnprintfRate(current_rate_.rate(), buf, "%.1f"); out += buf; break; @@ -246,7 +257,7 @@ string BuildStatus::FormatProgressStatus( return out; } -void BuildStatus::PrintStatus(Edge* edge) { +void BuildStatus::PrintStatus(Edge* edge, EdgeStatus status) { if (config_.verbosity == BuildConfig::QUIET) return; @@ -256,11 +267,7 @@ void BuildStatus::PrintStatus(Edge* edge) { if (to_print.empty() || force_full_command) to_print = edge->GetBinding("command"); - if (finished_edges_ == 0) { - overall_rate_.Restart(); - current_rate_.Restart(); - } - to_print = FormatProgressStatus(progress_status_format_) + to_print; + to_print = FormatProgressStatus(progress_status_format_, status) + to_print; printer_.Print(to_print, force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE); @@ -643,6 +650,9 @@ bool Builder::Build(string* err) { command_runner_.reset(new RealCommandRunner(config_)); } + // We are about to start the build process. + status_->BuildStarted(); + // This main loop runs the entire build process. // It is structured like this: // First, we attempt to start as many commands as allowed by the diff --git a/src/build.h b/src/build.h index 51589ef..66ce607 100644 --- a/src/build.h +++ b/src/build.h @@ -200,16 +200,24 @@ struct BuildStatus { void BuildEdgeStarted(Edge* edge); void BuildEdgeFinished(Edge* edge, bool success, const string& output, int* start_time, int* end_time); + void BuildStarted(); void BuildFinished(); + enum EdgeStatus { + kEdgeStarted, + kEdgeFinished, + }; + /// Format the progress status string by replacing the placeholders. /// See the user manual for more information about the available /// placeholders. /// @param progress_status_format The format of the progress status. - string FormatProgressStatus(const char* progress_status_format) const; + /// @param status The status of the edge. + string FormatProgressStatus(const char* progress_status_format, + EdgeStatus status) const; private: - void PrintStatus(Edge* edge); + void PrintStatus(Edge* edge, EdgeStatus status); const BuildConfig& config_; @@ -229,9 +237,11 @@ struct BuildStatus { const char* progress_status_format_; template<size_t S> - void snprinfRate(double rate, char(&buf)[S], const char* format) const { - if (rate == -1) snprintf(buf, S, "?"); - else snprintf(buf, S, format, rate); + void SnprintfRate(double rate, char(&buf)[S], const char* format) const { + if (rate == -1) + snprintf(buf, S, "?"); + else + snprintf(buf, S, format, rate); } struct RateInfo { diff --git a/src/build_test.cc b/src/build_test.cc index 55d093e..640e1b0 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1286,7 +1286,8 @@ TEST_F(BuildWithLogTest, RestatTest) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - EXPECT_EQ("[3/3]", builder_.status_->FormatProgressStatus("[%s/%t]")); + EXPECT_EQ("[3/3]", builder_.status_->FormatProgressStatus("[%s/%t]", + BuildStatus::kEdgeStarted)); command_runner_.commands_ran_.clear(); state_.Reset(); @@ -1724,9 +1725,18 @@ TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) { ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } +TEST_F(BuildTest, StatusFormatElapsed) { + status_.BuildStarted(); + // Before any task is done, the elapsed time must be zero. + EXPECT_EQ("[%/e0.000]", + status_.FormatProgressStatus("[%%/e%e]", + BuildStatus::kEdgeStarted)); +} + TEST_F(BuildTest, StatusFormatReplacePlaceholder) { EXPECT_EQ("[%/s0/t0/r0/u0/f0]", - status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]")); + status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", + BuildStatus::kEdgeStarted)); } TEST_F(BuildTest, FailedDepsParse) { diff --git a/src/graph_test.cc b/src/graph_test.cc index 723e8ea..be08b19 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -149,6 +149,45 @@ TEST_F(GraphTest, ImplicitOutputOutOfDate) { EXPECT_TRUE(GetNode("out.imp")->dirty()); } +TEST_F(GraphTest, ImplicitOutputOnlyParse) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build | out.imp: cat in\n")); + + Edge* edge = GetNode("out.imp")->in_edge(); + EXPECT_EQ(1, edge->outputs_.size()); + EXPECT_EQ("out.imp", edge->outputs_[0]->path()); + EXPECT_EQ(1, edge->implicit_outs_); + EXPECT_EQ(edge, GetNode("out.imp")->in_edge()); +} + +TEST_F(GraphTest, ImplicitOutputOnlyMissing) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build | out.imp: cat in\n")); + fs_.Create("in", ""); + + Edge* edge = GetNode("out.imp")->in_edge(); + string err; + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + EXPECT_TRUE(GetNode("out.imp")->dirty()); +} + +TEST_F(GraphTest, ImplicitOutputOnlyOutOfDate) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build | out.imp: cat in\n")); + fs_.Create("out.imp", ""); + fs_.Tick(); + fs_.Create("in", ""); + + Edge* edge = GetNode("out.imp")->in_edge(); + string err; + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + EXPECT_TRUE(GetNode("out.imp")->dirty()); +} + TEST_F(GraphTest, PathWithCurrentDirectory) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index a4f489e..d6dcf22 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -236,16 +236,13 @@ bool ManifestParser::ParseEdge(string* err) { EvalString out; if (!lexer_.ReadPath(&out, err)) return false; - if (out.empty()) - return lexer_.Error("expected path", err); - - do { + while (!out.empty()) { outs.push_back(out); out.Clear(); if (!lexer_.ReadPath(&out, err)) return false; - } while (!out.empty()); + } } // Add all implicit outs, counting how many as we go. @@ -262,6 +259,9 @@ bool ManifestParser::ParseEdge(string* err) { } } + if (outs.empty()) + return lexer_.Error("expected path", err); + if (!ExpectToken(Lexer::COLON, err)) return false; diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 1312d26..3c82dc5 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -976,13 +976,10 @@ TEST_F(ParserTest, ImplicitOutputDupes) { TEST_F(ParserTest, NoExplicitOutput) { ManifestParser parser(&state, NULL, kDupeEdgeActionWarn); string err; - EXPECT_FALSE(parser.ParseTest( + EXPECT_TRUE(parser.ParseTest( "rule cat\n" " command = cat $in > $out\n" "build | imp : cat bar\n", &err)); - ASSERT_EQ("input:3: expected path\n" - "build | imp : cat bar\n" - " ^ near here", err); } TEST_F(ParserTest, DefaultDefault) { diff --git a/src/state.cc b/src/state.cc index a70f211..d539e7b 100644 --- a/src/state.cc +++ b/src/state.cc @@ -159,11 +159,12 @@ bool State::AddDefault(StringPiece path, string* err) { return true; } -vector<Node*> State::RootNodes(string* err) { +vector<Node*> State::RootNodes(string* err) const { vector<Node*> root_nodes; // Search for nodes with no output. - for (vector<Edge*>::iterator e = edges_.begin(); e != edges_.end(); ++e) { - for (vector<Node*>::iterator out = (*e)->outputs_.begin(); + for (vector<Edge*>::const_iterator e = edges_.begin(); + e != edges_.end(); ++e) { + for (vector<Node*>::const_iterator out = (*e)->outputs_.begin(); out != (*e)->outputs_.end(); ++out) { if ((*out)->out_edges().empty()) root_nodes.push_back(*out); @@ -176,7 +177,7 @@ vector<Node*> State::RootNodes(string* err) { return root_nodes; } -vector<Node*> State::DefaultNodes(string* err) { +vector<Node*> State::DefaultNodes(string* err) const { return defaults_.empty() ? RootNodes(err) : defaults_; } diff --git a/src/state.h b/src/state.h index d7987ba..b530207 100644 --- a/src/state.h +++ b/src/state.h @@ -110,8 +110,8 @@ struct State { /// @return the root node(s) of the graph. (Root nodes have no output edges). /// @param error where to write the error message if somethings went wrong. - vector<Node*> RootNodes(string* error); - vector<Node*> DefaultNodes(string* error); + vector<Node*> RootNodes(string* error) const; + vector<Node*> DefaultNodes(string* error) const; /// Mapping of path -> Node. typedef ExternalStringHashMap<Node*>::Type Paths; diff --git a/src/subprocess.h b/src/subprocess.h index 51f40b2..b2d486c 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -26,6 +26,14 @@ using namespace std; #include <signal.h> #endif +// ppoll() exists on FreeBSD, but only on newer versions. +#ifdef __FreeBSD__ +# include <sys/param.h> +# if defined USE_PPOLL && __FreeBSD_version < 1002000 +# undef USE_PPOLL +# endif +#endif + #include "exit_status.h" /// Subprocess wraps a single async subprocess. It is entirely diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index ee16190..0a8c206 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -214,9 +214,7 @@ TEST_F(SubprocessTest, SetWithMulti) { } } -// OS X's process limit is less than 1025 by default -// (|sysctl kern.maxprocperuid| is 709 on 10.7 and 10.8 and less prior to that). -#if !defined(__APPLE__) && !defined(_WIN32) +#if defined(USE_PPOLL) TEST_F(SubprocessTest, SetWithLots) { // Arbitrary big number; needs to be over 1024 to confirm we're no longer // hostage to pselect. diff --git a/src/version.cc b/src/version.cc index a048a18..eafa082 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.7.1"; +const char* kNinjaVersion = "1.7.2"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.'); |