summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Niklas Hasse <jhasse@bixense.com>2020-01-04 17:34:13 (GMT)
committerGitHub <noreply@github.com>2020-01-04 17:34:13 (GMT)
commitc44d3406bae7d37a3815dafb8a99d01b5f6b666f (patch)
tree9b8b4e38eebc66bd28c7a283ab223401cf7db26f
parent714621dba1b8b2d8cd6080b7bc82955b44054734 (diff)
parent1c5857c37d18a5cf1b7242493c49c6fad02da3a9 (diff)
downloadNinja-c44d3406bae7d37a3815dafb8a99d01b5f6b666f.zip
Ninja-c44d3406bae7d37a3815dafb8a99d01b5f6b666f.tar.gz
Ninja-c44d3406bae7d37a3815dafb8a99d01b5f6b666f.tar.bz2
Merge branch 'master' into clean_dead_outputs
-rw-r--r--.editorconfig11
-rw-r--r--.github/workflows/release-ninja-binaries.yml73
-rw-r--r--.gitignore8
-rw-r--r--.travis.yml22
-rw-r--r--CMakeLists.txt115
-rw-r--r--appveyor.yml25
-rwxr-xr-xconfigure.py11
-rw-r--r--doc/manual.asciidoc11
-rwxr-xr-xmisc/output_test.py18
-rw-r--r--src/build.cc58
-rw-r--r--src/build.h22
-rw-r--r--src/build_log.cc60
-rw-r--r--src/build_log.h8
-rw-r--r--src/build_log_test.cc44
-rw-r--r--src/build_test.cc227
-rw-r--r--src/depfile_parser.cc176
-rw-r--r--src/depfile_parser.h13
-rw-r--r--src/depfile_parser.in.cc98
-rw-r--r--src/depfile_parser_test.cc125
-rw-r--r--src/deps_log.cc14
-rw-r--r--src/deps_log.h3
-rw-r--r--src/disk_interface.cc14
-rw-r--r--src/graph.cc82
-rw-r--r--src/graph.h14
-rw-r--r--src/graph_test.cc2
-rw-r--r--src/line_printer.cc4
-rw-r--r--src/load_status.h24
-rw-r--r--src/manifest_parser.cc12
-rw-r--r--src/manifest_parser_test.cc5
-rw-r--r--src/ninja.cc142
-rw-r--r--src/test.cc9
-rw-r--r--src/util.cc18
-rw-r--r--src/util_test.cc4
33 files changed, 1148 insertions, 324 deletions
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 46736a6..dca1129 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,7 @@
*.exe
*.pdb
*.ilk
-TAGS
-/build
+/build*/
/build.ninja
/ninja
/ninja.bootstrap
@@ -18,8 +17,8 @@ TAGS
/graph.png
/doc/manual.html
/doc/doxygen
-/gtest-1.6.0
*.patch
+.DS_Store
# Eclipse project files
.project
@@ -36,3 +35,6 @@ TAGS
# Visual Studio Code project files
/.vscode/
/.ccls-cache/
+
+# Qt Creator project files
+/CMakeLists.txt.user
diff --git a/.travis.yml b/.travis.yml
index f76b982..e5d7d2b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,35 @@
matrix:
include:
- os: linux
+ dist: precise
compiler: gcc
- os: linux
+ dist: precise
compiler: clang
+ - os: linux
+ dist: trusty
+ compiler: gcc
+ - os: linux
+ dist: trusty
+ compiler: clang
+ - os: linux
+ dist: xenial
+ compiler: gcc
+ - os: linux
+ dist: xenial
+ compiler: clang
+ - os: osx
+ osx_image: xcode10
- os: osx
+ osx_image: xcode10.1
sudo: false
language: cpp
+before_install:
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install re2c ; fi
+ - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then choco install re2c python ; fi
script:
- ./misc/ci.py
- - ./configure.py --bootstrap
+ - python3 configure.py --bootstrap
- ./ninja all
- ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
- ./misc/ninja_syntax_test.py
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..de0fe1a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,115 @@
+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()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -fdiagnostics-color")
+endif()
+
+find_program(RE2C re2c)
+if(RE2C)
+ # the depfile parser and ninja lexers are generated using re2c.
+ function(re2c IN OUT)
+ add_custom_command(DEPENDS ${IN} OUTPUT ${OUT}
+ COMMAND ${RE2C} -b -i --no-generation-date -o ${OUT} ${IN}
+ )
+ endfunction()
+ re2c(${CMAKE_SOURCE_DIR}/src/depfile_parser.in.cc ${CMAKE_BINARY_DIR}/depfile_parser.cc)
+ re2c(${CMAKE_SOURCE_DIR}/src/lexer.in.cc ${CMAKE_BINARY_DIR}/lexer.cc)
+ add_library(libninja-re2c OBJECT ${CMAKE_BINARY_DIR}/depfile_parser.cc ${CMAKE_BINARY_DIR}/lexer.cc)
+else()
+ message(WARNING "re2c was not found; changes to src/*.in.cc will not affect your build.")
+ add_library(libninja-re2c OBJECT src/depfile_parser.cc src/lexer.cc)
+endif()
+target_include_directories(libninja-re2c PRIVATE src)
+
+# Core source files all build into ninja library.
+add_library(libninja OBJECT
+ src/build_log.cc
+ src/build.cc
+ src/clean.cc
+ src/clparser.cc
+ src/dyndep.cc
+ src/dyndep_parser.cc
+ src/debug_flags.cc
+ src/deps_log.cc
+ src/disk_interface.cc
+ src/edit_distance.cc
+ src/eval_env.cc
+ src/graph.cc
+ src/graphviz.cc
+ src/line_printer.cc
+ src/manifest_parser.cc
+ src/metrics.cc
+ src/parser.cc
+ src/state.cc
+ src/string_piece_util.cc
+ src/util.cc
+ src/version.cc
+)
+if(WIN32)
+ target_sources(libninja PRIVATE
+ src/subprocess-win32.cc
+ src/includes_normalize-win32.cc
+ src/msvc_helper-win32.cc
+ src/msvc_helper_main-win32.cc
+ src/getopt.c
+ )
+ if(MSVC)
+ target_sources(libninja PRIVATE src/minidump-win32.cc)
+ endif()
+else()
+ target_sources(libninja PRIVATE src/subprocess-posix.cc)
+endif()
+
+#Fixes GetActiveProcessorCount on MinGW
+if(MINGW)
+target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601 __USE_MINGW_ANSI_STDIO=1)
+endif()
+
+# Main executable is library plus main() function.
+add_executable(ninja src/ninja.cc)
+target_link_libraries(ninja PRIVATE libninja libninja-re2c)
+
+# Tests all build into ninja_test executable.
+add_executable(ninja_test
+ src/build_log_test.cc
+ src/build_test.cc
+ src/clean_test.cc
+ src/clparser_test.cc
+ src/depfile_parser_test.cc
+ src/deps_log_test.cc
+ src/disk_interface_test.cc
+ src/dyndep_parser_test.cc
+ src/edit_distance_test.cc
+ src/graph_test.cc
+ src/lexer_test.cc
+ src/manifest_parser_test.cc
+ src/ninja_test.cc
+ src/state_test.cc
+ src/string_piece_util_test.cc
+ src/subprocess_test.cc
+ src/test.cc
+ src/util_test.cc
+)
+if(WIN32)
+ target_sources(ninja_test PRIVATE src/includes_normalize_test.cc src/msvc_helper_test.cc)
+endif()
+target_link_libraries(ninja_test PRIVATE libninja libninja-re2c)
+
+enable_testing()
+add_test(NinjaTest ninja_test)
diff --git a/appveyor.yml b/appveyor.yml
index 4c64f29..f0b92b8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,5 +1,7 @@
version: 1.0.{build}
-image: Visual Studio 2017
+image:
+ - Visual Studio 2017
+ - Ubuntu1804
environment:
CLICOLOR_FORCE: 1
@@ -7,6 +9,16 @@ environment:
matrix:
- MSYSTEM: MINGW64
- MSYSTEM: MSVC
+ - MSYSTEM: LINUX
+
+matrix:
+ exclude:
+ - image: Visual Studio 2017
+ MSYSTEM: LINUX
+ - image: Ubuntu1804
+ MSYSTEM: MINGW64
+ - image: Ubuntu1804
+ MSYSTEM: MSVC
for:
-
@@ -16,7 +28,6 @@ for:
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
@@ -37,4 +48,14 @@ for:
python misc/ninja_syntax_test.py
+ - matrix:
+ only:
+ - image: Ubuntu1804
+ build_script:
+ - ./configure.py --bootstrap
+ - ./ninja all
+ - ./ninja_test
+ - misc/ninja_syntax_test.py
+ - misc/output_test.py
+
test: off
diff --git a/configure.py b/configure.py
index 850bb98..7d8ce90 100755
--- a/configure.py
+++ b/configure.py
@@ -60,6 +60,8 @@ class Platform(object):
self._platform = 'netbsd'
elif self._platform.startswith('aix'):
self._platform = 'aix'
+ elif self._platform.startswith('os400'):
+ self._platform = 'os400'
elif self._platform.startswith('dragonfly'):
self._platform = 'dragonfly'
@@ -97,6 +99,9 @@ class Platform(object):
def is_aix(self):
return self._platform == 'aix'
+ def is_os400_pase(self):
+ return self._platform == 'os400' or os.uname().sysname.startswith('OS400')
+
def uses_usr_local(self):
return self._platform in ('freebsd', 'openbsd', 'bitrig', 'dragonfly', 'netbsd')
@@ -351,7 +356,7 @@ else:
except:
pass
if platform.is_mingw():
- cflags += ['-D_WIN32_WINNT=0x0501']
+ 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')
@@ -432,7 +437,7 @@ if host.is_msvc():
description='LIB $out')
elif host.is_mingw():
n.rule('ar',
- command='cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out',
+ command='$ar crs $out $in',
description='AR $out')
else:
n.rule('ar',
@@ -536,7 +541,7 @@ if platform.is_msvc():
else:
libs.append('-lninja')
-if platform.is_aix():
+if platform.is_aix() and not platform.is_os400_pase():
libs.append('-lperfstat')
all_targets = []
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 7f3ab8a..8f42efb 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -283,6 +283,9 @@ target, show just the target's dependencies. _Available since Ninja 1.4._
`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+.
@@ -452,6 +455,14 @@ without any dependencies, the target will be considered out of date if
it does not exist. Without a phony build statement, Ninja will report
an error if the file does not exist and is required by the build.
+To create a rule that never rebuilds, use a build rule without any input:
+----------------
+rule touch
+ command = touch $out
+build file_that_always_exists.dummy: touch
+build dummy_target_to_follow_a_pattern: phony file_that_always_exists.dummy
+----------------
+
Default target statements
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/misc/output_test.py b/misc/output_test.py
index 1dcde10..3fd9c32 100755
--- a/misc/output_test.py
+++ b/misc/output_test.py
@@ -18,12 +18,15 @@ if 'NINJA_STATUS' in default_env:
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)
@@ -56,7 +59,7 @@ build b: echo
delay = 2
build c: echo
delay = 1
-'''),
+''', '-j3'),
'''[1/3] echo c\x1b[K
c
[2/3] echo b\x1b[K
@@ -99,5 +102,10 @@ red
\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 a055738..cd8df4e 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -47,7 +47,7 @@ struct DryRunCommandRunner : public CommandRunner {
virtual ~DryRunCommandRunner() {}
// Overridden from CommandRunner:
- virtual bool CanRunMore();
+ virtual bool CanRunMore() const;
virtual bool StartCommand(Edge* edge);
virtual bool WaitForCommand(Result* result);
@@ -55,7 +55,7 @@ struct DryRunCommandRunner : public CommandRunner {
queue<Edge*> finished_;
};
-bool DryRunCommandRunner::CanRunMore() {
+bool DryRunCommandRunner::CanRunMore() const {
return true;
}
@@ -96,7 +96,7 @@ void BuildStatus::PlanHasTotalEdges(int total) {
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));
@@ -139,7 +139,11 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
o != edge->outputs_.end(); ++o)
outputs += (*o)->path() + " ";
- printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
+ if (printer_.supports_color()) {
+ printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
+ } else {
+ printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
+ }
printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
}
@@ -286,7 +290,7 @@ string BuildStatus::FormatProgressStatus(
return out;
}
-void BuildStatus::PrintStatus(Edge* edge, EdgeStatus status) {
+void BuildStatus::PrintStatus(const Edge* edge, EdgeStatus status) {
if (config_.verbosity == BuildConfig::QUIET)
return;
@@ -315,11 +319,11 @@ void Plan::Reset() {
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.
@@ -369,7 +373,7 @@ bool Plan::AddSubTarget(Node* node, Node* dependent, string* err,
return true;
}
-void Plan::EdgeWanted(Edge* edge) {
+void Plan::EdgeWanted(const Edge* edge) {
++wanted_edges_;
if (!edge->is_phony())
++command_edges_;
@@ -529,7 +533,7 @@ bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
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.
@@ -597,7 +601,7 @@ bool Plan::DyndepsLoaded(DependencyScan* scan, Node* node,
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.
@@ -631,7 +635,7 @@ bool Plan::RefreshDyndepDependents(DependencyScan* scan, Node* node,
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;
@@ -651,9 +655,9 @@ void Plan::UnmarkDependents(Node* node, set<Node*>* dependents) {
}
}
-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();
@@ -664,7 +668,7 @@ void Plan::Dump() {
struct RealCommandRunner : public CommandRunner {
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
virtual ~RealCommandRunner() {}
- virtual bool CanRunMore();
+ virtual bool CanRunMore() const;
virtual bool StartCommand(Edge* edge);
virtual bool WaitForCommand(Result* result);
virtual vector<Edge*> GetActiveEdges();
@@ -672,12 +676,12 @@ struct RealCommandRunner : public CommandRunner {
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;
@@ -687,7 +691,7 @@ void RealCommandRunner::Abort() {
subprocs_.Clear();
}
-bool RealCommandRunner::CanRunMore() {
+bool RealCommandRunner::CanRunMore() const {
size_t subproc_number =
subprocs_.running_.size() + subprocs_.finished_.size();
return (int)subproc_number < config_.parallelism
@@ -716,7 +720,7 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
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);
@@ -1029,14 +1033,16 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
}
if (!deps_type.empty() && !config_.dry_run) {
- assert(edge->outputs_.size() == 1 && "should have been rejected by parser");
- Node* out = edge->outputs_[0];
- TimeStamp deps_mtime = disk_interface_->Stat(out->path(), err);
- if (deps_mtime == -1)
- return false;
- if (!scan_.deps_log()->RecordDeps(out, deps_mtime, deps_nodes)) {
- *err = string("Error writing to deps log: ") + strerror(errno);
- return false;
+ assert(edge->outputs_.size() >= 1 && "should have been rejected by parser");
+ for (std::vector<Node*>::const_iterator o = edge->outputs_.begin();
+ o != edge->outputs_.end(); ++o) {
+ TimeStamp deps_mtime = disk_interface_->Stat((*o)->path(), err);
+ if (deps_mtime == -1)
+ return false;
+ if (!scan_.deps_log()->RecordDeps(*o, deps_mtime, deps_nodes)) {
+ *err = std::string("Error writing to deps log: ") + strerror(errno);
+ return false;
+ }
}
}
return true;
diff --git a/src/build.h b/src/build.h
index ab59f0c..97773c4 100644
--- a/src/build.h
+++ b/src/build.h
@@ -46,7 +46,7 @@ struct Plan {
/// 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 @@ struct Plan {
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 @@ struct Plan {
/// 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.
@@ -108,7 +108,7 @@ private:
kWantToFinish
};
- void EdgeWanted(Edge* edge);
+ void EdgeWanted(const Edge* edge);
bool EdgeMaybeReady(map<Edge*, Want>::iterator want_e, string* err);
/// Submits a ready edge as a candidate for execution.
@@ -138,7 +138,7 @@ private:
/// RealCommandRunner is an implementation that actually runs commands.
struct CommandRunner {
virtual ~CommandRunner() {}
- virtual bool CanRunMore() = 0;
+ virtual bool CanRunMore() const = 0;
virtual bool StartCommand(Edge* edge) = 0;
/// The result of waiting for a command.
@@ -240,7 +240,7 @@ struct Builder {
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 @@ struct BuildStatus {
EdgeStatus status) const;
private:
- void PrintStatus(Edge* edge, EdgeStatus status);
+ void PrintStatus(const Edge* edge, EdgeStatus status);
const BuildConfig& config_;
@@ -271,7 +271,7 @@ struct BuildStatus {
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 @@ struct LineReader {
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 @@ bool BuildLog::Load(const string& path, string* err) {
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 @@ bool BuildLog::Load(const string& path, string* err) {
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 @@ bool BuildLog::Load(const string& path, string* err) {
needs_recompaction_ = true;
}
- return true;
+ return LOAD_SUCCESS;
}
BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
@@ -418,3 +419,50 @@ bool BuildLog::Recompact(const string& path, const BuildLogUser& user,
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 @@ struct BuildLog {
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 @@ struct BuildLog {
/// 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 @@ TEST_F(BuildLogTest, Truncate) {
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 @@ TEST_F(BuildLogTest, DuplicateVersionHeader) {
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/build_test.cc b/src/build_test.cc
index b5dbc6c..426e825 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -470,7 +470,7 @@ struct FakeCommandRunner : public CommandRunner {
max_active_edges_(1), fs_(fs) {}
// CommandRunner impl
- virtual bool CanRunMore();
+ virtual bool CanRunMore() const;
virtual bool StartCommand(Edge* edge);
virtual bool WaitForCommand(Result* result);
virtual vector<Edge*> GetActiveEdges();
@@ -488,6 +488,11 @@ struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
status_(config_) {
}
+ BuildTest(DepsLog* log) : config_(MakeConfig()), command_runner_(&fs_),
+ builder_(&state_, config_, NULL, log, &fs_),
+ status_(config_) {
+ }
+
virtual void SetUp() {
StateTestWithBuiltinRules::SetUp();
@@ -569,7 +574,7 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest,
builder.command_runner_.release();
}
-bool FakeCommandRunner::CanRunMore() {
+bool FakeCommandRunner::CanRunMore() const {
return active_edges_.size() < max_active_edges_;
}
@@ -582,6 +587,8 @@ bool FakeCommandRunner::StartCommand(Edge* edge) {
edge->rule().name() == "cat_rsp" ||
edge->rule().name() == "cat_rsp_out" ||
edge->rule().name() == "cc" ||
+ edge->rule().name() == "cp_multi_msvc" ||
+ edge->rule().name() == "cp_multi_gcc" ||
edge->rule().name() == "touch" ||
edge->rule().name() == "touch-interrupt" ||
edge->rule().name() == "touch-fail-tick2") {
@@ -643,6 +650,14 @@ bool FakeCommandRunner::WaitForCommand(Result* result) {
return true;
}
+ if (edge->rule().name() == "cp_multi_msvc") {
+ const std::string prefix = edge->GetBinding("msvc_deps_prefix");
+ for (std::vector<Node*>::iterator in = edge->inputs_.begin();
+ in != edge->inputs_.end(); ++in) {
+ result->output += prefix + (*in)->path() + '\n';
+ }
+ }
+
if (edge->rule().name() == "fail" ||
(edge->rule().name() == "touch-fail-tick2" && fs_->now_ == 2))
result->status = ExitFailure;
@@ -1855,6 +1870,214 @@ TEST_F(BuildTest, FailedDepsParse) {
EXPECT_EQ("subcommand failed", err);
}
+struct BuildWithQueryDepsLogTest : public BuildTest {
+ BuildWithQueryDepsLogTest() : BuildTest(&log_) {
+ }
+
+ ~BuildWithQueryDepsLogTest() {
+ log_.Close();
+ }
+
+ virtual void SetUp() {
+ BuildTest::SetUp();
+
+ temp_dir_.CreateAndEnter("BuildWithQueryDepsLogTest");
+
+ std::string err;
+ ASSERT_TRUE(log_.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+ }
+
+ ScopedTempDir temp_dir_;
+
+ DepsLog log_;
+};
+
+/// Test a MSVC-style deps log with multiple outputs.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileMSVC) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_msvc\n"
+" command = echo 'using $in' && for file in $out; do cp $in $$file; done\n"
+" deps = msvc\n"
+" msvc_deps_prefix = using \n"
+"build out1 out2: cp_multi_msvc in1\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'using in1' && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(1, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(1, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+}
+
+/// Test a GCC-style deps log with multiple outputs.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCOneLine) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_gcc\n"
+" command = echo '$out: $in' > in.d && for file in $out; do cp in1 $$file; done\n"
+" deps = gcc\n"
+" depfile = in.d\n"
+"build out1 out2: cp_multi_gcc in1 in2\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in.d", "out1 out2: in1 in2");
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'out1 out2: in1 in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(2, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out1_deps->nodes[1]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(2, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out2_deps->nodes[1]->path());
+}
+
+/// Test a GCC-style deps log with multiple outputs using a line per input.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCMultiLineInput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_gcc\n"
+" command = echo '$out: in1\\n$out: in2' > in.d && for file in $out; do cp in1 $$file; done\n"
+" deps = gcc\n"
+" depfile = in.d\n"
+"build out1 out2: cp_multi_gcc in1 in2\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in.d", "out1 out2: in1\nout1 out2: in2");
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'out1 out2: in1\\nout1 out2: in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(2, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out1_deps->nodes[1]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(2, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out2_deps->nodes[1]->path());
+}
+
+/// Test a GCC-style deps log with multiple outputs using a line per output.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCMultiLineOutput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_gcc\n"
+" command = echo 'out1: $in\\nout2: $in' > in.d && for file in $out; do cp in1 $$file; done\n"
+" deps = gcc\n"
+" depfile = in.d\n"
+"build out1 out2: cp_multi_gcc in1 in2\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in.d", "out1: in1 in2\nout2: in1 in2");
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'out1: in1 in2\\nout2: in1 in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(2, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out1_deps->nodes[1]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(2, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out2_deps->nodes[1]->path());
+}
+
+/// Test a GCC-style deps log with multiple outputs mentioning only the main output.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCOnlyMainOutput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_gcc\n"
+" command = echo 'out1: $in' > in.d && for file in $out; do cp in1 $$file; done\n"
+" deps = gcc\n"
+" depfile = in.d\n"
+"build out1 out2: cp_multi_gcc in1 in2\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in.d", "out1: in1 in2");
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'out1: in1 in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(2, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out1_deps->nodes[1]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(2, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out2_deps->nodes[1]->path());
+}
+
+/// Test a GCC-style deps log with multiple outputs mentioning only the secondary output.
+TEST_F(BuildWithQueryDepsLogTest, TwoOutputsDepFileGCCOnlySecondaryOutput) {
+ // Note: This ends up short-circuiting the node creation due to the primary
+ // output not being present, but it should still work.
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cp_multi_gcc\n"
+" command = echo 'out2: $in' > in.d && for file in $out; do cp in1 $$file; done\n"
+" deps = gcc\n"
+" depfile = in.d\n"
+"build out1 out2: cp_multi_gcc in1 in2\n"));
+
+ std::string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in.d", "out2: in1 in2");
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("echo 'out2: in1 in2' > in.d && for file in out1 out2; do cp in1 $file; done", command_runner_.commands_ran_[0]);
+
+ Node* out1_node = state_.LookupNode("out1");
+ DepsLog::Deps* out1_deps = log_.GetDeps(out1_node);
+ EXPECT_EQ(2, out1_deps->node_count);
+ EXPECT_EQ("in1", out1_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out1_deps->nodes[1]->path());
+
+ Node* out2_node = state_.LookupNode("out2");
+ DepsLog::Deps* out2_deps = log_.GetDeps(out2_node);
+ EXPECT_EQ(2, out2_deps->node_count);
+ EXPECT_EQ("in1", out2_deps->nodes[0]->path());
+ EXPECT_EQ("in2", out2_deps->nodes[1]->path());
+}
+
/// Tests of builds involving deps logs necessarily must span
/// multiple builds. We reuse methods on BuildTest but not the
/// builder_ it sets up, because we want pristine objects for
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index 405289f..90d4a8a 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -16,6 +16,8 @@
#include "depfile_parser.h"
#include "util.h"
+#include <algorithm>
+
DepfileParser::DepfileParser(DepfileParserOptions options)
: options_(options)
{
@@ -30,9 +32,15 @@ DepfileParser::DepfileParser(DepfileParserOptions options)
// How do you end a line with a backslash? The netbsd Make docs suggest
// reading the result of a shell command echoing a backslash!
//
-// Rather than implement all of above, we do a simpler thing here:
-// Backslashes escape a set of characters (see "escapes" defined below),
-// otherwise they are passed through verbatim.
+// Rather than implement all of above, we follow what GCC/Clang produces:
+// Backslashes escape a space or hash sign.
+// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
+// followed by space.
+// When a space is preceded by 2N backslashes, it represents 2N backslashes at
+// the end of a filename.
+// A hash sign is escaped by a single backslash. All other backslashes remain
+// unchanged.
+//
// If anyone actually has depfiles that rely on the more complicated
// behavior we can adjust this.
bool DepfileParser::Parse(string* content, string* err) {
@@ -42,10 +50,8 @@ bool DepfileParser::Parse(string* content, string* err) {
char* in = &(*content)[0];
char* end = in + content->size();
bool have_target = false;
- bool have_secondary_target_on_this_rule = false;
- bool have_newline_since_primary_target = false;
- bool warned_distinct_target_lines = false;
bool parsing_targets = true;
+ bool poisoned_input = false;
while (in < end) {
bool have_newline = false;
// out: current output point (typically same as in, but can fall behind
@@ -72,7 +78,7 @@ bool DepfileParser::Parse(string* content, string* err) {
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 0, 0, 0, 0, 128,
+ 128, 128, 128, 128, 0, 128, 0, 128,
0, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128, 128, 128,
@@ -111,7 +117,8 @@ bool DepfileParser::Parse(string* content, string* err) {
if (yych <= '#') goto yy4;
goto yy12;
} else {
- if (yych == '\\') goto yy13;
+ if (yych <= '?') goto yy4;
+ if (yych <= '\\') goto yy13;
goto yy4;
}
}
@@ -143,6 +150,7 @@ yy9:
if (yybm[0+yych] & 128) {
goto yy9;
}
+yy11:
{
// Got a span of plain text.
int len = (int)(in - start);
@@ -158,24 +166,22 @@ yy12:
goto yy5;
yy13:
yych = *(yymarker = ++in);
- if (yych <= '"') {
- if (yych <= '\f') {
+ if (yych <= 0x1F) {
+ if (yych <= '\n') {
if (yych <= 0x00) goto yy5;
- if (yych == '\n') goto yy18;
- goto yy16;
+ if (yych <= '\t') goto yy16;
+ goto yy17;
} else {
- if (yych <= '\r') goto yy20;
- if (yych == ' ') goto yy22;
+ if (yych == '\r') goto yy19;
goto yy16;
}
} else {
- if (yych <= 'Z') {
- if (yych <= '#') goto yy22;
- if (yych == '*') goto yy22;
- goto yy16;
+ if (yych <= '#') {
+ if (yych <= ' ') goto yy21;
+ if (yych <= '"') goto yy16;
+ goto yy23;
} else {
- if (yych <= ']') goto yy22;
- if (yych == '|') goto yy22;
+ if (yych == '\\') goto yy25;
goto yy16;
}
}
@@ -188,30 +194,93 @@ yy14:
}
yy16:
++in;
- {
- // Let backslash before other characters through verbatim.
- *out++ = '\\';
- *out++ = yych;
- continue;
- }
-yy18:
+ goto yy11;
+yy17:
++in;
{
// A line continuation ends the current file name.
break;
}
-yy20:
+yy19:
yych = *++in;
- if (yych == '\n') goto yy18;
+ if (yych == '\n') goto yy17;
in = yymarker;
goto yy5;
-yy22:
+yy21:
++in;
{
- // De-escape backslashed character.
- *out++ = yych;
+ // 2N+1 backslashes plus space -> N backslashes plus space.
+ int len = (int)(in - start);
+ int n = len / 2 - 1;
+ if (out < start)
+ memset(out, '\\', n);
+ out += n;
+ *out++ = ' ';
continue;
}
+yy23:
+ ++in;
+ {
+ // De-escape hash sign, but preserve other leading backslashes.
+ int len = (int)(in - start);
+ if (len > 2 && out < start)
+ memset(out, '\\', len - 2);
+ out += len - 2;
+ *out++ = '#';
+ continue;
+ }
+yy25:
+ yych = *++in;
+ if (yych <= 0x1F) {
+ if (yych <= '\n') {
+ if (yych <= 0x00) goto yy11;
+ if (yych <= '\t') goto yy16;
+ goto yy11;
+ } else {
+ if (yych == '\r') goto yy11;
+ goto yy16;
+ }
+ } else {
+ if (yych <= '#') {
+ if (yych <= ' ') goto yy26;
+ if (yych <= '"') goto yy16;
+ goto yy23;
+ } else {
+ if (yych == '\\') goto yy28;
+ goto yy16;
+ }
+ }
+yy26:
+ ++in;
+ {
+ // 2N backslashes plus space -> 2N backslashes, end of filename.
+ int len = (int)(in - start);
+ if (out < start)
+ memset(out, '\\', len - 1);
+ out += len - 1;
+ break;
+ }
+yy28:
+ yych = *++in;
+ if (yych <= 0x1F) {
+ if (yych <= '\n') {
+ if (yych <= 0x00) goto yy11;
+ if (yych <= '\t') goto yy16;
+ goto yy11;
+ } else {
+ if (yych == '\r') goto yy11;
+ goto yy16;
+ }
+ } else {
+ if (yych <= '#') {
+ if (yych <= ' ') goto yy21;
+ if (yych <= '"') goto yy16;
+ goto yy23;
+ } else {
+ if (yych == '\\') goto yy25;
+ goto yy16;
+ }
+ }
}
}
@@ -225,41 +294,32 @@ yy22:
}
if (len > 0) {
- if (is_dependency) {
- if (have_secondary_target_on_this_rule) {
- if (!have_newline_since_primary_target) {
- *err = "depfile has multiple output paths";
- return false;
- } else if (options_.depfile_distinct_target_lines_action_ ==
- kDepfileDistinctTargetLinesActionError) {
- *err =
- "depfile has multiple output paths (on separate lines)"
- " [-w depfilemulti=err]";
+ StringPiece piece = StringPiece(filename, len);
+ // If we've seen this as an input before, skip it.
+ std::vector<StringPiece>::iterator pos = std::find(ins_.begin(), ins_.end(), piece);
+ if (pos == ins_.end()) {
+ if (is_dependency) {
+ if (poisoned_input) {
+ *err = "inputs may not also have inputs";
return false;
- } else {
- if (!warned_distinct_target_lines) {
- warned_distinct_target_lines = true;
- Warning("depfile has multiple output paths (on separate lines); "
- "continuing anyway [-w depfilemulti=warn]");
- }
- continue;
}
+ // New input.
+ ins_.push_back(piece);
+ } else {
+ // Check for a new output.
+ if (std::find(outs_.begin(), outs_.end(), piece) == outs_.end())
+ outs_.push_back(piece);
}
- ins_.push_back(StringPiece(filename, len));
- } else if (!out_.str_) {
- out_ = StringPiece(filename, len);
- } else if (out_ != StringPiece(filename, len)) {
- have_secondary_target_on_this_rule = true;
+ } else if (!is_dependency) {
+ // We've passed an input on the left side; reject new inputs.
+ poisoned_input = true;
}
}
if (have_newline) {
// A newline ends a rule so the next filename will be a new target.
parsing_targets = true;
- have_secondary_target_on_this_rule = false;
- if (have_target) {
- have_newline_since_primary_target = true;
- }
+ poisoned_input = false;
}
}
if (!have_target) {
diff --git a/src/depfile_parser.h b/src/depfile_parser.h
index be20374..11b1228 100644
--- a/src/depfile_parser.h
+++ b/src/depfile_parser.h
@@ -21,17 +21,8 @@ using namespace std;
#include "string_piece.h"
-enum DepfileDistinctTargetLinesAction {
- kDepfileDistinctTargetLinesActionWarn,
- kDepfileDistinctTargetLinesActionError,
-};
-
struct DepfileParserOptions {
- DepfileParserOptions()
- : depfile_distinct_target_lines_action_(
- kDepfileDistinctTargetLinesActionWarn) {}
- DepfileDistinctTargetLinesAction
- depfile_distinct_target_lines_action_;
+ DepfileParserOptions() {}
};
/// Parser for the dependency information emitted by gcc's -M flags.
@@ -44,7 +35,7 @@ struct DepfileParser {
/// pointers within it.
bool Parse(string* content, string* err);
- StringPiece out_;
+ std::vector<StringPiece> outs_;
vector<StringPiece> ins_;
DepfileParserOptions options_;
};
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index f8c94b3..b32b942 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -15,6 +15,8 @@
#include "depfile_parser.h"
#include "util.h"
+#include <algorithm>
+
DepfileParser::DepfileParser(DepfileParserOptions options)
: options_(options)
{
@@ -29,9 +31,15 @@ DepfileParser::DepfileParser(DepfileParserOptions options)
// How do you end a line with a backslash? The netbsd Make docs suggest
// reading the result of a shell command echoing a backslash!
//
-// Rather than implement all of above, we do a simpler thing here:
-// Backslashes escape a set of characters (see "escapes" defined below),
-// otherwise they are passed through verbatim.
+// Rather than implement all of above, we follow what GCC/Clang produces:
+// Backslashes escape a space or hash sign.
+// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
+// followed by space.
+// When a space is preceded by 2N backslashes, it represents 2N backslashes at
+// the end of a filename.
+// A hash sign is escaped by a single backslash. All other backslashes remain
+// unchanged.
+//
// If anyone actually has depfiles that rely on the more complicated
// behavior we can adjust this.
bool DepfileParser::Parse(string* content, string* err) {
@@ -41,10 +49,8 @@ bool DepfileParser::Parse(string* content, string* err) {
char* in = &(*content)[0];
char* end = in + content->size();
bool have_target = false;
- bool have_secondary_target_on_this_rule = false;
- bool have_newline_since_primary_target = false;
- bool warned_distinct_target_lines = false;
bool parsing_targets = true;
+ bool poisoned_input = false;
while (in < end) {
bool have_newline = false;
// out: current output point (typically same as in, but can fall behind
@@ -68,12 +74,33 @@ bool DepfileParser::Parse(string* content, string* err) {
re2c:indent:string = " ";
nul = "\000";
- escape = [ \\#*[|\]];
newline = '\r'?'\n';
- '\\' escape {
- // De-escape backslashed character.
- *out++ = yych;
+ '\\\\'* '\\ ' {
+ // 2N+1 backslashes plus space -> N backslashes plus space.
+ int len = (int)(in - start);
+ int n = len / 2 - 1;
+ if (out < start)
+ memset(out, '\\', n);
+ out += n;
+ *out++ = ' ';
+ continue;
+ }
+ '\\\\'+ ' ' {
+ // 2N backslashes plus space -> 2N backslashes, end of filename.
+ int len = (int)(in - start);
+ if (out < start)
+ memset(out, '\\', len - 1);
+ out += len - 1;
+ break;
+ }
+ '\\'+ '#' {
+ // De-escape hash sign, but preserve other leading backslashes.
+ int len = (int)(in - start);
+ if (len > 2 && out < start)
+ memset(out, '\\', len - 2);
+ out += len - 2;
+ *out++ = '#';
continue;
}
'$$' {
@@ -81,13 +108,7 @@ bool DepfileParser::Parse(string* content, string* err) {
*out++ = '$';
continue;
}
- '\\' [^\000\r\n] {
- // Let backslash before other characters through verbatim.
- *out++ = '\\';
- *out++ = yych;
- continue;
- }
- [a-zA-Z0-9+,/_:.~()}{%@=!\x80-\xFF-]+ {
+ '\\'+ [^\000\r\n] | [a-zA-Z0-9+,/_:.~()}{%=@\x5B\x5D!\x80-\xFF-]+ {
// Got a span of plain text.
int len = (int)(in - start);
// Need to shift it over if we're overwriting backslashes.
@@ -125,41 +146,32 @@ bool DepfileParser::Parse(string* content, string* err) {
}
if (len > 0) {
- if (is_dependency) {
- if (have_secondary_target_on_this_rule) {
- if (!have_newline_since_primary_target) {
- *err = "depfile has multiple output paths";
- return false;
- } else if (options_.depfile_distinct_target_lines_action_ ==
- kDepfileDistinctTargetLinesActionError) {
- *err =
- "depfile has multiple output paths (on separate lines)"
- " [-w depfilemulti=err]";
+ StringPiece piece = StringPiece(filename, len);
+ // If we've seen this as an input before, skip it.
+ std::vector<StringPiece>::iterator pos = std::find(ins_.begin(), ins_.end(), piece);
+ if (pos == ins_.end()) {
+ if (is_dependency) {
+ if (poisoned_input) {
+ *err = "inputs may not also have inputs";
return false;
- } else {
- if (!warned_distinct_target_lines) {
- warned_distinct_target_lines = true;
- Warning("depfile has multiple output paths (on separate lines); "
- "continuing anyway [-w depfilemulti=warn]");
- }
- continue;
}
+ // New input.
+ ins_.push_back(piece);
+ } else {
+ // Check for a new output.
+ if (std::find(outs_.begin(), outs_.end(), piece) == outs_.end())
+ outs_.push_back(piece);
}
- ins_.push_back(StringPiece(filename, len));
- } else if (!out_.str_) {
- out_ = StringPiece(filename, len);
- } else if (out_ != StringPiece(filename, len)) {
- have_secondary_target_on_this_rule = true;
+ } else if (!is_dependency) {
+ // We've passed an input on the left side; reject new inputs.
+ poisoned_input = true;
}
}
if (have_newline) {
// A newline ends a rule so the next filename will be a new target.
parsing_targets = true;
- have_secondary_target_on_this_rule = false;
- if (have_target) {
- have_newline_since_primary_target = true;
- }
+ poisoned_input = false;
}
}
if (!have_target) {
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index 52fe7cd..bf1a0bc 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -34,7 +34,8 @@ TEST_F(DepfileParserTest, Basic) {
"build/ninja.o: ninja.cc ninja.h eval_env.h manifest_parser.h\n",
&err));
ASSERT_EQ("", err);
- EXPECT_EQ("build/ninja.o", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("build/ninja.o", parser_.outs_[0].AsString());
EXPECT_EQ(4u, parser_.ins_.size());
}
@@ -54,7 +55,8 @@ TEST_F(DepfileParserTest, Continuation) {
" bar.h baz.h\n",
&err));
ASSERT_EQ("", err);
- EXPECT_EQ("foo.o", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("foo.o", parser_.outs_[0].AsString());
EXPECT_EQ(2u, parser_.ins_.size());
}
@@ -65,7 +67,8 @@ TEST_F(DepfileParserTest, CarriageReturnContinuation) {
" bar.h baz.h\r\n",
&err));
ASSERT_EQ("", err);
- EXPECT_EQ("foo.o", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("foo.o", parser_.outs_[0].AsString());
EXPECT_EQ(2u, parser_.ins_.size());
}
@@ -79,8 +82,9 @@ TEST_F(DepfileParserTest, BackSlashes) {
" Project\\Thing\\Bar.tlb \\\n",
&err));
ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
EXPECT_EQ("Project\\Dir\\Build\\Release8\\Foo\\Foo.res",
- parser_.out_.AsString());
+ parser_.outs_[0].AsString());
EXPECT_EQ(4u, parser_.ins_.size());
}
@@ -90,8 +94,9 @@ TEST_F(DepfileParserTest, Spaces) {
"a\\ bc\\ def: a\\ b c d",
&err));
ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
EXPECT_EQ("a bc def",
- parser_.out_.AsString());
+ parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("a b",
parser_.ins_[0].AsString());
@@ -101,16 +106,39 @@ TEST_F(DepfileParserTest, Spaces) {
parser_.ins_[2].AsString());
}
+TEST_F(DepfileParserTest, MultipleBackslashes) {
+ // Successive 2N+1 backslashes followed by space (' ') are replaced by N >= 0
+ // backslashes and the space. A single backslash before hash sign is removed.
+ // Other backslashes remain untouched (including 2N backslashes followed by
+ // space).
+ string err;
+ EXPECT_TRUE(Parse(
+"a\\ b\\#c.h: \\\\\\\\\\ \\\\\\\\ \\\\share\\info\\\\#1",
+ &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("a b#c.h",
+ parser_.outs_[0].AsString());
+ ASSERT_EQ(3u, parser_.ins_.size());
+ EXPECT_EQ("\\\\ ",
+ parser_.ins_[0].AsString());
+ EXPECT_EQ("\\\\\\\\",
+ parser_.ins_[1].AsString());
+ EXPECT_EQ("\\\\share\\info\\#1",
+ parser_.ins_[2].AsString());
+}
+
TEST_F(DepfileParserTest, Escapes) {
// Put backslashes before a variety of characters, see which ones make
// it through.
string err;
EXPECT_TRUE(Parse(
-"\\!\\@\\#$$\\%\\^\\&\\\\:",
+"\\!\\@\\#$$\\%\\^\\&\\[\\]\\\\:",
&err));
ASSERT_EQ("", err);
- EXPECT_EQ("\\!\\@#$\\%\\^\\&\\",
- parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("\\!\\@#$\\%\\^\\&\\[\\]\\\\",
+ parser_.outs_[0].AsString());
ASSERT_EQ(0u, parser_.ins_.size());
}
@@ -123,11 +151,12 @@ TEST_F(DepfileParserTest, SpecialChars) {
" en@quot.header~ t+t-x!=1 \\\n"
" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif\\\n"
" Fu\303\244ball\\\n"
-" a\\[1\\]b@2%c",
+" a[1]b@2%c",
&err));
ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
- parser_.out_.AsString());
+ parser_.outs_[0].AsString());
ASSERT_EQ(5u, parser_.ins_.size());
EXPECT_EQ("en@quot.header~",
parser_.ins_[0].AsString());
@@ -145,18 +174,25 @@ TEST_F(DepfileParserTest, UnifyMultipleOutputs) {
// check that multiple duplicate targets are properly unified
string err;
EXPECT_TRUE(Parse("foo foo: x y z", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
EXPECT_EQ("z", parser_.ins_[2].AsString());
}
-TEST_F(DepfileParserTest, RejectMultipleDifferentOutputs) {
- // check that multiple different outputs are rejected by the parser
+TEST_F(DepfileParserTest, MultipleDifferentOutputs) {
+ // check that multiple different outputs are accepted by the parser
string err;
- EXPECT_FALSE(Parse("foo bar: x y z", &err));
- ASSERT_EQ("depfile has multiple output paths", err);
+ EXPECT_TRUE(Parse("foo bar: x y z", &err));
+ ASSERT_EQ(2u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
+ ASSERT_EQ("bar", parser_.outs_[1].AsString());
+ ASSERT_EQ(3u, parser_.ins_.size());
+ EXPECT_EQ("x", parser_.ins_[0].AsString());
+ EXPECT_EQ("y", parser_.ins_[1].AsString());
+ EXPECT_EQ("z", parser_.ins_[2].AsString());
}
TEST_F(DepfileParserTest, MultipleEmptyRules) {
@@ -164,7 +200,8 @@ TEST_F(DepfileParserTest, MultipleEmptyRules) {
EXPECT_TRUE(Parse("foo: x\n"
"foo: \n"
"foo:\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(1u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
}
@@ -175,7 +212,8 @@ TEST_F(DepfileParserTest, UnifyMultipleRulesLF) {
"foo: y\n"
"foo \\\n"
"foo: z\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -188,7 +226,8 @@ TEST_F(DepfileParserTest, UnifyMultipleRulesCRLF) {
"foo: y\r\n"
"foo \\\r\n"
"foo: z\r\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -201,7 +240,8 @@ TEST_F(DepfileParserTest, UnifyMixedRulesLF) {
" y\n"
"foo \\\n"
"foo: z\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -214,7 +254,8 @@ TEST_F(DepfileParserTest, UnifyMixedRulesCRLF) {
" y\r\n"
"foo \\\r\n"
"foo: z\r\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -226,7 +267,8 @@ TEST_F(DepfileParserTest, IndentedRulesLF) {
EXPECT_TRUE(Parse(" foo: x\n"
" foo: y\n"
" foo: z\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -238,7 +280,8 @@ TEST_F(DepfileParserTest, IndentedRulesCRLF) {
EXPECT_TRUE(Parse(" foo: x\r\n"
" foo: y\r\n"
" foo: z\r\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -251,7 +294,8 @@ TEST_F(DepfileParserTest, TolerateMP) {
"x:\n"
"y:\n"
"z:\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
@@ -266,25 +310,34 @@ TEST_F(DepfileParserTest, MultipleRulesTolerateMP) {
"y:\n"
"foo: z\n"
"z:\n", &err));
- ASSERT_EQ("foo", parser_.out_.AsString());
+ ASSERT_EQ(1u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("x", parser_.ins_[0].AsString());
EXPECT_EQ("y", parser_.ins_[1].AsString());
EXPECT_EQ("z", parser_.ins_[2].AsString());
}
-TEST_F(DepfileParserTest, MultipleRulesRejectDifferentOutputs) {
- // check that multiple different outputs are rejected by the parser
+TEST_F(DepfileParserTest, MultipleRulesDifferentOutputs) {
+ // check that multiple different outputs are accepted by the parser
// when spread across multiple rules
- DepfileParserOptions parser_opts;
- parser_opts.depfile_distinct_target_lines_action_ =
- kDepfileDistinctTargetLinesActionError;
- DepfileParser parser(parser_opts);
string err;
- string input =
- "foo: x y\n"
- "bar: y z\n";
- EXPECT_FALSE(parser.Parse(&input, &err));
- ASSERT_EQ("depfile has multiple output paths (on separate lines)"
- " [-w depfilemulti=err]", err);
+ EXPECT_TRUE(Parse("foo: x y\n"
+ "bar: y z\n", &err));
+ ASSERT_EQ(2u, parser_.outs_.size());
+ ASSERT_EQ("foo", parser_.outs_[0].AsString());
+ ASSERT_EQ("bar", parser_.outs_[1].AsString());
+ ASSERT_EQ(3u, parser_.ins_.size());
+ EXPECT_EQ("x", parser_.ins_[0].AsString());
+ EXPECT_EQ("y", parser_.ins_[1].AsString());
+ EXPECT_EQ("z", parser_.ins_[2].AsString());
+}
+
+TEST_F(DepfileParserTest, BuggyMP) {
+ std::string err;
+ EXPECT_FALSE(Parse("foo: x y z\n"
+ "x: alsoin\n"
+ "y:\n"
+ "z:\n", &err));
+ ASSERT_EQ("inputs may not also have inputs", err);
}
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 @@ void DepsLog::Close() {
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 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
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 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
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 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
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 @@ using namespace std;
#include <stdio.h>
+#include "load_status.h"
#include "timestamp.h"
struct Node;
@@ -84,7 +85,7 @@ struct DepsLog {
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/disk_interface.cc b/src/disk_interface.cc
index d4c2fb0..dc297c4 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -202,19 +202,13 @@ TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
// that it doesn't exist.
if (st.st_mtime == 0)
return 1;
-#if defined(__APPLE__) && !defined(_POSIX_C_SOURCE)
+#if defined(_AIX)
+ return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
+#elif defined(__APPLE__)
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.
+#elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
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
diff --git a/src/graph.cc b/src/graph.cc
index add7868..28a9653 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -14,6 +14,7 @@
#include "graph.h"
+#include <algorithm>
#include <assert.h>
#include <stdio.h>
@@ -222,8 +223,8 @@ bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input,
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()) {
@@ -323,19 +324,17 @@ bool Edge::AllInputsReady() const {
struct EdgeEnv : public Env {
enum EscapeKind { kShellEscape, kDoNotEscape };
- EdgeEnv(Edge* edge, EscapeKind escape)
+ EdgeEnv(const Edge* const edge, const EscapeKind escape)
: edge_(edge), escape_in_out_(escape), recursive_(false) {}
virtual string LookupVariable(const string& var);
/// Given a span of Nodes, construct a list of paths suitable for a command
/// line.
- string MakePathList(vector<Node*>::iterator begin,
- vector<Node*>::iterator end,
- char sep);
+ std::string MakePathList(const Node* const* span, size_t size, char sep) const;
private:
vector<string> lookups_;
- Edge* edge_;
+ const Edge* const edge_;
EscapeKind escape_in_out_;
bool recursive_;
};
@@ -344,14 +343,15 @@ string EdgeEnv::LookupVariable(const string& var) {
if (var == "in" || var == "in_newline") {
int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ -
edge_->order_only_deps_;
- return MakePathList(edge_->inputs_.begin(),
- edge_->inputs_.begin() + explicit_deps_count,
+#if __cplusplus >= 201103L
+ return MakePathList(edge_->inputs_.data(), explicit_deps_count,
+#else
+ return MakePathList(&edge_->inputs_[0], explicit_deps_count,
+#endif
var == "in" ? ' ' : '\n');
} else if (var == "out") {
int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_;
- return MakePathList(edge_->outputs_.begin(),
- edge_->outputs_.begin() + explicit_outs_count,
- ' ');
+ return MakePathList(&edge_->outputs_[0], explicit_outs_count, ' ');
}
if (recursive_) {
@@ -376,16 +376,15 @@ string EdgeEnv::LookupVariable(const string& var) {
return edge_->env_->LookupWithFallback(var, eval, this);
}
-string EdgeEnv::MakePathList(vector<Node*>::iterator begin,
- vector<Node*>::iterator end,
- char sep) {
+std::string EdgeEnv::MakePathList(const Node* const* const span,
+ const size_t size, const char sep) const {
string result;
- for (vector<Node*>::iterator i = begin; i != end; ++i) {
+ for (const Node* const* i = span; i != span + size; ++i) {
if (!result.empty())
result.push_back(sep);
const string& path = (*i)->PathDecanonicalized();
if (escape_in_out_ == kShellEscape) {
-#if _WIN32
+#ifdef _WIN32
GetWin32EscapedString(path, &result);
#else
GetShellEscapedString(path, &result);
@@ -397,7 +396,7 @@ string EdgeEnv::MakePathList(vector<Node*>::iterator begin,
return result;
}
-string Edge::EvaluateCommand(bool incl_rsp_file) {
+std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
string command = GetBinding("command");
if (incl_rsp_file) {
string rspfile_content = GetBinding("rspfile_content");
@@ -407,26 +406,26 @@ string Edge::EvaluateCommand(bool incl_rsp_file) {
return command;
}
-string Edge::GetBinding(const string& key) {
+std::string Edge::GetBinding(const std::string& key) const {
EdgeEnv env(this, EdgeEnv::kShellEscape);
return env.LookupVariable(key);
}
-bool Edge::GetBindingBool(const string& key) {
+bool Edge::GetBindingBool(const string& key) const {
return !GetBinding(key).empty();
}
-string Edge::GetUnescapedDepfile() {
+string Edge::GetUnescapedDepfile() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("depfile");
}
-string Edge::GetUnescapedDyndep() {
+string Edge::GetUnescapedDyndep() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("dyndep");
}
-string Edge::GetUnescapedRspfile() {
+std::string Edge::GetUnescapedRspfile() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("rspfile");
}
@@ -513,6 +512,17 @@ bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) {
return true;
}
+struct matches {
+ matches(std::vector<StringPiece>::iterator i) : i_(i) {}
+
+ bool operator()(const Node* node) const {
+ StringPiece opath = StringPiece(node->path());
+ return *i_ == opath;
+ }
+
+ std::vector<StringPiece>::iterator i_;
+};
+
bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
string* err) {
METRIC_RECORD("depfile load");
@@ -543,9 +553,15 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
return false;
}
+ if (depfile.outs_.empty()) {
+ *err = path + ": no outputs declared";
+ return false;
+ }
+
uint64_t unused;
- if (!CanonicalizePath(const_cast<char*>(depfile.out_.str_),
- &depfile.out_.len_, &unused, err)) {
+ std::vector<StringPiece>::iterator primary_out = depfile.outs_.begin();
+ if (!CanonicalizePath(const_cast<char*>(primary_out->str_),
+ &primary_out->len_, &unused, err)) {
*err = path + ": " + *err;
return false;
}
@@ -554,12 +570,22 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
// mark the edge as dirty.
Node* first_output = edge->outputs_[0];
StringPiece opath = StringPiece(first_output->path());
- if (opath != depfile.out_) {
+ if (opath != *primary_out) {
EXPLAIN("expected depfile '%s' to mention '%s', got '%s'", path.c_str(),
- first_output->path().c_str(), depfile.out_.AsString().c_str());
+ first_output->path().c_str(), primary_out->AsString().c_str());
return false;
}
+ // Ensure that all mentioned outputs are outputs of the edge.
+ for (std::vector<StringPiece>::iterator o = depfile.outs_.begin();
+ o != depfile.outs_.end(); ++o) {
+ matches m(o);
+ if (std::find_if(edge->outputs_.begin(), edge->outputs_.end(), m) == edge->outputs_.end()) {
+ *err = path + ": depfile mentions '" + o->AsString() + "' as an output, but no such output was declared";
+ return false;
+ }
+ }
+
// Preallocate space in edge->inputs_ to be filled in below.
vector<Node*>::iterator implicit_dep =
PreallocateSpace(edge, depfile.ins_.size());
@@ -584,7 +610,7 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) {
// NOTE: deps are only supported for single-target edges.
Node* output = edge->outputs_[0];
- DepsLog::Deps* deps = deps_log_->GetDeps(output);
+ DepsLog::Deps* deps = deps_log_ ? deps_log_->GetDeps(output) : NULL;
if (!deps) {
EXPLAIN("deps for '%s' are missing", output->path().c_str());
return false;
diff --git a/src/graph.h b/src/graph.h
index 75edbc5..2fa54af 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -155,18 +155,18 @@ struct Edge {
/// Expand all variables in a command and return it as a string.
/// If incl_rsp_file is enabled, the string will also contain the
/// full contents of a response file (if applicable)
- string EvaluateCommand(bool incl_rsp_file = false);
+ std::string EvaluateCommand(bool incl_rsp_file = false) const;
/// Returns the shell-escaped value of |key|.
- string GetBinding(const string& key);
- bool GetBindingBool(const string& key);
+ std::string GetBinding(const string& key) const;
+ bool GetBindingBool(const string& key) const;
/// Like GetBinding("depfile"), but without shell escaping.
- string GetUnescapedDepfile();
+ string GetUnescapedDepfile() const;
/// Like GetBinding("dyndep"), but without shell escaping.
- string GetUnescapedDyndep();
+ string GetUnescapedDyndep() const;
/// Like GetBinding("rspfile"), but without shell escaping.
- string GetUnescapedRspfile();
+ std::string GetUnescapedRspfile() const;
void Dump(const char* prefix="") const;
@@ -310,7 +310,7 @@ struct DependencyScan {
/// 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/graph_test.cc b/src/graph_test.cc
index c8cca1c..660943f 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -218,7 +218,7 @@ TEST_F(GraphTest, VarInOutPathEscaping) {
"build a$ b: cat no'space with$ space$$ no\"space2\n"));
Edge* edge = GetNode("a b")->in_edge();
-#if _WIN32
+#ifdef _WIN32
EXPECT_EQ("cat no'space \"with space$\" \"no\\\"space2\" > \"a b\"",
edge->EvaluateCommand());
#else
diff --git a/src/line_printer.cc b/src/line_printer.cc
index 55469d9..c93173e 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -58,7 +58,9 @@ LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
if (supports_color_) {
DWORD mode;
if (GetConsoleMode(console_, &mode)) {
- SetConsoleMode(console_, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+ if (!SetConsoleMode(console_, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
+ supports_color_ = false;
+ }
}
}
#endif
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/manifest_parser.cc b/src/manifest_parser.cc
index 2011368..bb53dc2 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -228,7 +228,7 @@ bool ManifestParser::ParseEdge(string* err) {
for (;;) {
EvalString out;
if (!lexer_.ReadPath(&out, err))
- return err;
+ return false;
if (out.empty())
break;
outs.push_back(out);
@@ -266,7 +266,7 @@ bool ManifestParser::ParseEdge(string* err) {
for (;;) {
EvalString in;
if (!lexer_.ReadPath(&in, err))
- return err;
+ return false;
if (in.empty())
break;
ins.push_back(in);
@@ -379,14 +379,6 @@ bool ManifestParser::ParseEdge(string* err) {
}
}
- // Multiple outputs aren't (yet?) supported with depslog.
- string deps_type = edge->GetBinding("deps");
- if (!deps_type.empty() && edge->outputs_.size() > 1) {
- return lexer_.Error("multiple outputs aren't (yet?) supported by depslog; "
- "bring this up on the mailing list if it affects you",
- err);
- }
-
// Lookup, validate, and save any dyndep binding. It will be used later
// to load generated dependency information dynamically, but it must
// be one of our manifest-specified inputs.
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index f2b7467..f4aee2d 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -858,11 +858,10 @@ TEST_F(ParserTest, MultipleOutputsWithDeps) {
State local_state;
ManifestParser parser(&local_state, NULL);
string err;
- EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n"
+ EXPECT_TRUE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n"
"build a.o b.o: cc c.cc\n",
&err));
- EXPECT_EQ("input:5: multiple outputs aren't (yet?) supported by depslog; "
- "bring this up on the mailing list if it affects you\n", err);
+ EXPECT_EQ("", err);
}
TEST_F(ParserTest, SubNinja) {
diff --git a/src/ninja.cc b/src/ninja.cc
index 3b9fab5..08f717f 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"
@@ -73,10 +74,6 @@ struct Options {
/// Whether phony cycles should warn or print an error.
bool phony_cycle_should_err;
-
- /// Whether a depfile with multiple targets on separate lines should
- /// warn or print an error.
- bool depfile_distinct_target_lines_should_err;
};
/// The Ninja main() loads up a series of data structures; various tools need
@@ -126,15 +123,16 @@ struct NinjaMain : public BuildLogUser {
int ToolCleanDead(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.
@@ -154,6 +152,9 @@ struct NinjaMain : public BuildLogUser {
void DumpMetrics();
virtual bool IsPathDead(StringPiece s) const {
+ Node* n = state_.LookupNode(s);
+ 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
// have an in edge if one of its inputs is another output that's in the deps
@@ -735,7 +736,8 @@ enum EvaluateCommandMode {
ECM_NORMAL,
ECM_EXPAND_RSPFILE
};
-string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) {
+std::string EvaluateCommandWithRspfile(const Edge* edge,
+ const EvaluateCommandMode mode) {
string command = edge->EvaluateCommand();
if (mode == ECM_NORMAL)
return command;
@@ -759,6 +761,19 @@ string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) {
return command;
}
+void printCompdb(const char* const directory, const Edge* const edge,
+ const EvaluateCommandMode eval_mode) {
+ printf("\n {\n \"directory\": \"");
+ EncodeJSONString(directory);
+ printf("\",\n \"command\": \"");
+ EncodeJSONString(EvaluateCommandWithRspfile(edge, eval_mode).c_str());
+ printf("\",\n \"file\": \"");
+ EncodeJSONString(edge->inputs_[0]->path().c_str());
+ printf("\",\n \"output\": \"");
+ EncodeJSONString(edge->outputs_[0]->path().c_str());
+ printf("\"\n }");
+}
+
int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
char* argv[]) {
// The compdb tool uses getopt, and expects argv[0] to contain the name of
@@ -792,12 +807,14 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
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;
}
@@ -807,22 +824,21 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
e != state_.edges_.end(); ++e) {
if ((*e)->inputs_.empty())
continue;
- for (int i = 0; i != argc; ++i) {
- if ((*e)->rule_->name() == argv[i]) {
- if (!first)
- putchar(',');
-
- printf("\n {\n \"directory\": \"");
- EncodeJSONString(&cwd[0]);
- printf("\",\n \"command\": \"");
- 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;
+ if (argc == 0) {
+ if (!first) {
+ putchar(',');
+ }
+ printCompdb(&cwd[0], *e, eval_mode);
+ first = false;
+ } else {
+ for (int i = 0; i != argc; ++i) {
+ if ((*e)->rule_->name() == argv[i]) {
+ if (!first) {
+ putchar(',');
+ }
+ printCompdb(&cwd[0], *e, eval_mode);
+ first = false;
+ }
}
}
}
@@ -835,13 +851,53 @@ int NinjaMain::ToolRecompact(const Options* options, int argc, char* argv[]) {
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 =
@@ -894,6 +950,8 @@ const Tool* ChooseTool(const string& tool_name) {
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 },
{ "cleandead", "clean built files that are no longer produced by the manifest",
@@ -981,7 +1039,6 @@ bool WarningEnable(const string& name, Options* options) {
printf("warning flags:\n"
" dupbuild={err,warn} multiple build lines for one target\n"
" phonycycle={err,warn} phony build statement references itself\n"
-" depfilemulti={err,warn} depfile has multiple output paths on separate lines\n"
);
return false;
} else if (name == "dupbuild=err") {
@@ -996,11 +1053,9 @@ bool WarningEnable(const string& name, Options* options) {
} else if (name == "phonycycle=warn") {
options->phony_cycle_should_err = false;
return true;
- } else if (name == "depfilemulti=err") {
- options->depfile_distinct_target_lines_should_err = true;
- return true;
- } else if (name == "depfilemulti=warn") {
- options->depfile_distinct_target_lines_should_err = false;
+ } else if (name == "depfilemulti=err" ||
+ name == "depfilemulti=warn") {
+ Warning("deprecated warning 'depfilemulti'");
return true;
} else {
const char* suggestion =
@@ -1022,17 +1077,21 @@ bool NinjaMain::OpenBuildLog(bool recompact_only) {
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());
@@ -1057,17 +1116,21 @@ bool NinjaMain::OpenDepsLog(bool recompact_only) {
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());
@@ -1276,11 +1339,6 @@ NORETURN void real_main(int argc, char** argv) {
if (exit_code >= 0)
exit(exit_code);
- if (options.depfile_distinct_target_lines_should_err) {
- config.depfile_parser_options.depfile_distinct_target_lines_action_ =
- kDepfileDistinctTargetLinesActionError;
- }
-
if (options.working_dir) {
// The formatting of this string, complete with funny quotes, is
// so Emacs can properly identify that the cwd has changed for
diff --git a/src/test.cc b/src/test.cc
index a9816bc..8ba2297 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -33,6 +33,15 @@
#include "manifest_parser.h"
#include "util.h"
+#ifdef _AIX
+extern "C" {
+ // GCC "helpfully" strips the definition of mkdtemp out on AIX.
+ // The function is still present, so if we define it ourselves
+ // it will work perfectly fine.
+ extern char* mkdtemp(char* name_template);
+}
+#endif
+
namespace {
#ifdef _WIN32
diff --git a/src/util.cc b/src/util.cc
index ee810d6..4df2bb2 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -45,7 +45,7 @@
#elif defined(__SVR4) && defined(__sun)
#include <unistd.h>
#include <sys/loadavg.h>
-#elif defined(_AIX)
+#elif defined(_AIX) && !defined(__PASE__)
#include <libperfstat.h>
#elif defined(linux) || defined(__GLIBC__)
#include <sys/sysinfo.h>
@@ -481,9 +481,7 @@ string StripAnsiEscapeCodes(const string& in) {
int GetProcessorCount() {
#ifdef _WIN32
- SYSTEM_INFO info;
- GetNativeSystemInfo(&info);
- return info.dwNumberOfProcessors;
+ return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
#else
#ifdef CPU_COUNT
// The number of exposed processors might not represent the actual number of
@@ -564,6 +562,10 @@ double GetLoadAverage() {
return posix_compatible_load;
}
+#elif defined(__PASE__)
+double GetLoadAverage() {
+ return -0.0f;
+}
#elif defined(_AIX)
double GetLoadAverage() {
perfstat_cpu_total_t cpu_stats;
@@ -574,7 +576,7 @@ double GetLoadAverage() {
// Calculation taken from comment in libperfstats.h
return double(cpu_stats.loadavg[0]) / double(1 << SBITS);
}
-#elif defined(__UCLIBC__)
+#elif defined(__UCLIBC__) || (defined(__BIONIC__) && __ANDROID_API__ < 29)
double GetLoadAverage() {
struct sysinfo si;
if (sysinfo(&si) != 0)
@@ -594,6 +596,12 @@ double GetLoadAverage() {
#endif // _WIN32
string ElideMiddle(const string& str, size_t width) {
+ switch (width) {
+ case 0: return "";
+ case 1: return ".";
+ case 2: return "..";
+ case 3: return "...";
+ }
const int kMargin = 3; // Space for "...".
string result = str;
if (result.size() > width) {
diff --git a/src/util_test.cc b/src/util_test.cc
index d97b48c..b43788d 100644
--- a/src/util_test.cc
+++ b/src/util_test.cc
@@ -420,6 +420,10 @@ TEST(ElideMiddle, NothingToElide) {
string input = "Nothing to elide in this short string.";
EXPECT_EQ(input, ElideMiddle(input, 80));
EXPECT_EQ(input, ElideMiddle(input, 38));
+ EXPECT_EQ("", ElideMiddle(input, 0));
+ EXPECT_EQ(".", ElideMiddle(input, 1));
+ EXPECT_EQ("..", ElideMiddle(input, 2));
+ EXPECT_EQ("...", ElideMiddle(input, 3));
}
TEST(ElideMiddle, ElideInTheMiddle) {