Merge pull request #1534 from mathstuf/remove-depslog-restriction
manifest_parser: remove multi-output depslog restriction
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0cc68d6
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+end_of_line = lf
+
+[CMakeLists.txt]
+indent_style = tab
diff --git a/.github/workflows/release-ninja-binaries.yml b/.github/workflows/release-ninja-binaries.yml
new file mode 100644
index 0000000..c8dd9d3
--- /dev/null
+++ b/.github/workflows/release-ninja-binaries.yml
@@ -0,0 +1,73 @@
+name: Release Ninja Binaries
+
+on:
+ pull_request:
+ push:
+ release:
+ types: published
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macOS-latest, windows-latest]
+ include:
+ - os: ubuntu-latest
+ zip_name: ninja-linux
+ - os: macOS-latest
+ zip_name: ninja-mac
+ - os: windows-latest
+ zip_name: ninja-win
+
+ steps:
+ - uses: actions/checkout@v1
+
+ # Install OS specific dependencies
+ - name: Install Linux dependencies
+ if: matrix.os == 'ubuntu-latest'
+ run: sudo apt-get install re2c
+ - name: Install macOS dependencies
+ if: matrix.os == 'macOS-latest'
+ run: brew install re2c p7zip cmake
+ - name: Install Windows dependencies
+ if: matrix.os == 'windows-latest'
+ run: choco install re2c
+
+ - name: Build ninja
+ shell: bash
+ run: |
+ mkdir build && cd build
+ cmake -DCMAKE_BUILD_TYPE=Release ..
+ cmake --build . --parallel --config Release
+ ctest -vv
+
+ - name: Strip Linux binary
+ if: matrix.os == 'ubuntu-latest'
+ run: cd build && strip ninja
+
+ - name: Create ninja archive
+ shell: bash
+ env:
+ ZIP_NAME: ${{ matrix.zip_name }}
+ run: |
+ mkdir artifact
+ 7z a artifact/${ZIP_NAME}.zip $(find ./build -name ninja -or -name ninja.exe)
+
+ # Upload ninja binary archive as an artifact
+ - name: Upload artifact
+ uses: actions/upload-artifact@v1
+ with:
+ name: ninja-binary-archives
+ path: artifact
+
+ - name: Upload release asset
+ if: github.event.action == 'published'
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ github.event.release.upload_url }}
+ asset_path: ./artifact/${{ matrix.zip_name }}.zip
+ asset_name: ${{ matrix.zip_name }}.zip
+ asset_content_type: application/zip
diff --git a/.gitignore b/.gitignore
index 98fbb21..dca1129 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
*.exe
*.pdb
*.ilk
-TAGS
/build*/
/build.ninja
/ninja
@@ -18,8 +17,8 @@
/graph.png
/doc/manual.html
/doc/doxygen
-/gtest-1.6.0
*.patch
+.DS_Store
# Eclipse project files
.project
@@ -36,3 +35,6 @@
# Visual Studio Code project files
/.vscode/
/.ccls-cache/
+
+# Qt Creator project files
+/CMakeLists.txt.user
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2390732..de0fe1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,19 @@
cmake_minimum_required(VERSION 3.12)
project(ninja)
+if(CMAKE_BUILD_TYPE MATCHES "Release")
+ cmake_policy(SET CMP0069 NEW)
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT lto_supported OUTPUT error)
+
+ if(lto_supported)
+ message(STATUS "IPO / LTO enabled")
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+ else()
+ message(STATUS "IPO / LTO not supported: <${error}>")
+ endif()
+endif()
+
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /GR- /Zc:__cplusplus")
else()
@@ -65,7 +78,7 @@
#Fixes GetActiveProcessorCount on MinGW
if(MINGW)
-target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601)
+target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601 __USE_MINGW_ANSI_STDIO=1)
endif()
# Main executable is library plus main() function.
diff --git a/configure.py b/configure.py
index 1d6ee7d..7d8ce90 100755
--- a/configure.py
+++ b/configure.py
@@ -356,7 +356,7 @@
except:
pass
if platform.is_mingw():
- cflags += ['-D_WIN32_WINNT=0x0601']
+ cflags += ['-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1']
ldflags = ['-L$builddir']
if platform.uses_usr_local():
cflags.append('-I/usr/local/include')
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index e49d26d..8f42efb 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -283,6 +283,9 @@
`recompact`:: recompact the `.ninja_deps` file. _Available since Ninja 1.4._
+`restat`:: updates all recorded file modification timestamps in the `.ninja_log`
+file. _Available since Ninja 1.10._
+
`rules`:: output the list of all rules (eventually with their description
if they have one). It can be used to know which rule name to pass to
+ninja -t targets rule _name_+ or +ninja -t compdb+.
diff --git a/misc/output_test.py b/misc/output_test.py
index fb73d72..3fd9c32 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -18,12 +18,15 @@
if 'CLICOLOR_FORCE' in default_env:
del default_env['CLICOLOR_FORCE']
default_env['TERM'] = ''
+NINJA_PATH = os.path.abspath('./ninja')
def run(build_ninja, flags='', pipe=False, env=default_env):
- with tempfile.NamedTemporaryFile('w') as f:
- f.write(build_ninja)
- f.flush()
- ninja_cmd = './ninja {} -f {}'.format(flags, f.name)
+ with tempfile.TemporaryDirectory() as d:
+ os.chdir(d)
+ with open('build.ninja', 'w') as f:
+ f.write(build_ninja)
+ f.flush()
+ ninja_cmd = '{} {}'.format(NINJA_PATH, flags)
try:
if pipe:
output = subprocess.check_output([ninja_cmd], shell=True, env=env)
@@ -99,5 +102,10 @@
\x1b[31mred\x1b[0m
''')
+ def test_pr_1685(self):
+ # Running those tools without .ninja_deps and .ninja_log shouldn't fail.
+ self.assertEqual(run('', flags='-t recompact'), '')
+ self.assertEqual(run('', flags='-t restat'), '')
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/build.cc b/src/build.cc
index ced7110..cd8df4e 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -96,7 +96,7 @@
total_edges_ = total;
}
-void BuildStatus::BuildEdgeStarted(Edge* edge) {
+void BuildStatus::BuildEdgeStarted(const Edge* edge) {
assert(running_edges_.find(edge) == running_edges_.end());
int start_time = (int)(GetTimeMillis() - start_time_millis_);
running_edges_.insert(make_pair(edge, start_time));
@@ -290,7 +290,7 @@
return out;
}
-void BuildStatus::PrintStatus(Edge* edge, EdgeStatus status) {
+void BuildStatus::PrintStatus(const Edge* edge, EdgeStatus status) {
if (config_.verbosity == BuildConfig::QUIET)
return;
@@ -319,11 +319,11 @@
want_.clear();
}
-bool Plan::AddTarget(Node* node, string* err) {
+bool Plan::AddTarget(const Node* node, string* err) {
return AddSubTarget(node, NULL, err, NULL);
}
-bool Plan::AddSubTarget(Node* node, Node* dependent, string* err,
+bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err,
set<Edge*>* dyndep_walk) {
Edge* edge = node->in_edge();
if (!edge) { // Leaf node.
@@ -533,7 +533,7 @@
return true;
}
-bool Plan::DyndepsLoaded(DependencyScan* scan, Node* node,
+bool Plan::DyndepsLoaded(DependencyScan* scan, const Node* node,
const DyndepFile& ddf, string* err) {
// Recompute the dirty state of all our direct and indirect dependents now
// that our dyndep information has been loaded.
@@ -601,7 +601,7 @@
return true;
}
-bool Plan::RefreshDyndepDependents(DependencyScan* scan, Node* node,
+bool Plan::RefreshDyndepDependents(DependencyScan* scan, const Node* node,
string* err) {
// Collect the transitive closure of dependents and mark their edges
// as not yet visited by RecomputeDirty.
@@ -635,7 +635,7 @@
return true;
}
-void Plan::UnmarkDependents(Node* node, set<Node*>* dependents) {
+void Plan::UnmarkDependents(const Node* node, set<Node*>* dependents) {
for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
oe != node->out_edges().end(); ++oe) {
Edge* edge = *oe;
@@ -655,9 +655,9 @@
}
}
-void Plan::Dump() {
+void Plan::Dump() const {
printf("pending: %d\n", (int)want_.size());
- for (map<Edge*, Want>::iterator e = want_.begin(); e != want_.end(); ++e) {
+ for (map<Edge*, Want>::const_iterator e = want_.begin(); e != want_.end(); ++e) {
if (e->second != kWantNothing)
printf("want ");
e->first->Dump();
@@ -676,12 +676,12 @@
const BuildConfig& config_;
SubprocessSet subprocs_;
- map<Subprocess*, Edge*> subproc_to_edge_;
+ map<const Subprocess*, Edge*> subproc_to_edge_;
};
vector<Edge*> RealCommandRunner::GetActiveEdges() {
vector<Edge*> edges;
- for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
+ for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
e != subproc_to_edge_.end(); ++e)
edges.push_back(e->second);
return edges;
@@ -720,7 +720,7 @@
result->status = subproc->Finish();
result->output = subproc->GetOutput();
- map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.find(subproc);
+ map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.find(subproc);
result->edge = e->second;
subproc_to_edge_.erase(e);
diff --git a/src/build.h b/src/build.h
index 410d4a5..97773c4 100644
--- a/src/build.h
+++ b/src/build.h
@@ -46,7 +46,7 @@
/// Add a target to our plan (including all its dependencies).
/// Returns false if we don't need to build this target; may
/// fill in |err| with an error message if there's a problem.
- bool AddTarget(Node* node, string* err);
+ bool AddTarget(const Node* node, string* err);
// Pop a ready edge off the queue of edges to build.
// Returns NULL if there's no work to do.
@@ -56,7 +56,7 @@
bool more_to_do() const { return wanted_edges_ > 0 && command_edges_ > 0; }
/// Dumps the current state of the plan.
- void Dump();
+ void Dump() const;
enum EdgeResult {
kEdgeFailed,
@@ -81,12 +81,12 @@
/// Update the build plan to account for modifications made to the graph
/// by information loaded from a dyndep file.
- bool DyndepsLoaded(DependencyScan* scan, Node* node,
+ bool DyndepsLoaded(DependencyScan* scan, const Node* node,
const DyndepFile& ddf, string* err);
private:
- bool RefreshDyndepDependents(DependencyScan* scan, Node* node, string* err);
- void UnmarkDependents(Node* node, set<Node*>* dependents);
- bool AddSubTarget(Node* node, Node* dependent, string* err,
+ bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, string* err);
+ void UnmarkDependents(const Node* node, set<Node*>* dependents);
+ bool AddSubTarget(const Node* node, const Node* dependent, string* err,
set<Edge*>* dyndep_walk);
/// Update plan with knowledge that the given node is up to date.
@@ -240,7 +240,7 @@
struct BuildStatus {
explicit BuildStatus(const BuildConfig& config);
void PlanHasTotalEdges(int total);
- void BuildEdgeStarted(Edge* edge);
+ void BuildEdgeStarted(const Edge* edge);
void BuildEdgeFinished(Edge* edge, bool success, const string& output,
int* start_time, int* end_time);
void BuildLoadDyndeps();
@@ -261,7 +261,7 @@
EdgeStatus status) const;
private:
- void PrintStatus(Edge* edge, EdgeStatus status);
+ void PrintStatus(const Edge* edge, EdgeStatus status);
const BuildConfig& config_;
@@ -271,7 +271,7 @@
int started_edges_, finished_edges_, total_edges_;
/// Map of running edge to time the edge started running.
- typedef map<Edge*, int> RunningEdgeMap;
+ typedef map<const Edge*, int> RunningEdgeMap;
RunningEdgeMap running_edges_;
/// Prints progress output.
diff --git a/src/build_log.cc b/src/build_log.cc
index c4a08a0..e2a9344 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -21,6 +21,7 @@
#endif
#include "build_log.h"
+#include "disk_interface.h"
#include <errno.h>
#include <stdlib.h>
@@ -241,14 +242,14 @@
char* line_end_;
};
-bool BuildLog::Load(const string& path, string* err) {
+LoadStatus BuildLog::Load(const string& path, string* err) {
METRIC_RECORD(".ninja_log load");
FILE* file = fopen(path.c_str(), "r");
if (!file) {
if (errno == ENOENT)
- return true;
+ return LOAD_NOT_FOUND;
*err = strerror(errno);
- return false;
+ return LOAD_ERROR;
}
int log_version = 0;
@@ -269,7 +270,7 @@
unlink(path.c_str());
// Don't report this as a failure. An empty build log will cause
// us to rebuild the outputs anyway.
- return true;
+ return LOAD_SUCCESS;
}
}
@@ -339,7 +340,7 @@
fclose(file);
if (!line_start) {
- return true; // file was empty
+ return LOAD_SUCCESS; // file was empty
}
// Decide whether it's time to rebuild the log:
@@ -354,7 +355,7 @@
needs_recompaction_ = true;
}
- return true;
+ return LOAD_SUCCESS;
}
BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
@@ -418,3 +419,50 @@
return true;
}
+
+bool BuildLog::Restat(const StringPiece path,
+ const DiskInterface& disk_interface,
+ std::string* const err) {
+ METRIC_RECORD(".ninja_log restat");
+
+ Close();
+ std::string temp_path = path.AsString() + ".restat";
+ FILE* f = fopen(temp_path.c_str(), "wb");
+ if (!f) {
+ *err = strerror(errno);
+ return false;
+ }
+
+ if (fprintf(f, kFileSignature, kCurrentVersion) < 0) {
+ *err = strerror(errno);
+ fclose(f);
+ return false;
+ }
+ for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
+ const TimeStamp mtime = disk_interface.Stat(i->second->output, err);
+ if (mtime == -1) {
+ fclose(f);
+ return false;
+ }
+ i->second->mtime = mtime;
+
+ if (!WriteEntry(f, *i->second)) {
+ *err = strerror(errno);
+ fclose(f);
+ return false;
+ }
+ }
+
+ fclose(f);
+ if (unlink(path.str_) < 0) {
+ *err = strerror(errno);
+ return false;
+ }
+
+ if (rename(temp_path.c_str(), path.str_) < 0) {
+ *err = strerror(errno);
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/build_log.h b/src/build_log.h
index 5268fab..ed59d79 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -20,9 +20,11 @@
using namespace std;
#include "hash_map.h"
+#include "load_status.h"
#include "timestamp.h"
#include "util.h" // uint64_t
+struct DiskInterface;
struct Edge;
/// Can answer questions about the manifest for the BuildLog.
@@ -49,7 +51,7 @@
void Close();
/// Load the on-disk log.
- bool Load(const string& path, string* err);
+ LoadStatus Load(const string& path, string* err);
struct LogEntry {
string output;
@@ -81,6 +83,10 @@
/// Rewrite the known log entries, throwing away old data.
bool Recompact(const string& path, const BuildLogUser& user, string* err);
+ /// Restat all outputs in the log
+ bool Restat(StringPiece path, const DiskInterface& disk_interface,
+ std::string* err);
+
typedef ExternalStringHashMap<LogEntry*>::Type Entries;
const Entries& entries() const { return entries_; }
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index ad30380..48ece23 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -25,6 +25,7 @@
#include <sys/types.h>
#include <unistd.h>
#endif
+#include <cassert>
namespace {
@@ -150,7 +151,7 @@
BuildLog log3;
err.clear();
- ASSERT_TRUE(log3.Load(kTestFilename, &err) || !err.empty());
+ ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
}
}
@@ -216,6 +217,47 @@
ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
}
+struct TestDiskInterface : public DiskInterface {
+ virtual TimeStamp Stat(const string& path, string* err) const {
+ return 4;
+ }
+ virtual bool WriteFile(const string& path, const string& contents) {
+ assert(false);
+ return true;
+ }
+ virtual bool MakeDir(const string& path) {
+ assert(false);
+ return false;
+ }
+ virtual Status ReadFile(const string& path, string* contents, string* err) {
+ assert(false);
+ return NotFound;
+ }
+ virtual int RemoveFile(const string& path) {
+ assert(false);
+ return 0;
+ }
+};
+
+TEST_F(BuildLogTest, Restat) {
+ FILE* f = fopen(kTestFilename, "wb");
+ fprintf(f, "# ninja log v4\n"
+ "1\t2\t3\tout\tcommand\n");
+ fclose(f);
+ std::string err;
+ BuildLog log;
+ EXPECT_TRUE(log.Load(kTestFilename, &err));
+ ASSERT_EQ("", err);
+ BuildLog::LogEntry* e = log.LookupByOutput("out");
+ ASSERT_EQ(3, e->mtime);
+
+ TestDiskInterface testDiskInterface;
+ EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, &err));
+ ASSERT_EQ("", err);
+ e = log.LookupByOutput("out");
+ ASSERT_EQ(4, e->mtime);
+}
+
TEST_F(BuildLogTest, VeryLongInputLine) {
// Ninja's build log buffer is currently 256kB. Lines longer than that are
// silently ignored, but don't affect parsing of other lines.
diff --git a/src/deps_log.cc b/src/deps_log.cc
index 4aaffeb..cf55194 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -167,15 +167,15 @@
file_ = NULL;
}
-bool DepsLog::Load(const string& path, State* state, string* err) {
+LoadStatus DepsLog::Load(const string& path, State* state, string* err) {
METRIC_RECORD(".ninja_deps load");
char buf[kMaxRecordSize + 1];
FILE* f = fopen(path.c_str(), "rb");
if (!f) {
if (errno == ENOENT)
- return true;
+ return LOAD_NOT_FOUND;
*err = strerror(errno);
- return false;
+ return LOAD_ERROR;
}
bool valid_header = true;
@@ -196,7 +196,7 @@
unlink(path.c_str());
// Don't report this as a failure. An empty deps log will cause
// us to rebuild the outputs anyway.
- return true;
+ return LOAD_SUCCESS;
}
long offset;
@@ -284,12 +284,12 @@
fclose(f);
if (!Truncate(path, offset, err))
- return false;
+ return LOAD_ERROR;
// The truncate succeeded; we'll just report the load error as a
// warning because the build can proceed.
*err += "; recovering";
- return true;
+ return LOAD_SUCCESS;
}
fclose(f);
@@ -302,7 +302,7 @@
needs_recompaction_ = true;
}
- return true;
+ return LOAD_SUCCESS;
}
DepsLog::Deps* DepsLog::GetDeps(Node* node) {
diff --git a/src/deps_log.h b/src/deps_log.h
index 3812a28..e7974a1 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -21,6 +21,7 @@
#include <stdio.h>
+#include "load_status.h"
#include "timestamp.h"
struct Node;
@@ -84,7 +85,7 @@
int node_count;
Node** nodes;
};
- bool Load(const string& path, State* state, string* err);
+ LoadStatus Load(const string& path, State* state, string* err);
Deps* GetDeps(Node* node);
/// Rewrite the known log entries, throwing away old data.
diff --git a/src/graph.cc b/src/graph.cc
index 58a4630..28a9653 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -223,8 +223,8 @@
return true;
}
-bool DependencyScan::RecomputeOutputDirty(Edge* edge,
- Node* most_recent_input,
+bool DependencyScan::RecomputeOutputDirty(const Edge* edge,
+ const Node* most_recent_input,
const string& command,
Node* output) {
if (edge->is_phony()) {
diff --git a/src/graph.h b/src/graph.h
index 19b25c4..2fa54af 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -310,7 +310,7 @@
/// Recompute whether a given single output should be marked dirty.
/// Returns true if so.
- bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input,
+ bool RecomputeOutputDirty(const Edge* edge, const Node* most_recent_input,
const string& command, Node* output);
BuildLog* build_log_;
diff --git a/src/load_status.h b/src/load_status.h
new file mode 100644
index 0000000..0b16b1a
--- /dev/null
+++ b/src/load_status.h
@@ -0,0 +1,24 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_LOAD_STATUS_H_
+#define NINJA_LOAD_STATUS_H_
+
+enum LoadStatus {
+ LOAD_ERROR,
+ LOAD_SUCCESS,
+ LOAD_NOT_FOUND,
+};
+
+#endif // NINJA_LOAD_STATUS_H_
diff --git a/src/ninja.cc b/src/ninja.cc
index 6dadb44..bf97b4a 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -17,6 +17,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <cstdlib>
#ifdef _WIN32
#include "getopt.h"
@@ -121,15 +122,16 @@
int ToolClean(const Options* options, int argc, char* argv[]);
int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
int ToolRecompact(const Options* options, int argc, char* argv[]);
+ int ToolRestat(const Options* options, int argc, char* argv[]);
int ToolUrtle(const Options* options, int argc, char** argv);
int ToolRules(const Options* options, int argc, char* argv[]);
/// Open the build log.
- /// @return false on error.
+ /// @return LOAD_ERROR on error.
bool OpenBuildLog(bool recompact_only = false);
/// Open the deps log: load it, then open for writing.
- /// @return false on error.
+ /// @return LOAD_ERROR on error.
bool OpenDepsLog(bool recompact_only = false);
/// Ensure the build directory exists, creating it if necessary.
@@ -150,7 +152,7 @@
virtual bool IsPathDead(StringPiece s) const {
Node* n = state_.LookupNode(s);
- if (!n || !n->in_edge())
+ if (n && n->in_edge())
return false;
// 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
@@ -799,12 +801,14 @@
bool first = true;
vector<char> cwd;
+ char* success = NULL;
do {
cwd.resize(cwd.size() + 1024);
errno = 0;
- } while (!getcwd(&cwd[0], cwd.size()) && errno == ERANGE);
- if (errno != 0 && errno != ERANGE) {
+ success = getcwd(&cwd[0], cwd.size());
+ } while (!success && errno == ERANGE);
+ if (!success) {
Error("cannot determine working directory: %s", strerror(errno));
return 1;
}
@@ -841,13 +845,53 @@
if (!EnsureBuildDirExists())
return 1;
- if (!OpenBuildLog(/*recompact_only=*/true) ||
- !OpenDepsLog(/*recompact_only=*/true))
+ if (OpenBuildLog(/*recompact_only=*/true) == LOAD_ERROR ||
+ OpenDepsLog(/*recompact_only=*/true) == LOAD_ERROR)
return 1;
return 0;
}
+int NinjaMain::ToolRestat(const Options* options, int argc, char* argv[]) {
+ if (!EnsureBuildDirExists())
+ return 1;
+
+ string log_path = ".ninja_log";
+ if (!build_dir_.empty())
+ log_path = build_dir_ + "/" + log_path;
+
+ string err;
+ const LoadStatus status = build_log_.Load(log_path, &err);
+ if (status == LOAD_ERROR) {
+ Error("loading build log %s: %s", log_path.c_str(), err.c_str());
+ return EXIT_FAILURE;
+ }
+ if (status == LOAD_NOT_FOUND) {
+ // Nothing to restat, ignore this
+ return EXIT_SUCCESS;
+ }
+ if (!err.empty()) {
+ // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
+ Warning("%s", err.c_str());
+ err.clear();
+ }
+
+ bool success = build_log_.Restat(log_path, disk_interface_, &err);
+ if (!success) {
+ Error("failed recompaction: %s", err.c_str());
+ return EXIT_FAILURE;
+ }
+
+ if (!config_.dry_run) {
+ if (!build_log_.OpenForWrite(log_path, *this, &err)) {
+ Error("opening build log: %s", err.c_str());
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
int NinjaMain::ToolUrtle(const Options* options, int argc, char** argv) {
// RLE encoded.
const char* urtle =
@@ -900,6 +944,8 @@
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase },
{ "recompact", "recompacts ninja-internal data structures",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRecompact },
+ { "restat", "restats all outputs in the build log",
+ Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRestat },
{ "rules", "list all rules",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRules },
{ "urtle", NULL,
@@ -1023,17 +1069,21 @@
log_path = build_dir_ + "/" + log_path;
string err;
- if (!build_log_.Load(log_path, &err)) {
+ const LoadStatus status = build_log_.Load(log_path, &err);
+ if (status == LOAD_ERROR) {
Error("loading build log %s: %s", log_path.c_str(), err.c_str());
return false;
}
if (!err.empty()) {
- // Hack: Load() can return a warning via err by returning true.
+ // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
Warning("%s", err.c_str());
err.clear();
}
if (recompact_only) {
+ if (status == LOAD_NOT_FOUND) {
+ return true;
+ }
bool success = build_log_.Recompact(log_path, *this, &err);
if (!success)
Error("failed recompaction: %s", err.c_str());
@@ -1058,17 +1108,21 @@
path = build_dir_ + "/" + path;
string err;
- if (!deps_log_.Load(path, &state_, &err)) {
+ const LoadStatus status = deps_log_.Load(path, &state_, &err);
+ if (status == LOAD_ERROR) {
Error("loading deps log %s: %s", path.c_str(), err.c_str());
return false;
}
if (!err.empty()) {
- // Hack: Load() can return a warning via err by returning true.
+ // Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
Warning("%s", err.c_str());
err.clear();
}
if (recompact_only) {
+ if (status == LOAD_NOT_FOUND) {
+ return true;
+ }
bool success = deps_log_.Recompact(path, &err);
if (!success)
Error("failed recompaction: %s", err.c_str());