Merge pull request #1674 from cameron314/bugfix/core-count
Fixed processor count detection on Windows
diff --git a/.clang-format b/.clang-format
index 1841c03..b8e9225 100644
--- a/.clang-format
+++ b/.clang-format
@@ -23,3 +23,4 @@
ConstructorInitializerAllOnOneLineOrOnePerLine: false
Cpp11BracedListStyle: false
IndentCaseLabels: false
+DerivePointerBinding: false
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..e0afd47
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,17 @@
+---
+Checks: '
+ ,readability-avoid-const-params-in-decls,
+ ,readability-inconsistent-declaration-parameter-name,
+ ,readability-non-const-parameter,
+ ,readability-redundant-string-cstr,
+ ,readability-redundant-string-init,
+ ,readability-simplify-boolean-expr,
+'
+WarningsAsErrors: '
+ ,readability-avoid-const-params-in-decls,
+ ,readability-inconsistent-declaration-parameter-name,
+ ,readability-non-const-parameter,
+ ,readability-redundant-string-cstr,
+ ,readability-redundant-string-init,
+ ,readability-simplify-boolean-expr,
+'
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/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 0000000..cd55262
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,146 @@
+name: Linux
+
+on:
+ pull_request:
+ push:
+ release:
+ types: published
+
+jobs:
+ build:
+ runs-on: [ubuntu-latest]
+ container:
+ image: centos:7
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ curl -L -O https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-Linux-x86_64.sh
+ chmod +x cmake-3.16.4-Linux-x86_64.sh
+ ./cmake-3.16.4-Linux-x86_64.sh --skip-license --prefix=/usr/local
+ curl -L -O https://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/7/x86_64/Packages/p/p7zip-16.02-10.el7.x86_64.rpm
+ curl -L -O https://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/7/x86_64/Packages/p/p7zip-plugins-16.02-10.el7.x86_64.rpm
+ rpm -U --quiet p7zip-16.02-10.el7.x86_64.rpm
+ rpm -U --quiet p7zip-plugins-16.02-10.el7.x86_64.rpm
+ yum install -y make gcc-c++ libasan clang-analyzer
+
+ - name: Build debug ninja
+ shell: bash
+ env:
+ CFLAGS: -fstack-protector-all -fsanitize=address
+ CXXFLAGS: -fstack-protector-all -fsanitize=address
+ run: |
+ scan-build -o scanlogs cmake -DCMAKE_BUILD_TYPE=Debug -B debug-build
+ scan-build -o scanlogs cmake --build debug-build --parallel --config Debug
+
+ - name: Test debug ninja
+ run: ./ninja_test
+ working-directory: debug-build
+
+ - name: Build release ninja
+ shell: bash
+ run: |
+ cmake -DCMAKE_BUILD_TYPE=Release -B release-build
+ cmake --build release-build --parallel --config Release
+ strip release-build/ninja
+
+ - name: Test release ninja
+ run: ./ninja_test
+ working-directory: release-build
+
+ - name: Create ninja archive
+ run: |
+ mkdir artifact
+ 7z a artifact/ninja-linux.zip ./release-build/ninja
+
+ # 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/ninja-linux.zip
+ asset_name: ninja-linux.zip
+ asset_content_type: application/zip
+
+ test:
+ runs-on: [ubuntu-latest]
+ container:
+ image: ubuntu:20.04
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ apt update
+ apt install -y python3-pytest ninja-build clang-tidy python3-pip clang
+ pip3 install cmake==3.17.*
+ - name: Configure (GCC)
+ run: cmake -Bbuild-gcc -DCMAKE_BUILD_TYPE=Debug -G'Ninja Multi-Config'
+
+ - name: Build (GCC, Debug)
+ run: cmake --build build-gcc --config Debug
+ - name: Unit tests (GCC, Debug)
+ run: ./build-gcc/Debug/ninja_test
+ - name: Python tests (GCC, Debug)
+ run: pytest-3 --color=yes ../..
+ working-directory: build-gcc/Debug
+
+ - name: Build (GCC, Release)
+ run: cmake --build build-gcc --config Release
+ - name: Unit tests (GCC, Release)
+ run: ./build-gcc/Release/ninja_test
+ - name: Python tests (GCC, Release)
+ run: pytest-3 --color=yes ../..
+ working-directory: build-gcc/Release
+
+ - name: Configure (Clang)
+ run: CC=clang CXX=clang++ cmake -Bbuild-clang -DCMAKE_BUILD_TYPE=Debug -G'Ninja Multi-Config' -DCMAKE_EXPORT_COMPILE_COMMANDS=1
+
+ - name: Build (Clang, Debug)
+ run: cmake --build build-clang --config Debug
+ - name: Unit tests (Clang, Debug)
+ run: ./build-clang/Debug/ninja_test
+ - name: Python tests (Clang, Debug)
+ run: pytest-3 --color=yes ../..
+ working-directory: build-clang/Debug
+
+ - name: Build (Clang, Release)
+ run: cmake --build build-clang --config Release
+ - name: Unit tests (Clang, Release)
+ run: ./build-clang/Release/ninja_test
+ - name: Python tests (Clang, Release)
+ run: pytest-3 --color=yes ../..
+ working-directory: build-clang/Release
+
+ - name: clang-tidy
+ run: /usr/lib/llvm-10/share/clang/run-clang-tidy.py -header-filter=src
+ working-directory: build-clang
+
+ build-with-python:
+ runs-on: [ubuntu-latest]
+ container:
+ image: ${{ matrix.image }}
+ strategy:
+ matrix:
+ image: ['ubuntu:14.04', 'ubuntu:16.04', 'ubuntu:18.04']
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: |
+ apt update
+ apt install -y g++ python3
+ - name: ${{ matrix.image }}
+ run: |
+ python3 configure.py --bootstrap
+ ./ninja all
+ ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
+ python3 misc/ninja_syntax_test.py
+ ./misc/output_test.py
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000..af79080
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,54 @@
+name: macOS
+
+on:
+ pull_request:
+ push:
+ release:
+ types: published
+
+jobs:
+ build:
+ runs-on: macos-11.0
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: brew install re2c p7zip cmake
+
+ - name: Build ninja
+ shell: bash
+ env:
+ MACOSX_DEPLOYMENT_TARGET: 10.12
+ run: |
+ sudo xcode-select -s /Applications/Xcode_12.2.app
+ cmake -Bbuild -GXcode '-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64'
+ cmake --build build --config Release
+
+ - name: Test ninja
+ run: ctest -C Release -vv
+ working-directory: build
+
+ - name: Create ninja archive
+ shell: bash
+ run: |
+ mkdir artifact
+ 7z a artifact/ninja-mac.zip ./build/Release/ninja
+
+ # 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/ninja-mac.zip
+ asset_name: ninja-mac.zip
+ asset_content_type: application/zip
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
new file mode 100644
index 0000000..04fc2f6
--- /dev/null
+++ b/.github/workflows/windows.yml
@@ -0,0 +1,51 @@
+name: Windows
+
+on:
+ pull_request:
+ push:
+ release:
+ types: published
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: choco install re2c
+
+ - name: Build ninja
+ shell: bash
+ run: |
+ cmake -DCMAKE_BUILD_TYPE=Release -B build
+ cmake --build build --parallel --config Release
+
+ - name: Test ninja
+ run: .\ninja_test.exe
+ working-directory: build/Release
+
+ - name: Create ninja archive
+ shell: bash
+ run: |
+ mkdir artifact
+ 7z a artifact/ninja-win.zip ./build/Release/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/ninja-win.zip
+ asset_name: ninja-win.zip
+ asset_content_type: application/zip
diff --git a/.gitignore b/.gitignore
index 98fbb21..fdca015 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,11 @@
# Visual Studio Code project files
/.vscode/
/.ccls-cache/
+
+# Qt Creator project files
+/CMakeLists.txt.user
+
+# clangd
+/.clangd/
+/compile_commands.json
+/.cache/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e5d7d2b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-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
- - python3 configure.py --bootstrap
- - ./ninja all
- - ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
- - ./misc/ninja_syntax_test.py
- - ./misc/output_test.py
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2390732..39348c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,12 +1,39 @@
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.15)
+
+include(CheckIncludeFileCXX)
+include(CheckIPOSupported)
+
project(ninja)
-if(MSVC)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /GR- /Zc:__cplusplus")
+# --- optional link-time optimization
+check_ipo_supported(RESULT lto_supported OUTPUT error)
+
+if(lto_supported)
+ message(STATUS "IPO / LTO enabled")
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
else()
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -fdiagnostics-color")
+ message(STATUS "IPO / LTO not supported: <${error}>")
endif()
+# --- compiler flags
+if(MSVC)
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+ string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+ string(APPEND CMAKE_CXX_FLAGS " /W4 /wd4100 /wd4267 /wd4706 /wd4702 /wd4244 /GR- /Zc:__cplusplus")
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+else()
+ include(CheckCXXCompilerFlag)
+ check_cxx_compiler_flag(-Wno-deprecated flag_no_deprecated)
+ if(flag_no_deprecated)
+ string(APPEND CMAKE_CXX_FLAGS " -Wno-deprecated")
+ endif()
+ check_cxx_compiler_flag(-fdiagnostics-color flag_color_diag)
+ if(flag_color_diag)
+ string(APPEND CMAKE_CXX_FLAGS " -fdiagnostics-color")
+ endif()
+endif()
+
+# --- optional re2c
find_program(RE2C re2c)
if(RE2C)
# the depfile parser and ninja lexers are generated using re2c.
@@ -15,15 +42,39 @@
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)
+ re2c(${PROJECT_SOURCE_DIR}/src/depfile_parser.in.cc ${PROJECT_BINARY_DIR}/depfile_parser.cc)
+ re2c(${PROJECT_SOURCE_DIR}/src/lexer.in.cc ${PROJECT_BINARY_DIR}/lexer.cc)
+ add_library(libninja-re2c OBJECT ${PROJECT_BINARY_DIR}/depfile_parser.cc ${PROJECT_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)
+# --- Check for 'browse' mode support
+function(check_platform_supports_browse_mode RESULT)
+ # Make sure the inline.sh script works on this platform.
+ # It uses the shell commands such as 'od', which may not be available.
+ execute_process(
+ COMMAND sh -c "echo 'TEST' | src/inline.sh var"
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ RESULT_VARIABLE inline_result
+ OUTPUT_QUIET
+ ERROR_QUIET
+ )
+ if(NOT inline_result EQUAL "0")
+ # The inline script failed, so browse mode is not supported.
+ set(${RESULT} "0" PARENT_SCOPE)
+ return()
+ endif()
+
+ # Now check availability of the unistd header
+ check_include_file_cxx(unistd.h PLATFORM_HAS_UNISTD_HEADER)
+ set(${RESULT} "${PLATFORM_HAS_UNISTD_HEADER}" PARENT_SCOPE)
+endfunction()
+
+check_platform_supports_browse_mode(platform_supports_ninja_browse)
+
# Core source files all build into ninja library.
add_library(libninja OBJECT
src/build_log.cc
@@ -61,42 +112,104 @@
endif()
else()
target_sources(libninja PRIVATE src/subprocess-posix.cc)
+ if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
+ target_sources(libninja PRIVATE src/getopt.c)
+ endif()
+
+ # Needed for perfstat_cpu_total
+ if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
+ target_link_libraries(libninja PUBLIC "-lperfstat")
+ endif()
endif()
#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()
+
+# On IBM i (identified as "OS400" for compatibility reasons) and AIX, this fixes missing
+# PRId64 (and others) at compile time in C++ sources
+if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
+ string(APPEND CMAKE_CXX_FLAGS " -D__STDC_FORMAT_MACROS")
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)
+# Adds browse mode into the ninja binary if it's supported by the host platform.
+if(platform_supports_ninja_browse)
+ # Inlines src/browse.py into the browse_py.h header, so that it can be included
+ # by src/browse.cc
+ add_custom_command(
+ OUTPUT build/browse_py.h
+ MAIN_DEPENDENCY src/browse.py
+ DEPENDS src/inline.sh
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/build
+ COMMAND src/inline.sh kBrowsePy
+ < src/browse.py
+ > ${CMAKE_BINARY_DIR}/build/browse_py.h
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ VERBATIM
+ )
-enable_testing()
-add_test(NinjaTest ninja_test)
+ target_compile_definitions(ninja PRIVATE NINJA_HAVE_BROWSE)
+ target_sources(ninja PRIVATE src/browse.cc)
+ set_source_files_properties(src/browse.cc
+ PROPERTIES
+ OBJECT_DEPENDS "${CMAKE_BINARY_DIR}/build/browse_py.h"
+ INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR}"
+ COMPILE_DEFINITIONS NINJA_PYTHON="python"
+ )
+endif()
+
+include(CTest)
+if(BUILD_TESTING)
+ # 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)
+
+ foreach(perftest
+ build_log_perftest
+ canon_perftest
+ clparser_perftest
+ depfile_parser_perftest
+ hash_collision_bench
+ manifest_parser_perftest
+ )
+ add_executable(${perftest} src/${perftest}.cc)
+ target_link_libraries(${perftest} PRIVATE libninja libninja-re2c)
+ endforeach()
+
+ if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
+ # These tests require more memory than will fit in the standard AIX shared stack/heap (256M)
+ target_link_libraries(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000")
+ target_link_libraries(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000")
+ endif()
+
+ add_test(NAME NinjaTest COMMAND ninja_test)
+endif()
+
+install(TARGETS ninja DESTINATION bin)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..be1fc02
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,34 @@
+# How to successfully make changes to Ninja
+
+We're very wary of changes that increase the complexity of Ninja (in particular,
+new build file syntax or command-line flags) or increase the maintenance burden
+of Ninja. Ninja is already successfully used by hundreds of developers for large
+projects and it already achieves (most of) the goals we set out for it to do.
+It's probably best to discuss new feature ideas on the
+[mailing list](https://groups.google.com/forum/#!forum/ninja-build) or in an
+issue before creating a PR.
+
+## Coding guidelines
+
+Generally it's the
+[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with
+a few additions:
+
+* Any code merged into the Ninja codebase which will be part of the main
+ executable must compile as C++03. You may use C++11 features in a test or an
+ unimportant tool if you guard your code with `#if __cplusplus >= 201103L`.
+* We have used `using namespace std;` a lot in the past. For new contributions,
+ please try to avoid relying on it and instead whenever possible use `std::`.
+ However, please do not change existing code simply to add `std::` unless your
+ contribution already needs to change that line of code anyway.
+* All source files should have the Google Inc. license header.
+* Use `///` for [Doxygen](http://www.doxygen.nl/) (use `\a` to refer to
+ arguments).
+* It's not necessary to document each argument, especially when they're
+ relatively self-evident (e.g. in
+ `CanonicalizePath(string* path, string* err)`, the arguments are hopefully
+ obvious).
+
+If you're unsure about code formatting, please use
+[clang-format](https://clang.llvm.org/docs/ClangFormat.html). However, please do
+not format code that is not otherwise part of your contribution.
diff --git a/HACKING.md b/HACKING.md
deleted file mode 100644
index bd6fec7..0000000
--- a/HACKING.md
+++ /dev/null
@@ -1,252 +0,0 @@
-## Basic overview
-
-`./configure.py` generates the `build.ninja` files used to build
-ninja. It accepts various flags to adjust build parameters.
-Run './configure.py --help' for more configuration options.
-
-The primary build target of interest is `ninja`, but when hacking on
-Ninja your changes should be testable so it's more useful to build and
-run `ninja_test` when developing.
-
-### Bootstrapping
-
-Ninja is built using itself. To bootstrap the first binary, run the
-configure script as `./configure.py --bootstrap`. This first compiles
-all non-test source files together, then re-builds Ninja using itself.
-You should end up with a `ninja` binary (or `ninja.exe`) in the project root.
-
-#### Windows
-
-On Windows, you'll need to install Python to run `configure.py`, and
-run everything under a Visual Studio Tools Command Prompt (or after
-running `vcvarsall` in a normal command prompt).
-
-For other combinations such as gcc/clang you will need the compiler
-(gcc/cl) in your PATH and you will have to set the appropriate
-platform configuration script.
-
-See below if you want to use mingw or some other compiler instead of
-Visual Studio.
-
-##### Using Visual Studio
-Assuming that you now have Python installed, then the steps for building under
-Windows using Visual Studio are:
-
-Clone and checkout the latest release (or whatever branch you want). You
-can do this in either a command prompt or by opening a git bash prompt:
-
-```
- $ git clone git://github.com/ninja-build/ninja.git && cd ninja
- $ git checkout release
-```
-
-Then:
-
-1. Open a Windows command prompt in the folder where you checked out ninja.
-2. Select the Microsoft build environment by running
-`vcvarsall.bat` with the appropriate environment.
-3. Build ninja and test it.
-
-The steps for a Visual Studio 2015 64-bit build are outlined here:
-
-```
- > "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
- > python configure.py --bootstrap
- > ninja --help
-```
-Copy the ninja executable to another location, if desired, e.g. C:\local\Ninja.
-
-Finally add the path where ninja.exe is to the PATH variable.
-
-### Adjusting build flags
-
-Build in "debug" mode while developing (disables optimizations and builds
-way faster on Windows):
-
- ./configure.py --debug
-
-To use clang, set `CXX`:
-
- CXX=clang++ ./configure.py
-
-## How to successfully make changes to Ninja
-
-Github pull requests are convenient for me to merge (I can just click
-a button and it's all handled server-side), but I'm also comfortable
-accepting pre-github git patches (via `send-email` etc.).
-
-Good pull requests have all of these attributes:
-
-* Are scoped to one specific issue
-* Include a test to demonstrate their correctness
-* Update the docs where relevant
-* Match the Ninja coding style (see below)
-* Don't include a mess of "oops, fix typo" commits
-
-These are typically merged without hesitation. If a change is lacking
-any of the above I usually will ask you to fix it, though there are
-obvious exceptions (fixing typos in comments don't need tests).
-
-I am very wary of changes that increase the complexity of Ninja (in
-particular, new build file syntax or command-line flags) or increase
-the maintenance burden of Ninja. Ninja is already successfully used
-by hundreds of developers for large projects and it already achieves
-(most of) the goals I set out for it to do. It's probably best to
-discuss new feature ideas on the [mailing list](https://groups.google.com/forum/#!forum/ninja-build)
-before I shoot down your patch.
-
-## Testing
-
-### Test-driven development
-
-Set your build command to
-
- ./ninja ninja_test && ./ninja_test --gtest_filter=MyTest.Name
-
-now you can repeatedly run that while developing until the tests pass
-(I frequently set it as my compilation command in Emacs). Remember to
-build "all" before committing to verify the other source still works!
-
-## Testing performance impact of changes
-
-If you have a Chrome build handy, it's a good test case. There's a
-script at `misc/measure.py` that repeatedly runs a command (to address
-variance) and summarizes its runtime. E.g.
-
- path/to/misc/measure.py path/to/my/ninja chrome
-
-For changing the depfile parser, you can also build `parser_perftest`
-and run that directly on some representative input files.
-
-## Coding guidelines
-
-Generally it's the [Google C++ coding style][], but in brief:
-
-* Function name are camelcase.
-* Member methods are camelcase, except for trivial getters which are
- underscore separated.
-* Local variables are underscore separated.
-* Member variables are underscore separated and suffixed by an extra
- underscore.
-* Two spaces indentation.
-* Opening braces is at the end of line.
-* Lines are 80 columns maximum.
-* All source files should have the Google Inc. license header.
-
-[Google C++ coding style]: https://google.github.io/styleguide/cppguide.html
-
-## Documentation
-
-### Style guidelines
-
-* Use `///` for doxygen.
-* Use `\a` to refer to arguments.
-* It's not necessary to document each argument, especially when they're
- relatively self-evident (e.g. in `CanonicalizePath(string* path, string* err)`,
- the arguments are hopefully obvious)
-
-### Building the manual
-
- sudo apt-get install asciidoc --no-install-recommends
- ./ninja manual
-
-### Building the code documentation
-
- sudo apt-get install doxygen
- ./ninja doxygen
-
-## Building for Windows
-
-While developing, it's helpful to copy `ninja.exe` to another name like
-`n.exe`; otherwise, rebuilds will be unable to write `ninja.exe` because
-it's locked while in use.
-
-### Via Visual Studio
-
-* Install Visual Studio (Express is fine), [Python for Windows][],
- and (if making changes) googletest (see above instructions)
-* In a Visual Studio command prompt: `python configure.py --bootstrap`
-
-[Python for Windows]: http://www.python.org/getit/windows/
-
-### Via mingw on Windows (not well supported)
-
-* Install mingw, msys, and python
-* In the mingw shell, put Python in your path, and
- `python configure.py --bootstrap`
-* To reconfigure, run `python configure.py`
-* Remember to strip the resulting executable if size matters to you
-
-### Via mingw on Linux (not well supported)
-
-Setup on Ubuntu Lucid:
-* `sudo apt-get install gcc-mingw32 wine`
-* `export CC=i586-mingw32msvc-cc CXX=i586-mingw32msvc-c++ AR=i586-mingw32msvc-ar`
-
-Setup on Ubuntu Precise:
-* `sudo apt-get install gcc-mingw-w64-i686 g++-mingw-w64-i686 wine`
-* `export CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ AR=i686-w64-mingw32-ar`
-
-Setup on Arch:
-* Uncomment the `[multilib]` section of `/etc/pacman.conf` and `sudo pacman -Sy`.
-* `sudo pacman -S mingw-w64-gcc wine`
-* `export CC=x86_64-w64-mingw32-cc CXX=x86_64-w64-mingw32-c++ AR=x86_64-w64-mingw32-ar`
-* `export CFLAGS=-I/usr/x86_64-w64-mingw32/include`
-
-Then run:
-* `./configure.py --platform=mingw --host=linux`
-* Build `ninja.exe` using a Linux ninja binary: `/path/to/linux/ninja`
-* Run: `./ninja.exe` (implicitly runs through wine(!))
-
-### Using Microsoft compilers on Linux (extremely flaky)
-
-The trick is to install just the compilers, and not all of Visual Studio,
-by following [these instructions][win7sdk].
-
-[win7sdk]: http://www.kegel.com/wine/cl-howto-win7sdk.html
-
-### Using gcov
-
-Do a clean debug build with the right flags:
-
- CFLAGS=-coverage LDFLAGS=-coverage ./configure.py --debug
- ninja -t clean ninja_test && ninja ninja_test
-
-Run the test binary to generate `.gcda` and `.gcno` files in the build
-directory, then run gcov on the .o files to generate `.gcov` files in the
-root directory:
-
- ./ninja_test
- gcov build/*.o
-
-Look at the generated `.gcov` files directly, or use your favorite gcov viewer.
-
-### Using afl-fuzz
-
-Build with afl-clang++:
-
- CXX=path/to/afl-1.20b/afl-clang++ ./configure.py
- ninja
-
-Then run afl-fuzz like so:
-
- afl-fuzz -i misc/afl-fuzz -o /tmp/afl-fuzz-out ./ninja -n -f @@
-
-You can pass `-x misc/afl-fuzz-tokens` to use the token dictionary. In my
-testing, that did not seem more effective though.
-
-#### Using afl-fuzz with asan
-
-If you want to use asan (the `isysroot` bit is only needed on OS X; if clang
-can't find C++ standard headers make sure your LLVM checkout includes a libc++
-checkout and has libc++ installed in the build directory):
-
- CFLAGS="-fsanitize=address -isysroot $(xcrun -show-sdk-path)" \
- LDFLAGS=-fsanitize=address CXX=path/to/afl-1.20b/afl-clang++ \
- ./configure.py
- AFL_CXX=path/to/clang++ ninja
-
-Make sure ninja can find the asan runtime:
-
- DYLD_LIBRARY_PATH=path/to//lib/clang/3.7.0/lib/darwin/ \
- afl-fuzz -i misc/afl-fuzz -o /tmp/afl-fuzz-out ./ninja -n -f @@
diff --git a/README b/README
deleted file mode 100644
index a1535ff..0000000
--- a/README
+++ /dev/null
@@ -1,21 +0,0 @@
-Ninja is a small build system with a focus on speed.
-https://ninja-build.org/
-
-See the manual -- https://ninja-build.org/manual.html or
-doc/manual.asciidoc included in the distribution -- for background
-and more details.
-
-Binaries for Linux, Mac, and Windows are available at
- https://github.com/ninja-build/ninja/releases
-Run './ninja -h' for Ninja help.
-
-To build your own binary, on many platforms it should be sufficient to
-just run `./configure.py --bootstrap`; for more details see HACKING.md.
-(Also read that before making changes to Ninja, as it has advice.)
-
-Installation is not necessary because the only required file is the
-resulting ninja binary. However, to enable features like Bash
-completion and Emacs and Vim editing modes, some files in misc/ must be
-copied to appropriate locations.
-
-If you're interested in making changes to Ninja, read HACKING.md first.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d11fd33
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# Ninja
+
+Ninja is a small build system with a focus on speed.
+https://ninja-build.org/
+
+See [the manual](https://ninja-build.org/manual.html) or
+`doc/manual.asciidoc` included in the distribution for background
+and more details.
+
+Binaries for Linux, Mac, and Windows are available at
+ [GitHub](https://github.com/ninja-build/ninja/releases).
+Run `./ninja -h` for Ninja help.
+
+Installation is not necessary because the only required file is the
+resulting ninja binary. However, to enable features like Bash
+completion and Emacs and Vim editing modes, some files in misc/ must be
+copied to appropriate locations.
+
+If you're interested in making changes to Ninja, read
+[CONTRIBUTING.md](CONTRIBUTING.md) first.
+
+## Building Ninja itself
+
+You can either build Ninja via the custom generator script written in Python or
+via CMake. For more details see
+[the wiki](https://github.com/ninja-build/ninja/wiki).
+
+### Python
+
+```
+./configure.py --bootstrap
+```
+
+This will generate the `ninja` binary and a `build.ninja` file you can now use
+to build Ninja with itself.
+
+### CMake
+
+```
+cmake -Bbuild-cmake -H.
+cmake --build build-cmake
+```
+
+The `ninja` binary will now be inside the `build-cmake` directory (you can
+choose any other name you like).
+
+To run the unit tests:
+
+```
+./build-cmake/ninja_test
+```
diff --git a/RELEASING b/RELEASING
index da4dbdd..0b03341 100644
--- a/RELEASING
+++ b/RELEASING
@@ -1,7 +1,7 @@
Notes to myself on all the steps to make for a Ninja release.
Push new release branch:
-1. Run afl-fuzz for a day or so (see HACKING.md) and run ninja_test
+1. Run afl-fuzz for a day or so and run ninja_test
2. Consider sending a heads-up to the ninja-build mailing list first
3. Make sure branches 'master' and 'release' are synced up locally
4. Update src/version.cc with new version (with ".git"), then
diff --git a/bootstrap.py b/bootstrap.py
deleted file mode 100755
index 56eab64..0000000
--- a/bootstrap.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2011 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.
-
-from __future__ import print_function
-
-import subprocess
-import sys
-
-print('DEPRECATED: this script will be deleted.')
-print('use "configure.py --bootstrap" instead.')
-subprocess.check_call([sys.executable, 'configure.py', '--bootstrap'])
diff --git a/configure.py b/configure.py
index 1d6ee7d..cded265 100755
--- a/configure.py
+++ b/configure.py
@@ -269,7 +269,7 @@
n.variable('configure_env', config_str + '$ ')
n.newline()
-CXX = configure_env.get('CXX', 'g++')
+CXX = configure_env.get('CXX', 'c++')
objext = '.o'
if platform.is_msvc():
CXX = 'cl'
@@ -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')
@@ -596,6 +596,11 @@
n.comment('Ancillary executables.')
+if platform.is_aix() and '-maix64' not in ldflags:
+ # Both hash_collision_bench and manifest_parser_perftest require more
+ # memory than will fit in the standard 32-bit AIX shared stack/heap (256M)
+ libs.append('-Wl,-bmaxdata:0x80000000')
+
for name in ['build_log_perftest',
'canon_perftest',
'depfile_parser_perftest',
@@ -649,7 +654,7 @@
command='$doxygen_mainpage_generator $in > $out',
description='DOXYGEN_MAINPAGE $out')
mainpage = n.build(built('doxygen_mainpage'), 'doxygen_mainpage',
- ['README', 'COPYING'],
+ ['README.md', 'COPYING'],
implicit=['$doxygen_mainpage_generator'])
n.build('doxygen', 'doxygen', doc('doxygen.config'),
implicit=mainpage)
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index e49d26d..e1ae083 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -271,6 +271,9 @@
tool takes in account the +-v+ and the +-n+ options (note that +-n+
implies +-v+).
+`cleandead`:: remove files produced by previous builds that are no longer in the
+build file. _Available since Ninja 1.10._
+
`compdb`:: given a list of rules, each of which is expected to be a
C family language compiler rule whose first input is the name of the
source file, prints on standard output a compilation database in the
@@ -283,6 +286,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+.
@@ -569,10 +575,10 @@
----
rule cc
depfile = $out.d
- command = gcc -MMD -MF $out.d [other gcc flags here]
+ command = gcc -MD -MF $out.d [other gcc flags here]
----
-The `-MMD` flag to `gcc` tells it to output header dependencies, and
+The `-MD` flag to `gcc` tells it to output header dependencies, and
the `-MF` flag tells it where to write them.
deps
@@ -893,7 +899,7 @@
On Windows, commands are strings, so Ninja passes the `command` string
directly to `CreateProcess`. (In the common case of simply executing
a compiler this means there is less overhead.) Consequently the
-quoting rules are deterimined by the called program, which on Windows
+quoting rules are determined by the called program, which on Windows
are usually provided by the C library. If you need shell
interpretation of the command (such as the use of `&&` to chain
multiple commands), make the command execute the Windows shell by
@@ -929,7 +935,7 @@
1. _Explicit dependencies_, as listed in a build line. These are
available as the `$in` variable in the rule. Changes in these files
- cause the output to be rebuilt; if these file are missing and
+ cause the output to be rebuilt; if these files are missing and
Ninja doesn't know how to build them, the build is aborted.
+
This is the standard form of dependency to be used e.g. for the
diff --git a/misc/manifest_fuzzer.cc b/misc/manifest_fuzzer.cc
new file mode 100644
index 0000000..0e1261a
--- /dev/null
+++ b/misc/manifest_fuzzer.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 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.
+
+#include "stdint.h"
+#include <string>
+#include "disk_interface.h"
+#include "state.h"
+#include "manifest_parser.h"
+#include <filesystem>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char build_file[256];
+ sprintf(build_file, "/tmp/build.ninja");
+ FILE *fp = fopen(build_file, "wb");
+ if (!fp)
+ return 0;
+ fwrite(data, size, 1, fp);
+ fclose(fp);
+
+ std::string err;
+ RealDiskInterface disk_interface;
+ State state;
+ ManifestParser parser(&state, &disk_interface);
+
+ parser.Load("/tmp/build.ninja", &err);
+
+ std::__fs::filesystem::remove_all("/tmp/build.ninja");
+ return 0;
+}
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index ebe6490..ab5c0d4 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -1,5 +1,19 @@
#!/usr/bin/python
+# Copyright 2011 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.
+
"""Python module for generating .ninja files.
Note that this is emphatically not a required piece of Ninja; it's
diff --git a/misc/oss-fuzz/build.sh b/misc/oss-fuzz/build.sh
new file mode 100644
index 0000000..4328feb
--- /dev/null
+++ b/misc/oss-fuzz/build.sh
@@ -0,0 +1,29 @@
+#!/bin/bash -eu
+# Copyright 2020 Google Inc.
+#
+# 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.
+#
+################################################################################
+
+cmake -Bbuild-cmake -H.
+cmake --build build-cmake
+
+cd $SRC/ninja/misc
+
+$CXX $CXXFLAGS -fdiagnostics-color -I/src/ninja/src -o fuzzer.o -c manifest_fuzzer.cc
+
+find .. -name "*.o" -exec ar rcs fuzz_lib.a {} \;
+
+$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzzer.o -o $OUT/fuzzer fuzz_lib.a
+
+zip $OUT/fuzzer_seed_corpus.zip $SRC/sample_ninja_build
diff --git a/misc/oss-fuzz/sample_ninja_build b/misc/oss-fuzz/sample_ninja_build
new file mode 100644
index 0000000..7b513be
--- /dev/null
+++ b/misc/oss-fuzz/sample_ninja_build
@@ -0,0 +1,14 @@
+# build.ninja
+cc = clang
+cflags = -Weverything
+
+rule compile
+ command = $cc $cflags -c $in -o $out
+
+rule link
+ command = $cc $in -o $out
+
+build hello.o: compile hello.c
+build hello: link hello.o
+
+default hello
diff --git a/misc/output_test.py b/misc/output_test.py
index fb73d72..b63520f 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)
@@ -43,6 +46,7 @@
final_output += line.replace('\r', '')
return final_output
+@unittest.skipIf(platform.system() == 'Windows', 'These test methods do not work on Windows')
class Output(unittest.TestCase):
def test_issue_1418(self):
self.assertEqual(run(
@@ -99,5 +103,13 @@
\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'), '')
+
+ def test_status(self):
+ self.assertEqual(run(''), 'ninja: no work to do.\n')
+
if __name__ == '__main__':
unittest.main()
diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec
index 05f5a07..36e5181 100644
--- a/misc/packaging/ninja.spec
+++ b/misc/packaging/ninja.spec
@@ -32,7 +32,7 @@
%files
%defattr(-, root, root)
-%doc COPYING README doc/manual.html
+%doc COPYING README.md doc/manual.html
%{_bindir}/*
%clean
diff --git a/src/browse.cc b/src/browse.cc
index c08c9f4..76bee07 100644
--- a/src/browse.cc
+++ b/src/browse.cc
@@ -22,6 +22,8 @@
#include "build/browse_py.h"
+using namespace std;
+
void RunBrowsePython(State* state, const char* ninja_command,
const char* input_file, int argc, char* argv[]) {
// Fork off a Python process and have it run our code via its stdin.
diff --git a/src/browse.py b/src/browse.py
index 1c9c39b..653cbe9 100755
--- a/src/browse.py
+++ b/src/browse.py
@@ -29,12 +29,15 @@
import BaseHTTPServer as httpserver
import SocketServer as socketserver
import argparse
-import cgi
import os
import socket
import subprocess
import sys
import webbrowser
+if sys.version_info >= (3, 2):
+ from html import escape
+else:
+ from cgi import escape
try:
from urllib.request import unquote
except ImportError:
@@ -62,7 +65,7 @@
return (True, line[len(prefix):])
def html_escape(text):
- return cgi.escape(text, quote=True)
+ return escape(text, quote=True)
def parse(text):
lines = iter(text.split('\n'))
diff --git a/src/build.cc b/src/build.cc
index 931fb95..2007d82 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -40,6 +40,8 @@
#include "subprocess.h"
#include "util.h"
+using namespace std;
+
namespace {
/// A CommandRunner that doesn't actually run the commands.
@@ -77,12 +79,9 @@
} // namespace
BuildStatus::BuildStatus(const BuildConfig& config)
- : config_(config),
- start_time_millis_(GetTimeMillis()),
- started_edges_(0), finished_edges_(0), total_edges_(0),
- progress_status_format_(NULL),
- overall_rate_(), current_rate_(config.parallelism) {
-
+ : config_(config), start_time_millis_(GetTimeMillis()), started_edges_(0),
+ finished_edges_(0), total_edges_(0), progress_status_format_(NULL),
+ current_rate_(config.parallelism) {
// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
printer_.set_smart_terminal(false);
@@ -96,7 +95,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));
@@ -183,7 +182,7 @@
// it considers a portion of the graph to be out of date. Normally
// this is done before the build starts, but our caller is about to
// load a dyndep file during the build. Doing so may generate more
- // exlanation lines (via fprintf directly to stderr), but in an
+ // explanation lines (via fprintf directly to stderr), but in an
// interactive console the cursor is currently at the end of a status
// line. Start a new line so that the first explanation does not
// append to the status line. After the explanations are done a
@@ -290,7 +289,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 +318,11 @@
want_.clear();
}
-bool Plan::AddTarget(Node* node, string* err) {
- return AddSubTarget(node, NULL, err, NULL);
+bool Plan::AddTarget(const Node* target, string* err) {
+ return AddSubTarget(target, 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.
@@ -382,7 +381,7 @@
Edge* Plan::FindWork() {
if (ready_.empty())
return NULL;
- set<Edge*>::iterator e = ready_.begin();
+ EdgeSet::iterator e = ready_.begin();
Edge* edge = *e;
ready_.erase(e);
return edge;
@@ -533,7 +532,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 +600,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 +634,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 +654,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 +675,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 +719,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);
@@ -783,16 +782,16 @@
return node;
}
-bool Builder::AddTarget(Node* node, string* err) {
- if (!scan_.RecomputeDirty(node, err))
+bool Builder::AddTarget(Node* target, string* err) {
+ if (!scan_.RecomputeDirty(target, err))
return false;
- if (Edge* in_edge = node->in_edge()) {
+ if (Edge* in_edge = target->in_edge()) {
if (in_edge->outputs_ready())
return true; // Nothing to do.
}
- if (!plan_.AddTarget(node, err))
+ if (!plan_.AddTarget(target, err))
return false;
return true;
@@ -829,6 +828,10 @@
// See if we can start any more commands.
if (failures_allowed && command_runner_->CanRunMore()) {
if (Edge* edge = plan_.FindWork()) {
+ if (edge->GetBindingBool("generator")) {
+ scan_.build_log()->Close();
+ }
+
if (!StartEdge(edge, err)) {
Cleanup();
status_->BuildFinished();
@@ -1033,14 +1036,16 @@
}
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_.empty() && "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;
@@ -1065,8 +1070,7 @@
// complexity in IncludesNormalize::Relativize.
deps_nodes->push_back(state_->GetNode(*i, ~0u));
}
- } else
- if (deps_type == "gcc") {
+ } else if (deps_type == "gcc") {
string depfile = result->edge->GetUnescapedDepfile();
if (depfile.empty()) {
*err = string("edge with deps=gcc but no depfile makes no sense");
diff --git a/src/build.h b/src/build.h
index 410d4a5..0a68478 100644
--- a/src/build.h
+++ b/src/build.h
@@ -19,7 +19,6 @@
#include <map>
#include <memory>
#include <queue>
-#include <set>
#include <string>
#include <vector>
@@ -46,7 +45,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* target, std::string* err);
// Pop a ready edge off the queue of edges to build.
// Returns NULL if there's no work to do.
@@ -56,7 +55,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,
@@ -67,11 +66,11 @@
/// If any of the edge's outputs are dyndep bindings of their dependents,
/// this loads dynamic dependencies from the nodes' paths.
/// Returns 'false' if loading dyndep info fails and 'true' otherwise.
- bool EdgeFinished(Edge* edge, EdgeResult result, string* err);
+ bool EdgeFinished(Edge* edge, EdgeResult result, std::string* err);
/// Clean the given node during the build.
/// Return false on error.
- bool CleanNode(DependencyScan* scan, Node* node, string* err);
+ bool CleanNode(DependencyScan* scan, Node* node, std::string* err);
/// Number of edges with commands to run.
int command_edge_count() const { return command_edges_; }
@@ -81,19 +80,19 @@
/// 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,
- const DyndepFile& ddf, string* err);
+ bool DyndepsLoaded(DependencyScan* scan, const Node* node,
+ const DyndepFile& ddf, std::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,
- set<Edge*>* dyndep_walk);
+ bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, std::string* err);
+ void UnmarkDependents(const Node* node, std::set<Node*>* dependents);
+ bool AddSubTarget(const Node* node, const Node* dependent, std::string* err,
+ std::set<Edge*>* dyndep_walk);
/// Update plan with knowledge that the given node is up to date.
/// If the node is a dyndep binding on any of its dependents, this
/// loads dynamic dependencies from the node's path.
/// Returns 'false' if loading dyndep info fails and 'true' otherwise.
- bool NodeFinished(Node* node, string* err);
+ bool NodeFinished(Node* node, std::string* err);
/// Enumerate possible steps we want for an edge.
enum Want
@@ -109,20 +108,20 @@
};
void EdgeWanted(const Edge* edge);
- bool EdgeMaybeReady(map<Edge*, Want>::iterator want_e, string* err);
+ bool EdgeMaybeReady(std::map<Edge*, Want>::iterator want_e, std::string* err);
/// Submits a ready edge as a candidate for execution.
/// The edge may be delayed from running, for example if it's a member of a
/// currently-full pool.
- void ScheduleWork(map<Edge*, Want>::iterator want_e);
+ void ScheduleWork(std::map<Edge*, Want>::iterator want_e);
/// Keep track of which edges we want to build in this plan. If this map does
/// not contain an entry for an edge, we do not want to build the entry or its
/// dependents. If it does contain an entry, the enumeration indicates what
/// we want for the edge.
- map<Edge*, Want> want_;
+ std::map<Edge*, Want> want_;
- set<Edge*> ready_;
+ EdgeSet ready_;
Builder* builder_;
@@ -146,13 +145,13 @@
Result() : edge(NULL) {}
Edge* edge;
ExitStatus status;
- string output;
+ std::string output;
bool success() const { return status == ExitSuccess; }
};
/// Wait for a command to complete, or return false if interrupted.
virtual bool WaitForCommand(Result* result) = 0;
- virtual vector<Edge*> GetActiveEdges() { return vector<Edge*>(); }
+ virtual std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
virtual void Abort() {}
};
@@ -186,24 +185,24 @@
/// Clean up after interrupted commands by deleting output files.
void Cleanup();
- Node* AddTarget(const string& name, string* err);
+ Node* AddTarget(const std::string& name, std::string* err);
/// Add a target to the build, scanning dependencies.
/// @return false on error.
- bool AddTarget(Node* target, string* err);
+ bool AddTarget(Node* target, std::string* err);
/// Returns true if the build targets are already up to date.
bool AlreadyUpToDate() const;
/// Run the build. Returns false on error.
/// It is an error to call this function when AlreadyUpToDate() is true.
- bool Build(string* err);
+ bool Build(std::string* err);
- bool StartEdge(Edge* edge, string* err);
+ bool StartEdge(Edge* edge, std::string* err);
/// Update status ninja logs following a command termination.
/// @return false if the build can not proceed further due to a fatal error.
- bool FinishCommand(CommandRunner::Result* result, string* err);
+ bool FinishCommand(CommandRunner::Result* result, std::string* err);
/// Used for tests.
void SetBuildLog(BuildLog* log) {
@@ -211,22 +210,22 @@
}
/// Load the dyndep information provided by the given node.
- bool LoadDyndeps(Node* node, string* err);
+ bool LoadDyndeps(Node* node, std::string* err);
State* state_;
const BuildConfig& config_;
Plan plan_;
#if __cplusplus < 201703L
- auto_ptr<CommandRunner> command_runner_;
+ std::auto_ptr<CommandRunner> command_runner_;
#else
- unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
+ std::unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
#endif
BuildStatus* status_;
private:
- bool ExtractDeps(CommandRunner::Result* result, const string& deps_type,
- const string& deps_prefix, vector<Node*>* deps_nodes,
- string* err);
+ bool ExtractDeps(CommandRunner::Result* result, const std::string& deps_type,
+ const std::string& deps_prefix,
+ std::vector<Node*>* deps_nodes, std::string* err);
DiskInterface* disk_interface_;
DependencyScan scan_;
@@ -240,8 +239,8 @@
struct BuildStatus {
explicit BuildStatus(const BuildConfig& config);
void PlanHasTotalEdges(int total);
- void BuildEdgeStarted(Edge* edge);
- void BuildEdgeFinished(Edge* edge, bool success, const string& output,
+ void BuildEdgeStarted(const Edge* edge);
+ void BuildEdgeFinished(Edge* edge, bool success, const std::string& output,
int* start_time, int* end_time);
void BuildLoadDyndeps();
void BuildStarted();
@@ -257,11 +256,11 @@
/// placeholders.
/// @param progress_status_format The format of the progress status.
/// @param status The status of the edge.
- string FormatProgressStatus(const char* progress_status_format,
- EdgeStatus status) const;
+ std::string FormatProgressStatus(const char* progress_status_format,
+ EdgeStatus status) const;
private:
- void PrintStatus(Edge* edge, EdgeStatus status);
+ void PrintStatus(const Edge* edge, EdgeStatus status);
const BuildConfig& config_;
@@ -271,7 +270,7 @@
int started_edges_, finished_edges_, total_edges_;
/// Map of running edge to time the edge started running.
- typedef map<Edge*, int> RunningEdgeMap;
+ typedef std::map<const Edge*, int> RunningEdgeMap;
RunningEdgeMap running_edges_;
/// Prints progress output.
@@ -327,7 +326,7 @@
double rate_;
Stopwatch stopwatch_;
const size_t N;
- queue<double> times_;
+ std::queue<double> times_;
int last_update_;
};
diff --git a/src/build_log.cc b/src/build_log.cc
index c4a08a0..4dcd6ce 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -21,7 +21,9 @@
#endif
#include "build_log.h"
+#include "disk_interface.h"
+#include <cassert>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
@@ -39,6 +41,8 @@
#define strtoll _strtoi64
#endif
+using namespace std;
+
// Implementation details:
// Each run's log appends to the log file.
// To load, we run through all log entries in series, throwing away
@@ -131,25 +135,9 @@
return false;
}
- log_file_ = fopen(path.c_str(), "ab");
- if (!log_file_) {
- *err = strerror(errno);
- return false;
- }
- setvbuf(log_file_, NULL, _IOLBF, BUFSIZ);
- SetCloseOnExec(fileno(log_file_));
-
- // Opening a file in append mode doesn't set the file pointer to the file's
- // end on Windows. Do that explicitly.
- fseek(log_file_, 0, SEEK_END);
-
- if (ftell(log_file_) == 0) {
- if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
- *err = strerror(errno);
- return false;
- }
- }
-
+ assert(!log_file_);
+ log_file_path_ = path; // we don't actually open the file right now, but will
+ // do so on the first write attempt
return true;
}
@@ -173,6 +161,9 @@
log_entry->end_time = end_time;
log_entry->mtime = mtime;
+ if (!OpenForWriteIfNeeded()) {
+ return false;
+ }
if (log_file_) {
if (!WriteEntry(log_file_, *log_entry))
return false;
@@ -185,11 +176,37 @@
}
void BuildLog::Close() {
+ OpenForWriteIfNeeded(); // create the file even if nothing has been recorded
if (log_file_)
fclose(log_file_);
log_file_ = NULL;
}
+bool BuildLog::OpenForWriteIfNeeded() {
+ if (log_file_ || log_file_path_.empty()) {
+ return true;
+ }
+ log_file_ = fopen(log_file_path_.c_str(), "ab");
+ if (!log_file_) {
+ return false;
+ }
+ if (setvbuf(log_file_, NULL, _IOLBF, BUFSIZ) != 0) {
+ return false;
+ }
+ SetCloseOnExec(fileno(log_file_));
+
+ // Opening a file in append mode doesn't set the file pointer to the file's
+ // end on Windows. Do that explicitly.
+ fseek(log_file_, 0, SEEK_END);
+
+ if (ftell(log_file_) == 0) {
+ if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
struct LineReader {
explicit LineReader(FILE* file)
: file_(file), buf_end_(buf_), line_start_(buf_), line_end_(NULL) {
@@ -241,14 +258,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 +286,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 +356,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 +371,7 @@
needs_recompaction_ = true;
}
- return true;
+ return LOAD_SUCCESS;
}
BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
@@ -418,3 +435,60 @@
return true;
}
+
+bool BuildLog::Restat(const StringPiece path,
+ const DiskInterface& disk_interface,
+ const int output_count, char** outputs,
+ 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) {
+ bool skip = output_count > 0;
+ for (int j = 0; j < output_count; ++j) {
+ if (i->second->output == outputs[j]) {
+ skip = false;
+ break;
+ }
+ }
+ if (!skip) {
+ 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..88551e3 100644
--- a/src/build_log.h
+++ b/src/build_log.h
@@ -17,12 +17,13 @@
#include <string>
#include <stdio.h>
-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.
@@ -43,16 +44,19 @@
BuildLog();
~BuildLog();
- bool OpenForWrite(const string& path, const BuildLogUser& user, string* err);
+ /// Prepares writing to the log file without actually opening it - that will
+ /// happen when/if it's needed
+ bool OpenForWrite(const std::string& path, const BuildLogUser& user,
+ std::string* err);
bool RecordCommand(Edge* edge, int start_time, int end_time,
TimeStamp mtime = 0);
void Close();
/// Load the on-disk log.
- bool Load(const string& path, string* err);
+ LoadStatus Load(const std::string& path, std::string* err);
struct LogEntry {
- string output;
+ std::string output;
uint64_t command_hash;
int start_time;
int end_time;
@@ -67,26 +71,36 @@
mtime == o.mtime;
}
- explicit LogEntry(const string& output);
- LogEntry(const string& output, uint64_t command_hash,
+ explicit LogEntry(const std::string& output);
+ LogEntry(const std::string& output, uint64_t command_hash,
int start_time, int end_time, TimeStamp restat_mtime);
};
/// Lookup a previously-run command by its output path.
- LogEntry* LookupByOutput(const string& path);
+ LogEntry* LookupByOutput(const std::string& path);
/// Serialize an entry into a log file.
bool WriteEntry(FILE* f, const LogEntry& entry);
/// Rewrite the known log entries, throwing away old data.
- bool Recompact(const string& path, const BuildLogUser& user, string* err);
+ bool Recompact(const std::string& path, const BuildLogUser& user,
+ std::string* err);
+
+ /// Restat all outputs in the log
+ bool Restat(StringPiece path, const DiskInterface& disk_interface,
+ int output_count, char** outputs, std::string* err);
typedef ExternalStringHashMap<LogEntry*>::Type Entries;
const Entries& entries() const { return entries_; }
private:
+ /// Should be called before using log_file_. When false is returned, errno
+ /// will be set.
+ bool OpenForWriteIfNeeded();
+
Entries entries_;
FILE* log_file_;
+ std::string log_file_path_;
bool needs_recompaction_;
};
diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc
index e471d13..ced0df9 100644
--- a/src/build_log_perftest.cc
+++ b/src/build_log_perftest.cc
@@ -26,6 +26,8 @@
#include <unistd.h>
#endif
+using namespace std;
+
const char kTestFilename[] = "BuildLogPerfTest-tempfile";
struct NoDeadPaths : public BuildLogUser {
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
index ad30380..3718299 100644
--- a/src/build_log_test.cc
+++ b/src/build_log_test.cc
@@ -25,6 +25,9 @@
#include <sys/types.h>
#include <unistd.h>
#endif
+#include <cassert>
+
+using namespace std;
namespace {
@@ -150,7 +153,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 +219,54 @@
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;
+ char out2[] = { 'o', 'u', 't', '2', 0 };
+ char* filter2[] = { out2 };
+ EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err));
+ ASSERT_EQ("", err);
+ e = log.LookupByOutput("out");
+ ASSERT_EQ(3, e->mtime); // unchanged, since the filter doesn't match
+
+ EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 0, NULL, &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 ddf8574..0baabc4 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -21,6 +21,8 @@
#include "graph.h"
#include "test.h"
+using namespace std;
+
struct CompareEdgesByOutput {
static bool cmp(const Edge* a, const Edge* b) {
return a->outputs_[0]->path() < b->outputs_[0]->path();
@@ -488,6 +490,10 @@
status_(config_) {
}
+ explicit BuildTest(DepsLog* log)
+ : config_(MakeConfig()), command_runner_(&fs_),
+ builder_(&state_, config_, NULL, log, &fs_), status_(config_) {}
+
virtual void SetUp() {
StateTestWithBuiltinRules::SetUp();
@@ -582,6 +588,8 @@
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 +651,14 @@
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;
@@ -657,7 +673,7 @@
bool verify_active_edge_found = false;
for (vector<Edge*>::iterator i = active_edges_.begin();
i != active_edges_.end(); ++i) {
- if ((*i)->outputs_.size() >= 1 &&
+ if (!(*i)->outputs_.empty() &&
(*i)->outputs_[0]->path() == verify_active_edge) {
verify_active_edge_found = true;
}
@@ -1855,6 +1871,214 @@
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/canon_perftest.cc b/src/canon_perftest.cc
index 03f4a2f..088bd45 100644
--- a/src/canon_perftest.cc
+++ b/src/canon_perftest.cc
@@ -18,6 +18,8 @@
#include "util.h"
#include "metrics.h"
+using namespace std;
+
const char kPath[] =
"../../third_party/WebKit/Source/WebCore/"
"platform/leveldb/LevelDBWriteBatch.cpp";
diff --git a/src/clean.cc b/src/clean.cc
index d1f221d..3e57437 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -22,14 +22,14 @@
#include "state.h"
#include "util.h"
+using namespace std;
+
Cleaner::Cleaner(State* state,
const BuildConfig& config,
DiskInterface* disk_interface)
: state_(state),
config_(config),
dyndep_loader_(state, disk_interface),
- removed_(),
- cleaned_(),
cleaned_files_count_(0),
disk_interface_(disk_interface),
status_(0) {
@@ -124,6 +124,19 @@
return status_;
}
+int Cleaner::CleanDead(const BuildLog::Entries& entries) {
+ Reset();
+ PrintHeader();
+ for (BuildLog::Entries::const_iterator i = entries.begin(); i != entries.end(); ++i) {
+ Node* n = state_->LookupNode(i->first);
+ if (!n || !n->in_edge()) {
+ Remove(i->first.AsString());
+ }
+ }
+ PrintFooter();
+ return status_;
+}
+
void Cleaner::DoCleanTarget(Node* target) {
if (Edge* e = target->in_edge()) {
// Do not try to remove phony targets
diff --git a/src/clean.h b/src/clean.h
index d044fb1..cf3f1c3 100644
--- a/src/clean.h
+++ b/src/clean.h
@@ -20,8 +20,7 @@
#include "build.h"
#include "dyndep.h"
-
-using namespace std;
+#include "build_log.h"
struct State;
struct Node;
@@ -58,6 +57,10 @@
/// Clean the file produced by the given @a rules.
/// @return non-zero if an error occurs.
int CleanRules(int rule_count, char* rules[]);
+ /// Clean the files produced by previous builds that are no longer in the
+ /// manifest.
+ /// @return non-zero if an error occurs.
+ int CleanDead(const BuildLog::Entries& entries);
/// @return the number of file cleaned.
int cleaned_files_count() const {
@@ -73,15 +76,15 @@
private:
/// Remove the file @a path.
/// @return whether the file has been removed.
- int RemoveFile(const string& path);
+ int RemoveFile(const std::string& path);
/// @returns whether the file @a path exists.
- bool FileExists(const string& path);
- void Report(const string& path);
+ bool FileExists(const std::string& path);
+ void Report(const std::string& path);
/// Remove the given @a path file only if it has not been already removed.
- void Remove(const string& path);
+ void Remove(const std::string& path);
/// @return whether the given @a path has already been removed.
- bool IsAlreadyRemoved(const string& path);
+ bool IsAlreadyRemoved(const std::string& path);
/// Remove the depfile and rspfile for an Edge.
void RemoveEdgeFiles(Edge* edge);
@@ -98,8 +101,8 @@
State* state_;
const BuildConfig& config_;
DyndepLoader dyndep_loader_;
- set<string> removed_;
- set<Node*> cleaned_;
+ std::set<std::string> removed_;
+ std::set<Node*> cleaned_;
int cleaned_files_count_;
DiskInterface* disk_interface_;
int status_;
diff --git a/src/clean_test.cc b/src/clean_test.cc
index 45187f4..1b843a2 100644
--- a/src/clean_test.cc
+++ b/src/clean_test.cc
@@ -15,8 +15,19 @@
#include "clean.h"
#include "build.h"
+#include "util.h"
#include "test.h"
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+using namespace std;
+
+namespace {
+
+const char kTestFilename[] = "CleanTest-tempfile";
+
struct CleanTest : public StateTestWithBuiltinRules {
VirtualFileSystem fs_;
BuildConfig config_;
@@ -454,3 +465,76 @@
EXPECT_EQ(0, fs_.Stat("out 1.d", &err));
EXPECT_EQ(0, fs_.Stat("out 2.rsp", &err));
}
+
+struct CleanDeadTest : public CleanTest, public BuildLogUser{
+ virtual void SetUp() {
+ // In case a crashing test left a stale file behind.
+ unlink(kTestFilename);
+ CleanTest::SetUp();
+ }
+ virtual void TearDown() {
+ unlink(kTestFilename);
+ }
+ virtual bool IsPathDead(StringPiece) const { return false; }
+};
+
+TEST_F(CleanDeadTest, CleanDead) {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state,
+"rule cat\n"
+" command = cat $in > $out\n"
+"build out1: cat in\n"
+"build out2: cat in\n"
+));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out2: cat in\n"
+));
+ fs_.Create("in", "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
+
+ BuildLog log1;
+ string err;
+ EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
+ ASSERT_EQ("", err);
+ log1.RecordCommand(state.edges_[0], 15, 18);
+ log1.RecordCommand(state.edges_[1], 20, 25);
+ log1.Close();
+
+ BuildLog log2;
+ EXPECT_TRUE(log2.Load(kTestFilename, &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(2u, log2.entries().size());
+ ASSERT_TRUE(log2.LookupByOutput("out1"));
+ ASSERT_TRUE(log2.LookupByOutput("out2"));
+
+ // First use the manifest that describe how to build out1.
+ Cleaner cleaner1(&state, config_, &fs_);
+ EXPECT_EQ(0, cleaner1.CleanDead(log2.entries()));
+ EXPECT_EQ(0, cleaner1.cleaned_files_count());
+ EXPECT_EQ(0u, fs_.files_removed_.size());
+ EXPECT_NE(0, fs_.Stat("in", &err));
+ EXPECT_NE(0, fs_.Stat("out1", &err));
+ EXPECT_NE(0, fs_.Stat("out2", &err));
+
+ // Then use the manifest that does not build out1 anymore.
+ Cleaner cleaner2(&state_, config_, &fs_);
+ EXPECT_EQ(0, cleaner2.CleanDead(log2.entries()));
+ EXPECT_EQ(1, cleaner2.cleaned_files_count());
+ EXPECT_EQ(1u, fs_.files_removed_.size());
+ EXPECT_EQ("out1", *(fs_.files_removed_.begin()));
+ EXPECT_NE(0, fs_.Stat("in", &err));
+ EXPECT_EQ(0, fs_.Stat("out1", &err));
+ EXPECT_NE(0, fs_.Stat("out2", &err));
+
+ // Nothing to do now.
+ EXPECT_EQ(0, cleaner2.CleanDead(log2.entries()));
+ EXPECT_EQ(0, cleaner2.cleaned_files_count());
+ EXPECT_EQ(1u, fs_.files_removed_.size());
+ EXPECT_EQ("out1", *(fs_.files_removed_.begin()));
+ EXPECT_NE(0, fs_.Stat("in", &err));
+ EXPECT_EQ(0, fs_.Stat("out1", &err));
+ EXPECT_NE(0, fs_.Stat("out2", &err));
+ log2.Close();
+}
+} // anonymous namespace
diff --git a/src/clparser.cc b/src/clparser.cc
index 7994c06..275641e 100644
--- a/src/clparser.cc
+++ b/src/clparser.cc
@@ -28,6 +28,8 @@
#include "util.h"
#endif
+using namespace std;
+
namespace {
/// Return true if \a input ends with \a needle.
diff --git a/src/clparser.h b/src/clparser.h
index e597e7e..2a33628 100644
--- a/src/clparser.h
+++ b/src/clparser.h
@@ -17,7 +17,6 @@
#include <set>
#include <string>
-using namespace std;
/// Visual Studio's cl.exe requires some massaging to work with Ninja;
/// for example, it emits include information on stderr in a funny
@@ -27,26 +26,26 @@
/// Parse a line of cl.exe output and extract /showIncludes info.
/// If a dependency is extracted, returns a nonempty string.
/// Exposed for testing.
- static string FilterShowIncludes(const string& line,
- const string& deps_prefix);
+ static std::string FilterShowIncludes(const std::string& line,
+ const std::string& deps_prefix);
/// Return true if a mentioned include file is a system path.
/// Filtering these out reduces dependency information considerably.
- static bool IsSystemInclude(string path);
+ static bool IsSystemInclude(std::string path);
/// Parse a line of cl.exe output and return true if it looks like
/// it's printing an input filename. This is a heuristic but it appears
/// to be the best we can do.
/// Exposed for testing.
- static bool FilterInputFilename(string line);
+ static bool FilterInputFilename(std::string line);
/// Parse the full output of cl, filling filtered_output with the text that
/// should be printed (if any). Returns true on success, or false with err
/// filled. output must not be the same object as filtered_object.
- bool Parse(const string& output, const string& deps_prefix,
- string* filtered_output, string* err);
+ bool Parse(const std::string& output, const std::string& deps_prefix,
+ std::string* filtered_output, std::string* err);
- set<string> includes_;
+ std::set<std::string> includes_;
};
#endif // NINJA_CLPARSER_H_
diff --git a/src/clparser_perftest.cc b/src/clparser_perftest.cc
index 7ac5230..008ac46 100644
--- a/src/clparser_perftest.cc
+++ b/src/clparser_perftest.cc
@@ -18,6 +18,8 @@
#include "clparser.h"
#include "metrics.h"
+using namespace std;
+
int main(int argc, char* argv[]) {
// Output of /showIncludes from #include <iostream>
string perf_testdata =
diff --git a/src/clparser_test.cc b/src/clparser_test.cc
index 1549ab1..0b829c1 100644
--- a/src/clparser_test.cc
+++ b/src/clparser_test.cc
@@ -17,6 +17,8 @@
#include "test.h"
#include "util.h"
+using namespace std;
+
TEST(CLParserTest, ShowIncludes) {
ASSERT_EQ("", CLParser::FilterShowIncludes("", ""));
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
index 6faeac6..bffeb76 100644
--- a/src/depfile_parser.cc
+++ b/src/depfile_parser.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 1.1.1 */
+/* Generated by re2c 1.3 */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,10 @@
#include "depfile_parser.h"
#include "util.h"
+#include <algorithm>
+
+using namespace std;
+
DepfileParser::DepfileParser(DepfileParserOptions options)
: options_(options)
{
@@ -48,10 +52,8 @@
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
@@ -166,22 +168,23 @@
goto yy5;
yy13:
yych = *(yymarker = ++in);
- if (yych <= 0x1F) {
+ if (yych <= ' ') {
if (yych <= '\n') {
if (yych <= 0x00) goto yy5;
if (yych <= '\t') goto yy16;
goto yy17;
} else {
if (yych == '\r') goto yy19;
- goto yy16;
+ if (yych <= 0x1F) goto yy16;
+ goto yy21;
}
} else {
- if (yych <= '#') {
- if (yych <= ' ') goto yy21;
- if (yych <= '"') goto yy16;
- goto yy23;
+ if (yych <= '9') {
+ if (yych == '#') goto yy23;
+ goto yy16;
} else {
- if (yych == '\\') goto yy25;
+ if (yych <= ':') goto yy25;
+ if (yych == '\\') goto yy27;
goto yy16;
}
}
@@ -231,26 +234,63 @@
}
yy25:
yych = *++in;
- if (yych <= 0x1F) {
+ if (yych <= '\f') {
+ if (yych <= 0x00) goto yy28;
+ if (yych <= 0x08) goto yy26;
+ if (yych <= '\n') goto yy28;
+ } else {
+ if (yych <= '\r') goto yy28;
+ if (yych == ' ') goto yy28;
+ }
+yy26:
+ {
+ // De-escape colon sign, but preserve other leading backslashes.
+ // Regular expression uses lookahead to make sure that no whitespace
+ // nor EOF follows. In that case it'd be the : at the end of a target
+ int len = (int)(in - start);
+ if (len > 2 && out < start)
+ memset(out, '\\', len - 2);
+ out += len - 2;
+ *out++ = ':';
+ continue;
+ }
+yy27:
+ yych = *++in;
+ if (yych <= ' ') {
if (yych <= '\n') {
if (yych <= 0x00) goto yy11;
if (yych <= '\t') goto yy16;
goto yy11;
} else {
if (yych == '\r') goto yy11;
- goto yy16;
+ if (yych <= 0x1F) goto yy16;
+ goto yy30;
}
} else {
- if (yych <= '#') {
- if (yych <= ' ') goto yy26;
- if (yych <= '"') goto yy16;
- goto yy23;
+ if (yych <= '9') {
+ if (yych == '#') goto yy23;
+ goto yy16;
} else {
- if (yych == '\\') goto yy28;
+ if (yych <= ':') goto yy25;
+ if (yych == '\\') goto yy32;
goto yy16;
}
}
-yy26:
+yy28:
+ ++in;
+ {
+ // Backslash followed by : and whitespace.
+ // It is therefore normal text and not an escaped colon
+ int len = (int)(in - start - 1);
+ // Need to shift it over if we're overwriting backslashes.
+ if (out < start)
+ memmove(out, start, len);
+ out += len;
+ if (*(in - 1) == '\n')
+ have_newline = true;
+ break;
+ }
+yy30:
++in;
{
// 2N backslashes plus space -> 2N backslashes, end of filename.
@@ -260,24 +300,25 @@
out += len - 1;
break;
}
-yy28:
+yy32:
yych = *++in;
- if (yych <= 0x1F) {
+ if (yych <= ' ') {
if (yych <= '\n') {
if (yych <= 0x00) goto yy11;
if (yych <= '\t') goto yy16;
goto yy11;
} else {
if (yych == '\r') goto yy11;
- goto yy16;
+ if (yych <= 0x1F) goto yy16;
+ goto yy21;
}
} else {
- if (yych <= '#') {
- if (yych <= ' ') goto yy21;
- if (yych <= '"') goto yy16;
- goto yy23;
+ if (yych <= '9') {
+ if (yych == '#') goto yy23;
+ goto yy16;
} else {
- if (yych == '\\') goto yy25;
+ if (yych <= ':') goto yy25;
+ if (yych == '\\') goto yy27;
goto yy16;
}
}
@@ -294,41 +335,32 @@
}
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";
+ 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 (options_.depfile_distinct_target_lines_action_ ==
- kDepfileDistinctTargetLinesActionError) {
- *err =
- "depfile has multiple output paths (on separate lines)"
- " [-w depfilemulti=err]";
- 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..0e8db81 100644
--- a/src/depfile_parser.h
+++ b/src/depfile_parser.h
@@ -17,21 +17,11 @@
#include <string>
#include <vector>
-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.
@@ -42,10 +32,10 @@
/// Parse an input file. Input must be NUL-terminated.
/// Warning: may mutate the content in-place and parsed StringPieces are
/// pointers within it.
- bool Parse(string* content, string* err);
+ bool Parse(std::string* content, std::string* err);
- StringPiece out_;
- vector<StringPiece> ins_;
+ std::vector<StringPiece> outs_;
+ std::vector<StringPiece> ins_;
DepfileParserOptions options_;
};
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
index 735a0c3..75ba982 100644
--- a/src/depfile_parser.in.cc
+++ b/src/depfile_parser.in.cc
@@ -15,6 +15,10 @@
#include "depfile_parser.h"
#include "util.h"
+#include <algorithm>
+
+using namespace std;
+
DepfileParser::DepfileParser(DepfileParserOptions options)
: options_(options)
{
@@ -47,10 +51,8 @@
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
@@ -103,6 +105,29 @@
*out++ = '#';
continue;
}
+ '\\'+ ':' [\x00\x20\r\n\t] {
+ // Backslash followed by : and whitespace.
+ // It is therefore normal text and not an escaped colon
+ int len = (int)(in - start - 1);
+ // Need to shift it over if we're overwriting backslashes.
+ if (out < start)
+ memmove(out, start, len);
+ out += len;
+ if (*(in - 1) == '\n')
+ have_newline = true;
+ break;
+ }
+ '\\'+ ':' {
+ // De-escape colon sign, but preserve other leading backslashes.
+ // Regular expression uses lookahead to make sure that no whitespace
+ // nor EOF follows. In that case it'd be the : at the end of a target
+ int len = (int)(in - start);
+ if (len > 2 && out < start)
+ memset(out, '\\', len - 2);
+ out += len - 2;
+ *out++ = ':';
+ continue;
+ }
'$$' {
// De-escape dollar character.
*out++ = '$';
@@ -146,41 +171,32 @@
}
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";
+ 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 (options_.depfile_distinct_target_lines_action_ ==
- kDepfileDistinctTargetLinesActionError) {
- *err =
- "depfile has multiple output paths (on separate lines)"
- " [-w depfilemulti=err]";
- 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_perftest.cc b/src/depfile_parser_perftest.cc
index b215221..52555e6 100644
--- a/src/depfile_parser_perftest.cc
+++ b/src/depfile_parser_perftest.cc
@@ -19,6 +19,8 @@
#include "util.h"
#include "metrics.h"
+using namespace std;
+
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("usage: %s <file1> <file2...>\n", argv[0]);
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
index 19224f3..8886258 100644
--- a/src/depfile_parser_test.cc
+++ b/src/depfile_parser_test.cc
@@ -16,6 +16,8 @@
#include "test.h"
+using namespace std;
+
struct DepfileParserTest : public testing::Test {
bool Parse(const char* input, string* err);
@@ -34,7 +36,8 @@
"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 +57,8 @@
" 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 +69,8 @@
" 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 +84,9 @@
" 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 +96,9 @@
"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());
@@ -111,8 +118,9 @@
"a\\ b\\#c.h: \\\\\\\\\\ \\\\\\\\ \\\\share\\info\\\\#1",
&err));
ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
EXPECT_EQ("a b#c.h",
- parser_.out_.AsString());
+ parser_.outs_[0].AsString());
ASSERT_EQ(3u, parser_.ins_.size());
EXPECT_EQ("\\\\ ",
parser_.ins_[0].AsString());
@@ -130,11 +138,47 @@
"\\!\\@\\#$$\\%\\^\\&\\[\\]\\\\:",
&err));
ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
EXPECT_EQ("\\!\\@#$\\%\\^\\&\\[\\]\\\\",
- parser_.out_.AsString());
+ parser_.outs_[0].AsString());
ASSERT_EQ(0u, parser_.ins_.size());
}
+TEST_F(DepfileParserTest, EscapedColons)
+{
+ std::string err;
+ // Tests for correct parsing of depfiles produced on Windows
+ // by both Clang, GCC pre 10 and GCC 10
+ EXPECT_TRUE(Parse(
+"c\\:\\gcc\\x86_64-w64-mingw32\\include\\stddef.o: \\\n"
+" c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.h \n",
+ &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.o",
+ parser_.outs_[0].AsString());
+ ASSERT_EQ(1u, parser_.ins_.size());
+ EXPECT_EQ("c:\\gcc\\x86_64-w64-mingw32\\include\\stddef.h",
+ parser_.ins_[0].AsString());
+}
+
+TEST_F(DepfileParserTest, EscapedTargetColon)
+{
+ std::string err;
+ EXPECT_TRUE(Parse(
+"foo1\\: x\n"
+"foo1\\:\n"
+"foo1\\:\r\n"
+"foo1\\:\t\n"
+"foo1\\:",
+ &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, parser_.outs_.size());
+ EXPECT_EQ("foo1\\", parser_.outs_[0].AsString());
+ ASSERT_EQ(1u, parser_.ins_.size());
+ EXPECT_EQ("x", parser_.ins_[0].AsString());
+}
+
TEST_F(DepfileParserTest, SpecialChars) {
// See filenames like istreambuf.iterator_op!= in
// https://github.com/google/libcxx/tree/master/test/iterators/stream.iterators/istreambuf.iterator/
@@ -147,8 +191,9 @@
" 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());
@@ -166,18 +211,25 @@
// 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) {
@@ -185,7 +237,8 @@
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());
}
@@ -196,7 +249,8 @@
"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());
@@ -209,7 +263,8 @@
"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());
@@ -222,7 +277,8 @@
" 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());
@@ -235,7 +291,8 @@
" 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());
@@ -247,7 +304,8 @@
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());
@@ -259,7 +317,8 @@
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());
@@ -272,7 +331,8 @@
"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());
@@ -287,25 +347,34 @@
"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..191f300 100644
--- a/src/deps_log.cc
+++ b/src/deps_log.cc
@@ -30,6 +30,8 @@
#include "state.h"
#include "util.h"
+using namespace std;
+
// The version is stored as 4 bytes after the signature and also serves as a
// byte order mark. Signature and version combined are 16 bytes long.
const char kFileSignature[] = "# ninjadeps\n";
@@ -49,34 +51,9 @@
return false;
}
- file_ = fopen(path.c_str(), "ab");
- if (!file_) {
- *err = strerror(errno);
- return false;
- }
- // Set the buffer size to this and flush the file buffer after every record
- // to make sure records aren't written partially.
- setvbuf(file_, NULL, _IOFBF, kMaxRecordSize + 1);
- SetCloseOnExec(fileno(file_));
-
- // Opening a file in append mode doesn't set the file pointer to the file's
- // end on Windows. Do that explicitly.
- fseek(file_, 0, SEEK_END);
-
- if (ftell(file_) == 0) {
- if (fwrite(kFileSignature, sizeof(kFileSignature) - 1, 1, file_) < 1) {
- *err = strerror(errno);
- return false;
- }
- if (fwrite(&kCurrentVersion, 4, 1, file_) < 1) {
- *err = strerror(errno);
- return false;
- }
- }
- if (fflush(file_) != 0) {
- *err = strerror(errno);
- return false;
- }
+ assert(!file_);
+ file_path_ = path; // we don't actually open the file right now, but will do
+ // so on the first write attempt
return true;
}
@@ -132,6 +109,10 @@
errno = ERANGE;
return false;
}
+
+ if (!OpenForWriteIfNeeded()) {
+ return false;
+ }
size |= 0x80000000; // Deps record: set high bit.
if (fwrite(&size, 4, 1, file_) < 1)
return false;
@@ -162,20 +143,21 @@
}
void DepsLog::Close() {
+ OpenForWriteIfNeeded(); // create the file even if nothing has been recorded
if (file_)
fclose(file_);
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 +178,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 +266,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 +284,7 @@
needs_recompaction_ = true;
}
- return true;
+ return LOAD_SUCCESS;
}
DepsLog::Deps* DepsLog::GetDeps(Node* node) {
@@ -396,10 +378,14 @@
errno = ERANGE;
return false;
}
+
+ if (!OpenForWriteIfNeeded()) {
+ return false;
+ }
if (fwrite(&size, 4, 1, file_) < 1)
return false;
if (fwrite(node->path().data(), path_size, 1, file_) < 1) {
- assert(node->path().size() > 0);
+ assert(!node->path().empty());
return false;
}
if (padding && fwrite("\0\0", padding, 1, file_) < 1)
@@ -416,3 +402,37 @@
return true;
}
+
+bool DepsLog::OpenForWriteIfNeeded() {
+ if (file_path_.empty()) {
+ return true;
+ }
+ file_ = fopen(file_path_.c_str(), "ab");
+ if (!file_) {
+ return false;
+ }
+ // Set the buffer size to this and flush the file buffer after every record
+ // to make sure records aren't written partially.
+ if (setvbuf(file_, NULL, _IOFBF, kMaxRecordSize + 1) != 0) {
+ return false;
+ }
+ SetCloseOnExec(fileno(file_));
+
+ // Opening a file in append mode doesn't set the file pointer to the file's
+ // end on Windows. Do that explicitly.
+ fseek(file_, 0, SEEK_END);
+
+ if (ftell(file_) == 0) {
+ if (fwrite(kFileSignature, sizeof(kFileSignature) - 1, 1, file_) < 1) {
+ return false;
+ }
+ if (fwrite(&kCurrentVersion, 4, 1, file_) < 1) {
+ return false;
+ }
+ }
+ if (fflush(file_) != 0) {
+ return false;
+ }
+ file_path_.clear();
+ return true;
+}
diff --git a/src/deps_log.h b/src/deps_log.h
index 3812a28..cc44b41 100644
--- a/src/deps_log.h
+++ b/src/deps_log.h
@@ -17,10 +17,10 @@
#include <string>
#include <vector>
-using namespace std;
#include <stdio.h>
+#include "load_status.h"
#include "timestamp.h"
struct Node;
@@ -70,8 +70,8 @@
~DepsLog();
// Writing (build-time) interface.
- bool OpenForWrite(const string& path, string* err);
- bool RecordDeps(Node* node, TimeStamp mtime, const vector<Node*>& nodes);
+ bool OpenForWrite(const std::string& path, std::string* err);
+ bool RecordDeps(Node* node, TimeStamp mtime, const std::vector<Node*>& nodes);
bool RecordDeps(Node* node, TimeStamp mtime, int node_count, Node** nodes);
void Close();
@@ -84,11 +84,11 @@
int node_count;
Node** nodes;
};
- bool Load(const string& path, State* state, string* err);
+ LoadStatus Load(const std::string& path, State* state, std::string* err);
Deps* GetDeps(Node* node);
/// Rewrite the known log entries, throwing away old data.
- bool Recompact(const string& path, string* err);
+ bool Recompact(const std::string& path, std::string* err);
/// Returns if the deps entry for a node is still reachable from the manifest.
///
@@ -99,8 +99,8 @@
bool IsDepsEntryLiveFor(Node* node);
/// Used for tests.
- const vector<Node*>& nodes() const { return nodes_; }
- const vector<Deps*>& deps() const { return deps_; }
+ const std::vector<Node*>& nodes() const { return nodes_; }
+ const std::vector<Deps*>& deps() const { return deps_; }
private:
// Updates the in-memory representation. Takes ownership of |deps|.
@@ -109,13 +109,18 @@
// Write a node name record, assigning it an id.
bool RecordId(Node* node);
+ /// Should be called before using file_. When false is returned, errno will
+ /// be set.
+ bool OpenForWriteIfNeeded();
+
bool needs_recompaction_;
FILE* file_;
+ std::string file_path_;
/// Maps id -> Node.
- vector<Node*> nodes_;
+ std::vector<Node*> nodes_;
/// Maps id -> deps of that id.
- vector<Deps*> deps_;
+ std::vector<Deps*> deps_;
friend struct DepsLogTest;
};
diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc
index 0cdeb45..4055941 100644
--- a/src/deps_log_test.cc
+++ b/src/deps_log_test.cc
@@ -23,6 +23,8 @@
#include "util.h"
#include "test.h"
+using namespace std;
+
namespace {
const char kTestFilename[] = "DepsLogTest-tempfile";
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index dc297c4..49af001 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -26,11 +26,15 @@
#include <sstream>
#include <windows.h>
#include <direct.h> // _mkdir
+#else
+#include <unistd.h>
#endif
#include "metrics.h"
#include "util.h"
+using namespace std;
+
namespace {
string DirName(const string& path) {
diff --git a/src/disk_interface.h b/src/disk_interface.h
index 145e089..bc29ab7 100644
--- a/src/disk_interface.h
+++ b/src/disk_interface.h
@@ -17,7 +17,6 @@
#include <map>
#include <string>
-using namespace std;
#include "timestamp.h"
@@ -35,8 +34,8 @@
/// Read and store in given string. On success, return Okay.
/// On error, return another Status and fill |err|.
- virtual Status ReadFile(const string& path, string* contents,
- string* err) = 0;
+ virtual Status ReadFile(const std::string& path, std::string* contents,
+ std::string* err) = 0;
};
/// Interface for accessing the disk.
@@ -46,25 +45,26 @@
struct DiskInterface: public FileReader {
/// stat() a file, returning the mtime, or 0 if missing and -1 on
/// other errors.
- virtual TimeStamp Stat(const string& path, string* err) const = 0;
+ virtual TimeStamp Stat(const std::string& path, std::string* err) const = 0;
/// Create a directory, returning false on failure.
- virtual bool MakeDir(const string& path) = 0;
+ virtual bool MakeDir(const std::string& path) = 0;
/// Create a file, with the specified name and contents
/// Returns true on success, false on failure
- virtual bool WriteFile(const string& path, const string& contents) = 0;
+ virtual bool WriteFile(const std::string& path,
+ const std::string& contents) = 0;
/// Remove the file named @a path. It behaves like 'rm -f path' so no errors
/// are reported if it does not exists.
/// @returns 0 if the file has been removed,
/// 1 if the file does not exist, and
/// -1 if an error occurs.
- virtual int RemoveFile(const string& path) = 0;
+ virtual int RemoveFile(const std::string& path) = 0;
/// Create all the parent directories for path; like mkdir -p
/// `basename path`.
- bool MakeDirs(const string& path);
+ bool MakeDirs(const std::string& path);
};
/// Implementation of DiskInterface that actually hits the disk.
@@ -75,11 +75,12 @@
#endif
{}
virtual ~RealDiskInterface() {}
- virtual TimeStamp Stat(const string& path, string* err) const;
- virtual bool MakeDir(const string& path);
- virtual bool WriteFile(const string& path, const string& contents);
- virtual Status ReadFile(const string& path, string* contents, string* err);
- virtual int RemoveFile(const string& path);
+ virtual TimeStamp Stat(const std::string& path, std::string* err) const;
+ virtual bool MakeDir(const std::string& path);
+ virtual bool WriteFile(const std::string& path, const std::string& contents);
+ virtual Status ReadFile(const std::string& path, std::string* contents,
+ std::string* err);
+ virtual int RemoveFile(const std::string& path);
/// Whether stat information can be cached. Only has an effect on Windows.
void AllowStatCache(bool allow);
@@ -89,10 +90,10 @@
/// Whether stat information can be cached.
bool use_cache_;
- typedef map<string, TimeStamp> DirCache;
+ typedef std::map<std::string, TimeStamp> DirCache;
// TODO: Neither a map nor a hashmap seems ideal here. If the statcache
// works out, come up with a better data structure.
- typedef map<string, DirCache> Cache;
+ typedef std::map<std::string, DirCache> Cache;
mutable Cache cache_;
#endif
};
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index bac515d..066c770 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -23,6 +23,8 @@
#include "graph.h"
#include "test.h"
+using namespace std;
+
namespace {
struct DiskInterfaceTest : public testing::Test {
@@ -190,7 +192,7 @@
TEST_F(DiskInterfaceTest, MakeDirs) {
string path = "path/with/double//slash/";
- EXPECT_TRUE(disk_.MakeDirs(path.c_str()));
+ EXPECT_TRUE(disk_.MakeDirs(path));
FILE* f = fopen((path + "a_file").c_str(), "w");
EXPECT_TRUE(f);
EXPECT_EQ(0, fclose(f));
diff --git a/src/dyndep.cc b/src/dyndep.cc
index 2aee601..b388e9b 100644
--- a/src/dyndep.cc
+++ b/src/dyndep.cc
@@ -24,6 +24,8 @@
#include "state.h"
#include "util.h"
+using namespace std;
+
bool DyndepLoader::LoadDyndeps(Node* node, std::string* err) const {
DyndepFile ddf;
return LoadDyndeps(node, &ddf, err);
diff --git a/src/dyndep_parser.cc b/src/dyndep_parser.cc
index baebbac..56da16f 100644
--- a/src/dyndep_parser.cc
+++ b/src/dyndep_parser.cc
@@ -22,6 +22,8 @@
#include "util.h"
#include "version.h"
+using namespace std;
+
DyndepParser::DyndepParser(State* state, FileReader* file_reader,
DyndepFile* dyndep_file)
: Parser(state, file_reader)
diff --git a/src/dyndep_parser.h b/src/dyndep_parser.h
index 09a3722..8f4c28d 100644
--- a/src/dyndep_parser.h
+++ b/src/dyndep_parser.h
@@ -27,17 +27,18 @@
DyndepFile* dyndep_file);
/// Parse a text string of input. Used by tests.
- bool ParseTest(const string& input, string* err) {
+ bool ParseTest(const std::string& input, std::string* err) {
return Parse("input", input, err);
}
private:
/// Parse a file, given its contents as a string.
- bool Parse(const string& filename, const string& input, string* err);
+ bool Parse(const std::string& filename, const std::string& input,
+ std:: string* err);
- bool ParseDyndepVersion(string* err);
- bool ParseLet(string* key, EvalString* val, string* err);
- bool ParseEdge(string* err);
+ bool ParseDyndepVersion(std::string* err);
+ bool ParseLet(std::string* key, EvalString* val, std::string* err);
+ bool ParseEdge(std::string* err);
DyndepFile* dyndep_file_;
BindingEnv env_;
diff --git a/src/dyndep_parser_test.cc b/src/dyndep_parser_test.cc
index 39ec657..1bba7ba 100644
--- a/src/dyndep_parser_test.cc
+++ b/src/dyndep_parser_test.cc
@@ -22,6 +22,8 @@
#include "state.h"
#include "test.h"
+using namespace std;
+
struct DyndepParserTest : public testing::Test {
void AssertParse(const char* input) {
DyndepParser parser(&state_, &fs_, &dyndep_file_);
diff --git a/src/edit_distance.cc b/src/edit_distance.cc
index 3bb62b8..34bf0e5 100644
--- a/src/edit_distance.cc
+++ b/src/edit_distance.cc
@@ -17,6 +17,8 @@
#include <algorithm>
#include <vector>
+using namespace std;
+
int EditDistance(const StringPiece& s1,
const StringPiece& s2,
bool allow_replacements,
diff --git a/src/eval_env.cc b/src/eval_env.cc
index e9b6c43..796a326 100644
--- a/src/eval_env.cc
+++ b/src/eval_env.cc
@@ -16,6 +16,8 @@
#include "eval_env.h"
+using namespace std;
+
string BindingEnv::LookupVariable(const string& var) {
map<string, string>::iterator i = bindings_.find(var);
if (i != bindings_.end())
diff --git a/src/eval_env.h b/src/eval_env.h
index 8fb9bf4..ca7daa4 100644
--- a/src/eval_env.h
+++ b/src/eval_env.h
@@ -18,7 +18,6 @@
#include <map>
#include <string>
#include <vector>
-using namespace std;
#include "string_piece.h"
@@ -27,7 +26,7 @@
/// An interface for a scope for variable (e.g. "$foo") lookups.
struct Env {
virtual ~Env() {}
- virtual string LookupVariable(const string& var) = 0;
+ virtual std::string LookupVariable(const std::string& var) = 0;
};
/// A tokenized string that contains variable references.
@@ -35,10 +34,10 @@
struct EvalString {
/// @return The evaluated string with variable expanded using value found in
/// environment @a env.
- string Evaluate(Env* env) const;
+ std::string Evaluate(Env* env) const;
/// @return The string with variables not expanded.
- string Unparse() const;
+ std::string Unparse() const;
void Clear() { parsed_.clear(); }
bool empty() const { return parsed_.empty(); }
@@ -48,32 +47,32 @@
/// Construct a human-readable representation of the parsed state,
/// for use in tests.
- string Serialize() const;
+ std::string Serialize() const;
private:
enum TokenType { RAW, SPECIAL };
- typedef vector<pair<string, TokenType> > TokenList;
+ typedef std::vector<std::pair<std::string, TokenType> > TokenList;
TokenList parsed_;
};
/// An invokable build command and associated metadata (description, etc.).
struct Rule {
- explicit Rule(const string& name) : name_(name) {}
+ explicit Rule(const std::string& name) : name_(name) {}
- const string& name() const { return name_; }
+ const std::string& name() const { return name_; }
- void AddBinding(const string& key, const EvalString& val);
+ void AddBinding(const std::string& key, const EvalString& val);
- static bool IsReservedBinding(const string& var);
+ static bool IsReservedBinding(const std::string& var);
- const EvalString* GetBinding(const string& key) const;
+ const EvalString* GetBinding(const std::string& key) const;
private:
// Allow the parsers to reach into this object and fill out its fields.
friend struct ManifestParser;
- string name_;
- typedef map<string, EvalString> Bindings;
+ std::string name_;
+ typedef std::map<std::string, EvalString> Bindings;
Bindings bindings_;
};
@@ -84,26 +83,26 @@
explicit BindingEnv(BindingEnv* parent) : parent_(parent) {}
virtual ~BindingEnv() {}
- virtual string LookupVariable(const string& var);
+ virtual std::string LookupVariable(const std::string& var);
void AddRule(const Rule* rule);
- const Rule* LookupRule(const string& rule_name);
- const Rule* LookupRuleCurrentScope(const string& rule_name);
- const map<string, const Rule*>& GetRules() const;
+ const Rule* LookupRule(const std::string& rule_name);
+ const Rule* LookupRuleCurrentScope(const std::string& rule_name);
+ const std::map<std::string, const Rule*>& GetRules() const;
- void AddBinding(const string& key, const string& val);
+ void AddBinding(const std::string& key, const std::string& val);
/// This is tricky. Edges want lookup scope to go in this order:
/// 1) value set on edge itself (edge_->env_)
/// 2) value set on rule, with expansion in the edge's scope
/// 3) value set on enclosing scope of edge (edge_->env_->parent_)
/// This function takes as parameters the necessary info to do (2).
- string LookupWithFallback(const string& var, const EvalString* eval,
- Env* env);
+ std::string LookupWithFallback(const std::string& var, const EvalString* eval,
+ Env* env);
private:
- map<string, string> bindings_;
- map<string, const Rule*> rules_;
+ std::map<std::string, std::string> bindings_;
+ std::map<std::string, const Rule*> rules_;
BindingEnv* parent_;
};
diff --git a/src/graph.cc b/src/graph.cc
index facb76d..78d0d49 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -14,6 +14,7 @@
#include "graph.h"
+#include <algorithm>
#include <assert.h>
#include <stdio.h>
@@ -27,6 +28,8 @@
#include "state.h"
#include "util.h"
+using namespace std;
+
bool Node::Stat(DiskInterface* disk_interface, string* err) {
return (mtime_ = disk_interface->Stat(path_, err)) != -1;
}
@@ -222,8 +225,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()) {
@@ -383,7 +386,7 @@
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);
@@ -511,6 +514,17 @@
return true;
}
+struct matches {
+ explicit 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");
@@ -541,9 +555,15 @@
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;
}
@@ -552,12 +572,22 @@
// 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());
diff --git a/src/graph.h b/src/graph.h
index 19b25c4..8c51782 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -15,9 +15,9 @@
#ifndef NINJA_GRAPH_H_
#define NINJA_GRAPH_H_
+#include <set>
#include <string>
#include <vector>
-using namespace std;
#include "dyndep.h"
#include "eval_env.h"
@@ -36,7 +36,7 @@
/// Information about a node in the dependency graph: the file, whether
/// it's dirty, mtime, etc.
struct Node {
- Node(const string& path, uint64_t slash_bits)
+ Node(const std::string& path, uint64_t slash_bits)
: path_(path),
slash_bits_(slash_bits),
mtime_(-1),
@@ -46,10 +46,10 @@
id_(-1) {}
/// Return false on error.
- bool Stat(DiskInterface* disk_interface, string* err);
+ bool Stat(DiskInterface* disk_interface, std::string* err);
/// Return false on error.
- bool StatIfNecessary(DiskInterface* disk_interface, string* err) {
+ bool StatIfNecessary(DiskInterface* disk_interface, std::string* err) {
if (status_known())
return true;
return Stat(disk_interface, err);
@@ -74,13 +74,13 @@
return mtime_ != -1;
}
- const string& path() const { return path_; }
+ const std::string& path() const { return path_; }
/// Get |path()| but use slash_bits to convert back to original slash styles.
- string PathDecanonicalized() const {
+ std::string PathDecanonicalized() const {
return PathDecanonicalized(path_, slash_bits_);
}
- static string PathDecanonicalized(const string& path,
- uint64_t slash_bits);
+ static std::string PathDecanonicalized(const std::string& path,
+ uint64_t slash_bits);
uint64_t slash_bits() const { return slash_bits_; }
TimeStamp mtime() const { return mtime_; }
@@ -98,13 +98,13 @@
int id() const { return id_; }
void set_id(int id) { id_ = id; }
- const vector<Edge*>& out_edges() const { return out_edges_; }
+ const std::vector<Edge*>& out_edges() const { return out_edges_; }
void AddOutEdge(Edge* edge) { out_edges_.push_back(edge); }
void Dump(const char* prefix="") const;
private:
- string path_;
+ std::string path_;
/// Set bits starting from lowest for backslashes that were normalized to
/// forward slashes by CanonicalizePath. See |PathDecanonicalized|.
@@ -130,7 +130,7 @@
Edge* in_edge_;
/// All Edges that use this Node as an input.
- vector<Edge*> out_edges_;
+ std::vector<Edge*> out_edges_;
/// A dense integer id for the node, assigned and used by DepsLog.
int id_;
@@ -144,10 +144,11 @@
VisitDone
};
- Edge() : rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL),
- mark_(VisitNone), outputs_ready_(false), deps_loaded_(false),
- deps_missing_(false), implicit_deps_(0), order_only_deps_(0),
- implicit_outs_(0) {}
+ Edge()
+ : rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL), mark_(VisitNone),
+ id_(0), outputs_ready_(false), deps_loaded_(false),
+ deps_missing_(false), implicit_deps_(0), order_only_deps_(0),
+ implicit_outs_(0) {}
/// Return true if all inputs' in-edges are ready.
bool AllInputsReady() const;
@@ -158,13 +159,13 @@
std::string EvaluateCommand(bool incl_rsp_file = false) const;
/// Returns the shell-escaped value of |key|.
- std::string GetBinding(const string& key) const;
- bool GetBindingBool(const string& key) const;
+ std::string GetBinding(const std::string& key) const;
+ bool GetBindingBool(const std::string& key) const;
/// Like GetBinding("depfile"), but without shell escaping.
- string GetUnescapedDepfile() const;
+ std::string GetUnescapedDepfile() const;
/// Like GetBinding("dyndep"), but without shell escaping.
- string GetUnescapedDyndep() const;
+ std::string GetUnescapedDyndep() const;
/// Like GetBinding("rspfile"), but without shell escaping.
std::string GetUnescapedRspfile() const;
@@ -172,11 +173,12 @@
const Rule* rule_;
Pool* pool_;
- vector<Node*> inputs_;
- vector<Node*> outputs_;
+ std::vector<Node*> inputs_;
+ std::vector<Node*> outputs_;
Node* dyndep_;
BindingEnv* env_;
VisitMark mark_;
+ size_t id_;
bool outputs_ready_;
bool deps_loaded_;
bool deps_missing_;
@@ -219,6 +221,13 @@
bool maybe_phonycycle_diagnostic() const;
};
+struct EdgeCmp {
+ bool operator()(const Edge* a, const Edge* b) const {
+ return a->id_ < b->id_;
+ }
+};
+
+typedef std::set<Edge*, EdgeCmp> EdgeSet;
/// ImplicitDepLoader loads implicit dependencies, as referenced via the
/// "depfile" attribute in build files.
@@ -232,7 +241,7 @@
/// Load implicit dependencies for \a edge.
/// @return false on error (without filling \a err if info is just missing
// or out of date).
- bool LoadDeps(Edge* edge, string* err);
+ bool LoadDeps(Edge* edge, std::string* err);
DepsLog* deps_log() const {
return deps_log_;
@@ -241,15 +250,15 @@
private:
/// Load implicit dependencies for \a edge from a depfile attribute.
/// @return false on error (without filling \a err if info is just missing).
- bool LoadDepFile(Edge* edge, const string& path, string* err);
+ bool LoadDepFile(Edge* edge, const std::string& path, std::string* err);
/// Load implicit dependencies for \a edge from the DepsLog.
/// @return false on error (without filling \a err if info is just missing).
- bool LoadDepsFromLog(Edge* edge, string* err);
+ bool LoadDepsFromLog(Edge* edge, std::string* err);
/// Preallocate \a count spaces in the input array on \a edge, returning
/// an iterator pointing at the first new space.
- vector<Node*>::iterator PreallocateSpace(Edge* edge, int count);
+ std::vector<Node*>::iterator PreallocateSpace(Edge* edge, int count);
/// If we don't have a edge that generates this input already,
/// create one; this makes us not abort if the input is missing,
@@ -279,12 +288,12 @@
/// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_|
/// state accordingly.
/// Returns false on failure.
- bool RecomputeDirty(Node* node, string* err);
+ bool RecomputeDirty(Node* node, std::string* err);
/// Recompute whether any output of the edge is dirty, if so sets |*dirty|.
/// Returns false on failure.
bool RecomputeOutputsDirty(Edge* edge, Node* most_recent_input,
- bool* dirty, string* err);
+ bool* dirty, std::string* err);
BuildLog* build_log() const {
return build_log_;
@@ -301,17 +310,17 @@
/// build graph with the new information. One overload accepts
/// a caller-owned 'DyndepFile' object in which to store the
/// information loaded from the dyndep file.
- bool LoadDyndeps(Node* node, string* err) const;
- bool LoadDyndeps(Node* node, DyndepFile* ddf, string* err) const;
+ bool LoadDyndeps(Node* node, std::string* err) const;
+ bool LoadDyndeps(Node* node, DyndepFile* ddf, std::string* err) const;
private:
- bool RecomputeDirty(Node* node, vector<Node*>* stack, string* err);
- bool VerifyDAG(Node* node, vector<Node*>* stack, string* err);
+ bool RecomputeDirty(Node* node, std::vector<Node*>* stack, std::string* err);
+ bool VerifyDAG(Node* node, std::vector<Node*>* stack, std::string* err);
/// Recompute whether a given single output should be marked dirty.
/// Returns true if so.
- bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input,
- const string& command, Node* output);
+ bool RecomputeOutputDirty(const Edge* edge, const Node* most_recent_input,
+ const std::string& command, Node* output);
BuildLog* build_log_;
DiskInterface* disk_interface_;
diff --git a/src/graph_test.cc b/src/graph_test.cc
index c8cca1c..14f6375 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -17,6 +17,8 @@
#include "test.h"
+using namespace std;
+
struct GraphTest : public StateTestWithBuiltinRules {
GraphTest() : scan_(&state_, NULL, NULL, &fs_, NULL) {}
@@ -218,7 +220,7 @@
"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/graphviz.cc b/src/graphviz.cc
index 0d07251..37b7108 100644
--- a/src/graphviz.cc
+++ b/src/graphviz.cc
@@ -20,6 +20,8 @@
#include "dyndep.h"
#include "graph.h"
+using namespace std;
+
void GraphViz::AddTarget(Node* node) {
if (visited_nodes_.find(node) != visited_nodes_.end())
return;
diff --git a/src/graphviz.h b/src/graphviz.h
index 601c9b2..3a3282e 100644
--- a/src/graphviz.h
+++ b/src/graphviz.h
@@ -18,6 +18,7 @@
#include <set>
#include "dyndep.h"
+#include "graph.h"
struct DiskInterface;
struct Node;
@@ -34,7 +35,7 @@
DyndepLoader dyndep_loader_;
std::set<Node*> visited_nodes_;
- std::set<Edge*> visited_edges_;
+ EdgeSet visited_edges_;
};
#endif // NINJA_GRAPHVIZ_H_
diff --git a/src/hash_collision_bench.cc b/src/hash_collision_bench.cc
index ff947dc..8f37ed0 100644
--- a/src/hash_collision_bench.cc
+++ b/src/hash_collision_bench.cc
@@ -15,20 +15,22 @@
#include "build_log.h"
#include <algorithm>
-using namespace std;
#include <stdlib.h>
#include <time.h>
+using namespace std;
+
int random(int low, int high) {
return int(low + (rand() / double(RAND_MAX)) * (high - low) + 0.5);
}
void RandomCommand(char** s) {
int len = random(5, 100);
- *s = new char[len];
+ *s = new char[len+1];
for (int i = 0; i < len; ++i)
(*s)[i] = (char)random(32, 127);
+ (*s)[len] = '\0';
}
int main() {
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
index 79bf5b4..9f8dfc2 100644
--- a/src/includes_normalize-win32.cc
+++ b/src/includes_normalize-win32.cc
@@ -24,6 +24,8 @@
#include <windows.h>
+using namespace std;
+
namespace {
bool InternalGetFullPathName(const StringPiece& file_name, char* buffer,
diff --git a/src/includes_normalize.h b/src/includes_normalize.h
index 0339581..7d50556 100644
--- a/src/includes_normalize.h
+++ b/src/includes_normalize.h
@@ -14,7 +14,6 @@
#include <string>
#include <vector>
-using namespace std;
struct StringPiece;
@@ -22,18 +21,20 @@
/// TODO: this likely duplicates functionality of CanonicalizePath; refactor.
struct IncludesNormalize {
/// Normalize path relative to |relative_to|.
- IncludesNormalize(const string& relative_to);
+ IncludesNormalize(const std::string& relative_to);
// Internal utilities made available for testing, maybe useful otherwise.
- static string AbsPath(StringPiece s, string* err);
- static string Relativize(StringPiece path,
- const vector<StringPiece>& start_list, string* err);
+ static std::string AbsPath(StringPiece s, std::string* err);
+ static std::string Relativize(StringPiece path,
+ const std::vector<StringPiece>& start_list,
+ std::string* err);
/// Normalize by fixing slashes style, fixing redundant .. and . and makes the
/// path |input| relative to |this->relative_to_| and store to |result|.
- bool Normalize(const string& input, string* result, string* err) const;
+ bool Normalize(const std::string& input, std::string* result,
+ std::string* err) const;
private:
- string relative_to_;
- vector<StringPiece> split_relative_to_;
+ std::string relative_to_;
+ std::vector<StringPiece> split_relative_to_;
};
diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc
index dbcdbe0..9214f53 100644
--- a/src/includes_normalize_test.cc
+++ b/src/includes_normalize_test.cc
@@ -22,6 +22,8 @@
#include "test.h"
#include "util.h"
+using namespace std;
+
namespace {
string GetCurDir() {
diff --git a/src/inline.sh b/src/inline.sh
index b64e8ca..5092fa2 100755
--- a/src/inline.sh
+++ b/src/inline.sh
@@ -19,7 +19,14 @@
# stdin and writes stdout.
varname="$1"
-echo "const char $varname[] ="
-od -t x1 -A n -v | sed -e 's|^[\t ]\{0,\}$||g; s|[\t ]\{1,\}| |g; s| \{1,\}$||g; s| |\\x|g; s|^|"|; s|$|"|'
-echo ";"
+# 'od' and 'sed' may not be available on all platforms, and may not support the
+# flags used here. We must ensure that the script exits with a non-zero exit
+# code in those cases.
+byte_vals=$(od -t x1 -A n -v) || exit 1
+escaped_byte_vals=$(echo "${byte_vals}" \
+ | sed -e 's|^[\t ]\{0,\}$||g; s|[\t ]\{1,\}| |g; s| \{1,\}$||g; s| |\\x|g; s|^|"|; s|$|"|') \
+ || exit 1
+
+# Only write output once we have successfully generated the required data
+printf "const char %s[] = \n%s;" "${varname}" "${escaped_byte_vals}"
diff --git a/src/lexer.cc b/src/lexer.cc
index 35ae97b..6e4a470 100644
--- a/src/lexer.cc
+++ b/src/lexer.cc
@@ -1,4 +1,4 @@
-/* Generated by re2c 0.16 */
+/* Generated by re2c 1.1.1 */
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,6 +20,8 @@
#include "eval_env.h"
#include "util.h"
+using namespace std;
+
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
@@ -233,8 +235,7 @@
goto yy5;
yy9:
yyaccept = 0;
- q = ++p;
- yych = *p;
+ yych = *(q = ++p);
if (yybm[0+yych] & 32) {
goto yy9;
}
@@ -252,8 +253,7 @@
if (yych <= 0x00) goto yy5;
goto yy33;
yy13:
- ++p;
- yych = *p;
+ yych = *++p;
yy14:
if (yybm[0+yych] & 64) {
goto yy13;
@@ -290,8 +290,8 @@
if (yych == 'u') goto yy41;
goto yy14;
yy26:
- ++p;
- if ((yych = *p) == '|') goto yy42;
+ yych = *++p;
+ if (yych == '|') goto yy42;
{ token = PIPE; break; }
yy28:
++p;
@@ -307,8 +307,7 @@
goto yy5;
}
yy32:
- ++p;
- yych = *p;
+ yych = *++p;
yy33:
if (yybm[0+yych] & 128) {
goto yy32;
@@ -380,14 +379,14 @@
if (yych == 'u') goto yy61;
goto yy14;
yy53:
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = POOL; break; }
yy55:
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = RULE; break; }
@@ -396,8 +395,8 @@
if (yych == 'i') goto yy62;
goto yy14;
yy58:
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = BUILD; break; }
@@ -426,22 +425,22 @@
if (yych == 'j') goto yy70;
goto yy14;
yy66:
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = DEFAULT; break; }
yy68:
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = INCLUDE; break; }
yy70:
yych = *++p;
if (yych != 'a') goto yy14;
- ++p;
- if (yybm[0+(yych = *p)] & 64) {
+ yych = *++p;
+ if (yybm[0+yych] & 64) {
goto yy13;
}
{ token = SUBNINJA; break; }
@@ -521,8 +520,7 @@
yy78:
{ break; }
yy79:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 128) {
goto yy79;
}
@@ -600,8 +598,7 @@
return false;
}
yy93:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 128) {
goto yy93;
}
@@ -681,8 +678,7 @@
return Error("unexpected EOF", err);
}
yy100:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 16) {
goto yy100;
}
@@ -704,8 +700,8 @@
}
}
yy105:
- ++p;
- if ((yych = *p) == '\n') goto yy108;
+ yych = *++p;
+ if (yych == '\n') goto yy108;
{
last_token_ = start;
return Error(DescribeLastError(), err);
@@ -750,8 +746,7 @@
return Error("bad $-escape (literal $ must be written as $$)", err);
}
yy112:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 32) {
goto yy112;
}
@@ -775,8 +770,7 @@
continue;
}
yy120:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 64) {
goto yy120;
}
@@ -797,15 +791,13 @@
}
goto yy111;
yy126:
- ++p;
- yych = *p;
+ yych = *++p;
if (yych == ' ') goto yy126;
{
continue;
}
yy129:
- ++p;
- yych = *p;
+ yych = *++p;
if (yybm[0+yych] & 128) {
goto yy129;
}
diff --git a/src/lexer.h b/src/lexer.h
index f366556..788d948 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -55,7 +55,7 @@
/// If the last token read was an ERROR token, provide more info
/// or the empty string.
- string DescribeLastError();
+ std::string DescribeLastError();
/// Start parsing some input.
void Start(StringPiece filename, StringPiece input);
@@ -71,30 +71,30 @@
/// Read a simple identifier (a rule or variable name).
/// Returns false if a name can't be read.
- bool ReadIdent(string* out);
+ bool ReadIdent(std::string* out);
/// Read a path (complete with $escapes).
/// Returns false only on error, returned path may be empty if a delimiter
/// (space, newline) is hit.
- bool ReadPath(EvalString* path, string* err) {
+ bool ReadPath(EvalString* path, std::string* err) {
return ReadEvalString(path, true, err);
}
/// Read the value side of a var = value line (complete with $escapes).
/// Returns false only on error.
- bool ReadVarValue(EvalString* value, string* err) {
+ bool ReadVarValue(EvalString* value, std::string* err) {
return ReadEvalString(value, false, err);
}
/// Construct an error message with context.
- bool Error(const string& message, string* err);
+ bool Error(const std::string& message, std::string* err);
private:
/// Skip past whitespace (called after each read token/ident/etc.).
void EatWhitespace();
/// Read a $-escaped string.
- bool ReadEvalString(EvalString* eval, bool path, string* err);
+ bool ReadEvalString(EvalString* eval, bool path, std::string* err);
StringPiece filename_;
StringPiece input_;
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
index c1fb822..88007e7 100644
--- a/src/lexer.in.cc
+++ b/src/lexer.in.cc
@@ -19,6 +19,8 @@
#include "eval_env.h"
#include "util.h"
+using namespace std;
+
bool Lexer::Error(const string& message, string* err) {
// Compute line/column.
int line = 1;
diff --git a/src/lexer_test.cc b/src/lexer_test.cc
index 331d8e1..c5c416d 100644
--- a/src/lexer_test.cc
+++ b/src/lexer_test.cc
@@ -17,6 +17,8 @@
#include "eval_env.h"
#include "test.h"
+using namespace std;
+
TEST(Lexer, ReadVarValue) {
Lexer lexer("plain text $var $VaR ${x}\n");
EvalString eval;
diff --git a/src/line_printer.cc b/src/line_printer.cc
index c93173e..68c58ad 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -30,6 +30,8 @@
#include "util.h"
+using namespace std;
+
LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
const char* term = getenv("TERM");
#ifndef _WIN32
diff --git a/src/line_printer.h b/src/line_printer.h
index 92d4dc4..a8ec9ff 100644
--- a/src/line_printer.h
+++ b/src/line_printer.h
@@ -17,7 +17,6 @@
#include <stddef.h>
#include <string>
-using namespace std;
/// Prints lines of text, possibly overprinting previously printed lines
/// if the terminal supports it.
@@ -35,10 +34,10 @@
};
/// Overprints the current line. If type is ELIDE, elides to_print to fit on
/// one line.
- void Print(string to_print, LineType type);
+ void Print(std::string to_print, LineType type);
/// Prints a string on a new line, not overprinting previous output.
- void PrintOnNewLine(const string& to_print);
+ void PrintOnNewLine(const std::string& to_print);
/// Lock or unlock the console. Any output sent to the LinePrinter while the
/// console is locked will not be printed until it is unlocked.
@@ -58,13 +57,13 @@
bool console_locked_;
/// Buffered current line while console is locked.
- string line_buffer_;
+ std::string line_buffer_;
/// Buffered line type while console is locked.
LineType line_type_;
/// Buffered console output while console is locked.
- string output_buffer_;
+ std::string output_buffer_;
#ifdef _WIN32
void* console_;
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..54ad3f4 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -23,6 +23,8 @@
#include "util.h"
#include "version.h"
+using namespace std;
+
ManifestParser::ManifestParser(State* state, FileReader* file_reader,
ManifestParserOptions options)
: Parser(state, file_reader),
@@ -200,10 +202,7 @@
return false;
} while (!eval.empty());
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- return true;
+ return ExpectToken(Lexer::NEWLINE, err);
}
bool ManifestParser::ParseEdge(string* err) {
@@ -228,7 +227,7 @@
for (;;) {
EvalString out;
if (!lexer_.ReadPath(&out, err))
- return err;
+ return false;
if (out.empty())
break;
outs.push_back(out);
@@ -266,7 +265,7 @@
for (;;) {
EvalString in;
if (!lexer_.ReadPath(&in, err))
- return err;
+ return false;
if (in.empty())
break;
ins.push_back(in);
@@ -379,14 +378,6 @@
}
}
- // 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.h b/src/manifest_parser.h
index e14d069..954cf46 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -44,24 +44,25 @@
ManifestParserOptions options = ManifestParserOptions());
/// Parse a text string of input. Used by tests.
- bool ParseTest(const string& input, string* err) {
+ bool ParseTest(const std::string& input, std::string* err) {
quiet_ = true;
return Parse("input", input, err);
}
private:
/// Parse a file, given its contents as a string.
- bool Parse(const string& filename, const string& input, string* err);
+ bool Parse(const std::string& filename, const std::string& input,
+ std::string* err);
/// Parse various statement types.
- bool ParsePool(string* err);
- bool ParseRule(string* err);
- bool ParseLet(string* key, EvalString* val, string* err);
- bool ParseEdge(string* err);
- bool ParseDefault(string* err);
+ bool ParsePool(std::string* err);
+ bool ParseRule(std::string* err);
+ bool ParseLet(std::string* key, EvalString* val, std::string* err);
+ bool ParseEdge(std::string* err);
+ bool ParseDefault(std::string* err);
/// Parse either a 'subninja' or 'include' line.
- bool ParseFileInclude(bool new_scope, string* err);
+ bool ParseFileInclude(bool new_scope, std::string* err);
BindingEnv* env_;
ManifestParserOptions options_;
diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc
index 67d11f9..853d8e0 100644
--- a/src/manifest_parser_perftest.cc
+++ b/src/manifest_parser_perftest.cc
@@ -25,6 +25,9 @@
#ifdef _WIN32
#include "getopt.h"
#include <direct.h>
+#elif defined(_AIX)
+#include "getopt.h"
+#include <unistd.h>
#else
#include <getopt.h>
#include <unistd.h>
@@ -37,6 +40,8 @@
#include "state.h"
#include "util.h"
+using namespace std;
+
bool WriteFakeManifests(const string& dir, string* err) {
RealDiskInterface disk_interface;
TimeStamp mtime = disk_interface.Stat(dir + "/build.ninja", err);
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index f2b7467..ec2eeed 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -21,6 +21,8 @@
#include "state.h"
#include "test.h"
+using namespace std;
+
struct ParserTest : public testing::Test {
void AssertParse(const char* input) {
ManifestParser parser(&state, &fs_);
@@ -858,11 +860,10 @@
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/metrics.cc b/src/metrics.cc
index a7d3c7a..dbaf221 100644
--- a/src/metrics.cc
+++ b/src/metrics.cc
@@ -28,6 +28,8 @@
#include "util.h"
+using namespace std;
+
Metrics* g_metrics = NULL;
namespace {
diff --git a/src/metrics.h b/src/metrics.h
index b6da859..11239b5 100644
--- a/src/metrics.h
+++ b/src/metrics.h
@@ -17,7 +17,6 @@
#include <string>
#include <vector>
-using namespace std;
#include "util.h" // For int64_t.
@@ -26,7 +25,7 @@
/// A single metrics we're tracking, like "depfile load time".
struct Metric {
- string name;
+ std::string name;
/// Number of times we've hit the code path.
int count;
/// Total time (in micros) we've spent on the code path.
@@ -49,13 +48,13 @@
/// The singleton that stores metrics and prints the report.
struct Metrics {
- Metric* NewMetric(const string& name);
+ Metric* NewMetric(const std::string& name);
/// Print a summary report to stdout.
void Report();
private:
- vector<Metric*> metrics_;
+ std::vector<Metric*> metrics_;
};
/// Get the current time as relative to some epoch.
diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc
index ca93638..9aea767 100644
--- a/src/minidump-win32.cc
+++ b/src/minidump-win32.cc
@@ -19,6 +19,8 @@
#include "util.h"
+using namespace std;
+
typedef BOOL (WINAPI *MiniDumpWriteDumpFunc) (
IN HANDLE,
IN DWORD,
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
index de6147a..1148ae5 100644
--- a/src/msvc_helper-win32.cc
+++ b/src/msvc_helper-win32.cc
@@ -18,6 +18,8 @@
#include "util.h"
+using namespace std;
+
namespace {
string Replace(const string& input, const string& find, const string& replace) {
diff --git a/src/msvc_helper.h b/src/msvc_helper.h
index 70d1fff..568b9f9 100644
--- a/src/msvc_helper.h
+++ b/src/msvc_helper.h
@@ -13,9 +13,8 @@
// limitations under the License.
#include <string>
-using namespace std;
-string EscapeForDepfile(const string& path);
+std::string EscapeForDepfile(const std::string& path);
/// Wraps a synchronous execution of a CL subprocess.
struct CLWrapper {
@@ -27,7 +26,7 @@
/// Start a process and gather its raw output. Returns its exit code.
/// Crashes (calls Fatal()) on error.
- int Run(const string& command, string* output);
+ int Run(const std::string& command, std::string* output);
void* env_block_;
};
diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc
index 644b2a2..7d59307 100644
--- a/src/msvc_helper_main-win32.cc
+++ b/src/msvc_helper_main-win32.cc
@@ -24,6 +24,8 @@
#include "getopt.h"
+using namespace std;
+
namespace {
void Usage() {
diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc
index eaae51f..d9e2ee6 100644
--- a/src/msvc_helper_test.cc
+++ b/src/msvc_helper_test.cc
@@ -17,6 +17,8 @@
#include "test.h"
#include "util.h"
+using namespace std;
+
TEST(EscapeForDepfileTest, SpacesInFilename) {
ASSERT_EQ("sub\\some\\ sdk\\foo.h",
EscapeForDepfile("sub\\some sdk\\foo.h"));
diff --git a/src/ninja.cc b/src/ninja.cc
index c24f09d..eb97320 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"
@@ -45,6 +46,8 @@
#include "util.h"
#include "version.h"
+using namespace std;
+
#ifdef _MSC_VER
// Defined in msvc_helper_main-win32.cc.
int MSVCHelperMain(int argc, char** argv);
@@ -73,10 +76,6 @@
/// 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
@@ -94,7 +93,7 @@
/// Loaded state (rules, nodes).
State state_;
- /// Functions for accesssing the disk.
+ /// Functions for accessing the disk.
RealDiskInterface disk_interface_;
/// The build directory, used for storing the build log etc.
@@ -123,17 +122,19 @@
int ToolTargets(const Options* options, int argc, char* argv[]);
int ToolCommands(const Options* options, int argc, char* argv[]);
int ToolClean(const Options* options, int argc, char* argv[]);
+ 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,7 +155,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
@@ -612,7 +613,7 @@
}
enum PrintCommandMode { PCM_Single, PCM_All };
-void PrintCommands(Edge* edge, set<Edge*>* seen, PrintCommandMode mode) {
+void PrintCommands(Edge* edge, EdgeSet* seen, PrintCommandMode mode) {
if (!edge)
return;
if (!seen->insert(edge).second)
@@ -663,7 +664,7 @@
return 1;
}
- set<Edge*> seen;
+ EdgeSet seen;
for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
PrintCommands((*in)->in_edge(), &seen, mode);
@@ -719,6 +720,11 @@
}
}
+int NinjaMain::ToolCleanDead(const Options* options, int argc, char* argv[]) {
+ Cleaner cleaner(&state_, config_, &disk_interface_);
+ return cleaner.CleanDead(build_log_.entries());
+}
+
void EncodeJSONString(const char *str) {
while (*str) {
if (*str == '"' || *str == '\\')
@@ -803,12 +809,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;
}
@@ -845,13 +853,71 @@
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[]) {
+ // The restat tool uses getopt, and expects argv[0] to contain the name of the
+ // tool, i.e. "restat"
+ argc++;
+ argv--;
+
+ optind = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, const_cast<char*>("h"))) != -1) {
+ switch (opt) {
+ case 'h':
+ default:
+ printf("usage: ninja -t restat [outputs]\n");
+ return 1;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ 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_, argc, argv, &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 =
@@ -904,8 +970,12 @@
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_FLAGS, &NinjaMain::ToolRestat },
{ "rules", "list all rules",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRules },
+ { "cleandead", "clean built files that are no longer produced by the manifest",
+ Tool::RUN_AFTER_LOGS, &NinjaMain::ToolCleanDead },
{ "urtle", NULL,
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle },
{ NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
@@ -982,14 +1052,13 @@
}
}
-/// Set a warning flag. Returns false if Ninja should exit instead of
+/// Set a warning flag. Returns false if Ninja should exit instead of
/// continuing.
bool WarningEnable(const string& name, Options* options) {
if (name == "list") {
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") {
@@ -1004,11 +1073,9 @@
} 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 =
@@ -1030,17 +1097,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());
@@ -1065,17 +1136,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());
@@ -1284,11 +1359,6 @@
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/ninja_test.cc b/src/ninja_test.cc
index d642c5c..b40e176 100644
--- a/src/ninja_test.cc
+++ b/src/ninja_test.cc
@@ -28,6 +28,8 @@
#include "test.h"
#include "line_printer.h"
+using namespace std;
+
struct RegisteredTest {
testing::Test* (*factory)();
const char *name;
diff --git a/src/parser.cc b/src/parser.cc
index 745c532..756922d 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -17,6 +17,8 @@
#include "disk_interface.h"
#include "metrics.h"
+using namespace std;
+
bool Parser::Load(const string& filename, string* err, Lexer* parent) {
METRIC_RECORD(".ninja parse");
string contents;
diff --git a/src/parser.h b/src/parser.h
index e2d2b97..011fad8 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -17,8 +17,6 @@
#include <string>
-using namespace std;
-
#include "lexer.h"
struct FileReader;
@@ -30,12 +28,12 @@
: state_(state), file_reader_(file_reader) {}
/// Load and parse a file.
- bool Load(const string& filename, string* err, Lexer* parent = NULL);
+ bool Load(const std::string& filename, std::string* err, Lexer* parent = NULL);
protected:
/// If the next token is not \a expected, produce an error string
/// saying "expected foo, got bar".
- bool ExpectToken(Lexer::Token expected, string* err);
+ bool ExpectToken(Lexer::Token expected, std::string* err);
State* state_;
FileReader* file_reader_;
@@ -43,8 +41,8 @@
private:
/// Parse a file, given its contents as a string.
- virtual bool Parse(const string& filename, const string& input,
- string* err) = 0;
+ virtual bool Parse(const std::string& filename, const std::string& input,
+ std::string* err) = 0;
};
#endif // NINJA_PARSER_H_
diff --git a/src/state.cc b/src/state.cc
index 74cf4c1..a33d5a8 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -22,6 +22,7 @@
#include "metrics.h"
#include "util.h"
+using namespace std;
void Pool::EdgeScheduled(const Edge& edge) {
if (depth_ != 0)
@@ -38,7 +39,7 @@
delayed_.insert(edge);
}
-void Pool::RetrieveReadyEdges(set<Edge*>* ready_queue) {
+void Pool::RetrieveReadyEdges(EdgeSet* ready_queue) {
DelayedEdges::iterator it = delayed_.begin();
while (it != delayed_.end()) {
Edge* edge = *it;
@@ -61,14 +62,6 @@
}
}
-// static
-bool Pool::WeightedEdgeCmp(const Edge* a, const Edge* b) {
- if (!a) return b;
- if (!b) return false;
- int weight_diff = a->weight() - b->weight();
- return ((weight_diff < 0) || (weight_diff == 0 && a < b));
-}
-
Pool State::kDefaultPool("", 0);
Pool State::kConsolePool("console", 1);
const Rule State::kPhonyRule("phony");
@@ -96,6 +89,7 @@
edge->rule_ = rule;
edge->pool_ = &State::kDefaultPool;
edge->env_ = &bindings_;
+ edge->id_ = edges_.size();
edges_.push_back(edge);
return edge;
}
diff --git a/src/state.h b/src/state.h
index 6fe886c..72c5b33 100644
--- a/src/state.h
+++ b/src/state.h
@@ -19,9 +19,9 @@
#include <set>
#include <string>
#include <vector>
-using namespace std;
#include "eval_env.h"
+#include "graph.h"
#include "hash_map.h"
#include "util.h"
@@ -38,13 +38,13 @@
/// the total scheduled weight diminishes enough (i.e. when a scheduled edge
/// completes).
struct Pool {
- Pool(const string& name, int depth)
- : name_(name), current_use_(0), depth_(depth), delayed_(&WeightedEdgeCmp) {}
+ Pool(const std::string& name, int depth)
+ : name_(name), current_use_(0), depth_(depth), delayed_() {}
// A depth of 0 is infinite
bool is_valid() const { return depth_ >= 0; }
int depth() const { return depth_; }
- const string& name() const { return name_; }
+ const std::string& name() const { return name_; }
int current_use() const { return current_use_; }
/// true if the Pool might delay this edge
@@ -62,22 +62,29 @@
void DelayEdge(Edge* edge);
/// Pool will add zero or more edges to the ready_queue
- void RetrieveReadyEdges(set<Edge*>* ready_queue);
+ void RetrieveReadyEdges(EdgeSet* ready_queue);
/// Dump the Pool and its edges (useful for debugging).
void Dump() const;
private:
- string name_;
+ std::string name_;
/// |current_use_| is the total of the weights of the edges which are
/// currently scheduled in the Plan (i.e. the edges in Plan::ready_).
int current_use_;
int depth_;
- static bool WeightedEdgeCmp(const Edge* a, const Edge* b);
+ struct WeightedEdgeCmp {
+ bool operator()(const Edge* a, const Edge* b) const {
+ if (!a) return b;
+ if (!b) return false;
+ int weight_diff = a->weight() - b->weight();
+ return ((weight_diff < 0) || (weight_diff == 0 && EdgeCmp()(a, b)));
+ }
+ };
- typedef set<Edge*,bool(*)(const Edge*, const Edge*)> DelayedEdges;
+ typedef std::set<Edge*, WeightedEdgeCmp> DelayedEdges;
DelayedEdges delayed_;
};
@@ -90,17 +97,17 @@
State();
void AddPool(Pool* pool);
- Pool* LookupPool(const string& pool_name);
+ Pool* LookupPool(const std::string& pool_name);
Edge* AddEdge(const Rule* rule);
Node* GetNode(StringPiece path, uint64_t slash_bits);
Node* LookupNode(StringPiece path) const;
- Node* SpellcheckNode(const string& path);
+ Node* SpellcheckNode(const std::string& path);
void AddIn(Edge* edge, StringPiece path, uint64_t slash_bits);
bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits);
- bool AddDefault(StringPiece path, string* error);
+ bool AddDefault(StringPiece path, std::string* error);
/// Reset state. Keeps all nodes and edges, but restores them to the
/// state where we haven't yet examined the disk for dirty state.
@@ -111,21 +118,21 @@
/// @return the root node(s) of the graph. (Root nodes have no output edges).
/// @param error where to write the error message if somethings went wrong.
- vector<Node*> RootNodes(string* error) const;
- vector<Node*> DefaultNodes(string* error) const;
+ std::vector<Node*> RootNodes(std::string* error) const;
+ std::vector<Node*> DefaultNodes(std::string* error) const;
/// Mapping of path -> Node.
typedef ExternalStringHashMap<Node*>::Type Paths;
Paths paths_;
/// All the pools used in the graph.
- map<string, Pool*> pools_;
+ std::map<std::string, Pool*> pools_;
/// All the edges of the graph.
- vector<Edge*> edges_;
+ std::vector<Edge*> edges_;
BindingEnv bindings_;
- vector<Node*> defaults_;
+ std::vector<Node*> defaults_;
};
#endif // NINJA_STATE_H_
diff --git a/src/state_test.cc b/src/state_test.cc
index 458b519..96469f9 100644
--- a/src/state_test.cc
+++ b/src/state_test.cc
@@ -16,6 +16,8 @@
#include "state.h"
#include "test.h"
+using namespace std;
+
namespace {
TEST(State, Basic) {
diff --git a/src/string_piece.h b/src/string_piece.h
index 031bda4..1c0bee6 100644
--- a/src/string_piece.h
+++ b/src/string_piece.h
@@ -17,8 +17,6 @@
#include <string>
-using namespace std;
-
#include <string.h>
/// StringPiece represents a slice of a string whose memory is managed
@@ -30,7 +28,7 @@
StringPiece() : str_(NULL), len_(0) {}
/// The constructors intentionally allow for implicit conversions.
- StringPiece(const string& str) : str_(str.data()), len_(str.size()) {}
+ StringPiece(const std::string& str) : str_(str.data()), len_(str.size()) {}
StringPiece(const char* str) : str_(str), len_(strlen(str)) {}
StringPiece(const char* str, size_t len) : str_(str), len_(len) {}
@@ -38,14 +36,15 @@
bool operator==(const StringPiece& other) const {
return len_ == other.len_ && memcmp(str_, other.str_, len_) == 0;
}
+
bool operator!=(const StringPiece& other) const {
return !(*this == other);
}
/// Convert the slice into a full-fledged std::string, copying the
/// data into a new string.
- string AsString() const {
- return len_ ? string(str_, len_) : string();
+ std::string AsString() const {
+ return len_ ? std::string(str_, len_) : std::string();
}
const_iterator begin() const {
diff --git a/src/string_piece_util.cc b/src/string_piece_util.cc
index 8e1ecfd..69513f5 100644
--- a/src/string_piece_util.cc
+++ b/src/string_piece_util.cc
@@ -39,7 +39,7 @@
}
string JoinStringPiece(const vector<StringPiece>& list, char sep) {
- if (list.size() == 0){
+ if (list.empty()) {
return "";
}
diff --git a/src/string_piece_util.h b/src/string_piece_util.h
index 2e40b9f..28470f1 100644
--- a/src/string_piece_util.h
+++ b/src/string_piece_util.h
@@ -19,11 +19,10 @@
#include <vector>
#include "string_piece.h"
-using namespace std;
-vector<StringPiece> SplitStringPiece(StringPiece input, char sep);
+std::vector<StringPiece> SplitStringPiece(StringPiece input, char sep);
-string JoinStringPiece(const vector<StringPiece>& list, char sep);
+std::string JoinStringPiece(const std::vector<StringPiece>& list, char sep);
inline char ToLowerASCII(char c) {
return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
diff --git a/src/string_piece_util_test.cc b/src/string_piece_util_test.cc
index 648c647..61586dd 100644
--- a/src/string_piece_util_test.cc
+++ b/src/string_piece_util_test.cc
@@ -16,6 +16,8 @@
#include "test.h"
+using namespace std;
+
TEST(StringPieceUtilTest, SplitStringPiece) {
{
string input("a:b:c");
@@ -29,7 +31,7 @@
}
{
- string empty("");
+ string empty;
vector<StringPiece> list = SplitStringPiece(empty, ':');
EXPECT_EQ(list.size(), 1);
@@ -80,7 +82,7 @@
}
{
- string empty("");
+ string empty;
vector<StringPiece> list = SplitStringPiece(empty, ':');
EXPECT_EQ("", JoinStringPiece(list, ':'));
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index fc5543e..8e78540 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -18,17 +18,24 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
-#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <spawn.h>
+#if defined(USE_PPOLL)
+#include <poll.h>
+#else
+#include <sys/select.h>
+#endif
+
extern char** environ;
#include "util.h"
+using namespace std;
+
Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1),
use_console_(use_console) {
}
@@ -147,6 +154,16 @@
Fatal("waitpid(%d): %s", pid_, strerror(errno));
pid_ = -1;
+#ifdef _AIX
+ if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) {
+ // Map the shell's exit code used for signal failure (128 + signal) to the
+ // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike
+ // other systems, uses a different bit layout.
+ int signal = WEXITSTATUS(status) & 0x7f;
+ status = (signal << 16) | signal;
+ }
+#endif
+
if (WIFEXITED(status)) {
int exit = WEXITSTATUS(status);
if (exit == 0)
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index a4a7669..ff3baac 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -21,6 +21,8 @@
#include "util.h"
+using namespace std;
+
Subprocess::Subprocess(bool use_console) : child_(NULL) , overlapped_(),
is_reading_(false),
use_console_(use_console) {
@@ -124,12 +126,20 @@
buf_ = "CreateProcess failed: The system cannot find the file "
"specified.\n";
return true;
- } else if (error == ERROR_INVALID_PARAMETER) {
- // This generally means that the command line was too long. Give extra
- // context for this case.
- Win32Fatal("CreateProcess", "is the command line too long?");
} else {
- Win32Fatal("CreateProcess"); // pass all other errors to Win32Fatal
+ fprintf(stderr, "\nCreateProcess failed. Command attempted:\n\"%s\"\n",
+ command.c_str());
+ const char* hint = NULL;
+ // ERROR_INVALID_PARAMETER means the command line was formatted
+ // incorrectly. This can be caused by a command line being too long or
+ // leading whitespace in the command. Give extra context for this case.
+ if (error == ERROR_INVALID_PARAMETER) {
+ if (command.length() > 0 && (command[0] == ' ' || command[0] == '\t'))
+ hint = "command contains leading whitespace";
+ else
+ hint = "is the command line too long?";
+ }
+ Win32Fatal("CreateProcess", hint);
}
}
diff --git a/src/subprocess.h b/src/subprocess.h
index b2d486c..9e3d2ee 100644
--- a/src/subprocess.h
+++ b/src/subprocess.h
@@ -18,7 +18,6 @@
#include <string>
#include <vector>
#include <queue>
-using namespace std;
#ifdef _WIN32
#include <windows.h>
@@ -49,14 +48,14 @@
bool Done() const;
- const string& GetOutput() const;
+ const std::string& GetOutput() const;
private:
Subprocess(bool use_console);
- bool Start(struct SubprocessSet* set, const string& command);
+ bool Start(struct SubprocessSet* set, const std::string& command);
void OnPipeReady();
- string buf_;
+ std::string buf_;
#ifdef _WIN32
/// Set up pipe_ as the parent-side pipe of the subprocess; return the
@@ -84,13 +83,13 @@
SubprocessSet();
~SubprocessSet();
- Subprocess* Add(const string& command, bool use_console = false);
+ Subprocess* Add(const std::string& command, bool use_console = false);
bool DoWork();
Subprocess* NextFinished();
void Clear();
- vector<Subprocess*> running_;
- queue<Subprocess*> finished_;
+ std::vector<Subprocess*> running_;
+ std::queue<Subprocess*> finished_;
#ifdef _WIN32
static BOOL WINAPI NotifyInterrupted(DWORD dwCtrlType);
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 6e487db..073fe86 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -24,6 +24,8 @@
#include <unistd.h>
#endif
+using namespace std;
+
namespace {
#ifdef _WIN32
diff --git a/src/test.cc b/src/test.cc
index 8ba2297..11b1c9e 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -24,6 +24,7 @@
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
+#include <io.h>
#else
#include <unistd.h>
#endif
@@ -42,22 +43,14 @@
}
#endif
+using namespace std;
+
namespace {
#ifdef _WIN32
-#ifndef _mktemp_s
-/// mingw has no mktemp. Implement one with the same type as the one
-/// found in the Windows API.
-int _mktemp_s(char* templ) {
- char* ofs = strchr(templ, 'X');
- sprintf(ofs, "%d", rand() % 1000000);
- return 0;
-}
-#endif
-
/// Windows has no mkdtemp. Implement it in terms of _mktemp_s.
char* mkdtemp(char* name_template) {
- int err = _mktemp_s(name_template);
+ int err = _mktemp_s(name_template, strlen(name_template) + 1);
if (err < 0) {
perror("_mktemp_s");
return NULL;
diff --git a/src/test.h b/src/test.h
index 6af17b3..4552c34 100644
--- a/src/test.h
+++ b/src/test.h
@@ -118,7 +118,7 @@
void AddCatRule(State* state);
/// Short way to get a Node by its path from state_.
- Node* GetNode(const string& path);
+ Node* GetNode(const std::string& path);
State state_;
};
@@ -135,7 +135,7 @@
VirtualFileSystem() : now_(1) {}
/// "Create" a file with contents.
- void Create(const string& path, const string& contents);
+ void Create(const std::string& path, const std::string& contents);
/// Tick "time" forwards; subsequent file operations will be newer than
/// previous ones.
@@ -144,25 +144,26 @@
}
// DiskInterface
- virtual TimeStamp Stat(const string& path, string* err) const;
- virtual bool WriteFile(const string& path, const string& contents);
- virtual bool MakeDir(const string& path);
- virtual Status ReadFile(const string& path, string* contents, string* err);
- virtual int RemoveFile(const string& path);
+ virtual TimeStamp Stat(const std::string& path, std::string* err) const;
+ virtual bool WriteFile(const std::string& path, const std::string& contents);
+ virtual bool MakeDir(const std::string& path);
+ virtual Status ReadFile(const std::string& path, std::string* contents,
+ std::string* err);
+ virtual int RemoveFile(const std::string& path);
/// An entry for a single in-memory file.
struct Entry {
int mtime;
- string stat_error; // If mtime is -1.
- string contents;
+ std::string stat_error; // If mtime is -1.
+ std::string contents;
};
- vector<string> directories_made_;
- vector<string> files_read_;
- typedef map<string, Entry> FileMap;
+ std::vector<std::string> directories_made_;
+ std::vector<std::string> files_read_;
+ typedef std::map<std::string, Entry> FileMap;
FileMap files_;
- set<string> files_removed_;
- set<string> files_created_;
+ std::set<std::string> files_removed_;
+ std::set<std::string> files_created_;
/// A simple fake timestamp for file operations.
int now_;
@@ -170,15 +171,15 @@
struct ScopedTempDir {
/// Create a temporary directory and chdir into it.
- void CreateAndEnter(const string& name);
+ void CreateAndEnter(const std::string& name);
/// Clean up the temporary directory.
void Cleanup();
/// The temp directory containing our dir.
- string start_dir_;
+ std::string start_dir_;
/// The subdirectory name for our dir, or empty if it hasn't been set up.
- string temp_dir_name_;
+ std::string temp_dir_name_;
};
#endif // NINJA_TEST_H_
diff --git a/src/util.cc b/src/util.cc
index 6ea854b..ae2e3c2 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -51,9 +51,15 @@
#include <sys/sysinfo.h>
#endif
+#if defined(__FreeBSD__)
+#include <sys/cpuset.h>
+#endif
+
#include "edit_distance.h"
#include "metrics.h"
+using namespace std;
+
void Fatal(const char* msg, ...) {
va_list ap;
fprintf(stderr, "ninja: fatal: ");
@@ -512,10 +518,17 @@
#endif
return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
#else
-#ifdef CPU_COUNT
// The number of exposed processors might not represent the actual number of
// processors threads can run on. This happens when a CPU set limitation is
// active, see https://github.com/ninja-build/ninja/issues/1278
+#if defined(__FreeBSD__)
+ cpuset_t mask;
+ CPU_ZERO(&mask);
+ if (cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(mask),
+ &mask) == 0) {
+ return CPU_COUNT(&mask);
+ }
+#elif defined(CPU_COUNT)
cpu_set_t set;
if (sched_getaffinity(getpid(), sizeof(set), &set) == 0) {
return CPU_COUNT(&set);
diff --git a/src/util.h b/src/util.h
index 6a4a7a9..4e6ebb8 100644
--- a/src/util.h
+++ b/src/util.h
@@ -23,7 +23,6 @@
#include <string>
#include <vector>
-using namespace std;
#ifdef _MSC_VER
#define NORETURN __declspec(noreturn)
@@ -57,29 +56,30 @@
/// Canonicalize a path like "foo/../bar.h" into just "bar.h".
/// |slash_bits| has bits set starting from lowest for a backslash that was
/// normalized to a forward slash. (only used on Windows)
-bool CanonicalizePath(string* path, uint64_t* slash_bits, string* err);
+bool CanonicalizePath(std::string* path, uint64_t* slash_bits,
+ std::string* err);
bool CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits,
- string* err);
+ std::string* err);
/// Appends |input| to |*result|, escaping according to the whims of either
/// Bash, or Win32's CommandLineToArgvW().
/// Appends the string directly to |result| without modification if we can
/// determine that it contains no problematic characters.
-void GetShellEscapedString(const string& input, string* result);
-void GetWin32EscapedString(const string& input, string* result);
+void GetShellEscapedString(const std::string& input, std::string* result);
+void GetWin32EscapedString(const std::string& input, std::string* result);
/// Read a file to a string (in text mode: with CRLF conversion
/// on Windows).
/// Returns -errno and fills in \a err on error.
-int ReadFile(const string& path, string* contents, string* err);
+int ReadFile(const std::string& path, std::string* contents, std::string* err);
/// Mark a file descriptor to not be inherited on exec()s.
void SetCloseOnExec(int fd);
/// Given a misspelled string and a list of correct spellings, returns
/// the closest match or NULL if there is no close enough match.
-const char* SpellcheckStringV(const string& text,
- const vector<const char*>& words);
+const char* SpellcheckStringV(const std::string& text,
+ const std::vector<const char*>& words);
/// Like SpellcheckStringV, but takes a NULL-terminated list.
const char* SpellcheckString(const char* text, ...);
@@ -87,7 +87,7 @@
bool islatinalpha(int c);
/// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm).
-string StripAnsiEscapeCodes(const string& in);
+std::string StripAnsiEscapeCodes(const std::string& in);
/// @return the number of processors on the machine. Useful for an initial
/// guess for how many jobs to run in parallel. @return 0 on error.
@@ -99,10 +99,10 @@
/// Elide the given string @a str with '...' in the middle if the length
/// exceeds @a width.
-string ElideMiddle(const string& str, size_t width);
+std::string ElideMiddle(const std::string& str, size_t width);
/// Truncates a file to the given size.
-bool Truncate(const string& path, size_t size, string* err);
+bool Truncate(const std::string& path, size_t size, std::string* err);
#ifdef _MSC_VER
#define snprintf _snprintf
@@ -116,7 +116,7 @@
#ifdef _WIN32
/// Convert the value returned by GetLastError() into a string.
-string GetLastErrorString();
+std::string GetLastErrorString();
/// Calls Fatal() with a function name and GetLastErrorString.
NORETURN void Win32Fatal(const char* function, const char* hint = NULL);
diff --git a/src/util_test.cc b/src/util_test.cc
index b43788d..1621c91 100644
--- a/src/util_test.cc
+++ b/src/util_test.cc
@@ -16,6 +16,8 @@
#include "test.h"
+using namespace std;
+
namespace {
bool CanonicalizePath(string* path, string* err) {
diff --git a/src/version.cc b/src/version.cc
index 1c906ae..97afa7e 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -18,7 +18,9 @@
#include "util.h"
-const char* kNinjaVersion = "1.9.0.git";
+using namespace std;
+
+const char* kNinjaVersion = "1.10.2.git";
void ParseVersion(const string& version, int* major, int* minor) {
size_t end = version.find('.');
diff --git a/src/version.h b/src/version.h
index bd6b9ff..9d84ecb 100644
--- a/src/version.h
+++ b/src/version.h
@@ -16,17 +16,16 @@
#define NINJA_VERSION_H_
#include <string>
-using namespace std;
/// The version number of the current Ninja release. This will always
/// be "git" on trunk.
extern const char* kNinjaVersion;
/// Parse the major/minor components of a version string.
-void ParseVersion(const string& version, int* major, int* minor);
+void ParseVersion(const std::string& version, int* major, int* minor);
/// Check whether \a version is compatible with the current Ninja version,
/// aborting if not.
-void CheckNinjaVersion(const string& required_version);
+void CheckNinjaVersion(const std::string& required_version);
#endif // NINJA_VERSION_H_