Close fuchsia-rfc-0153 branch.
Since all development has been moved to the `main`
branch, remove all files from the current branch,
and add a new README.md one explaining the situation
and listing the corresponding `main` commit that
replicates the last known state of this one.
Fixed: 301120237
Change-Id: Ifd3f4ffe22d5d4c547bbcace517e088df6c76f2b
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/github.com/ninja-build/ninja/+/987832
Reviewed-by: Tyler Mandry <tmandry@google.com>
Reviewed-by: David Turner <digit@google.com>
diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index b8e9225..0000000
--- a/.clang-format
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2014 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.
-
-# This isn't meant to be authoritative, but it's good enough to be useful.
-# Still use your best judgement for formatting decisions: clang-format
-# sometimes makes strange choices.
-
-BasedOnStyle: Google
-AllowShortFunctionsOnASingleLine: Inline
-AllowShortIfStatementsOnASingleLine: false
-AllowShortLoopsOnASingleLine: false
-ConstructorInitializerAllOnOneLineOrOnePerLine: false
-Cpp11BracedListStyle: false
-IndentCaseLabels: false
-DerivePointerBinding: false
diff --git a/.clang-tidy b/.clang-tidy
deleted file mode 100644
index e0afd47..0000000
--- a/.clang-tidy
+++ /dev/null
@@ -1,17 +0,0 @@
----
-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
deleted file mode 100644
index 0cc68d6..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-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
deleted file mode 100644
index 57a569e..0000000
--- a/.github/workflows/linux.yml
+++ /dev/null
@@ -1,209 +0,0 @@
-name: Linux
-
-on:
- pull_request:
- push:
- release:
- types: published
-
-jobs:
- build:
- runs-on: [ubuntu-latest]
- container:
- image: centos:7
- steps:
- - uses: actions/checkout@v2
- - uses: codespell-project/actions-codespell@master
- with:
- ignore_words_list: fo,wee
- - 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-20.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-20.el7.x86_64.rpm
- rpm -U --quiet p7zip-16.02-20.el7.x86_64.rpm
- rpm -U --quiet p7zip-plugins-16.02-20.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
-
- build-aarch64:
- name: Build Linux ARM64
- runs-on: [ubuntu-latest]
- steps:
- - uses: actions/checkout@v3
-
- - name: Build
- uses: uraimo/run-on-arch-action@v2
- with:
- arch: aarch64
- distro: ubuntu18.04
- githubToken: ${{ github.token }}
- dockerRunArgs: |
- --volume "${PWD}:/ninja"
- install: |
- apt-get update -q -y
- apt-get install -q -y make gcc g++ libasan5 clang-tools curl p7zip-full file
- run: |
- set -x
- cd /ninja
-
- # INSTALL CMAKE
- CMAKE_VERSION=3.23.4
- curl -L -O https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-aarch64.sh
- chmod +x cmake-${CMAKE_VERSION}-Linux-aarch64.sh
- ./cmake-${CMAKE_VERSION}-Linux-aarch64.sh --skip-license --prefix=/usr/local
-
- # BUILD
- cmake -DCMAKE_BUILD_TYPE=Release -B release-build
- cmake --build release-build --parallel --config Release
- strip release-build/ninja
- file release-build/ninja
-
- # TEST
- pushd release-build
- ./ninja_test
- popd
-
- # CREATE ARCHIVE
- mkdir artifact
- 7z a artifact/ninja-linux-aarch64.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-aarch64.zip
- asset_name: ninja-linux-aarch64.zip
- asset_content_type: application/zip
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
deleted file mode 100644
index 96b7cf1..0000000
--- a/.github/workflows/macos.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: macOS
-
-on:
- pull_request:
- push:
- release:
- types: published
-
-jobs:
- build:
- runs-on: macos-12
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Install dependencies
- run: brew install re2c p7zip cmake
-
- - name: Build ninja
- shell: bash
- env:
- MACOSX_DEPLOYMENT_TARGET: 10.15
- run: |
- 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
deleted file mode 100644
index b6ec2ac..0000000
--- a/.github/workflows/windows.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Windows
-
-on:
- pull_request:
- push:
- release:
- types: published
-
-jobs:
- build:
- runs-on: windows-latest
-
- strategy:
- fail-fast: false
- matrix:
- include:
- - arch: 'x64'
- suffix: ''
- - arch: 'arm64'
- suffix: 'arm64'
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Install dependencies
- run: choco install re2c
-
- - name: Build ninja
- shell: bash
- run: |
- cmake -Bbuild -A ${{ matrix.arch }}
- cmake --build build --parallel --config Debug
- cmake --build build --parallel --config Release
-
- - name: Test ninja (Debug)
- if: matrix.arch != 'arm64'
- run: .\ninja_test.exe
- working-directory: build/Debug
-
- - name: Test ninja (Release)
- if: matrix.arch != 'arm64'
- run: .\ninja_test.exe
- working-directory: build/Release
-
- - name: Create ninja archive
- shell: bash
- run: |
- mkdir artifact
- 7z a artifact/ninja-win${{ matrix.suffix }}.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${{ matrix.suffix }}.zip
- asset_name: ninja-win${{ matrix.suffix }}.zip
- asset_content_type: application/zip
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index ca36ec8..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,49 +0,0 @@
-*.pyc
-*.obj
-*.exe
-*.pdb
-*.ilk
-/build*/
-/build.ninja
-/ninja
-/ninja.bootstrap
-/build_log_perftest
-/canon_perftest
-/clparser_perftest
-/depfile_parser_perftest
-/hash_collision_bench
-/ninja_test
-/manifest_parser_perftest
-/graph.png
-/doc/manual.html
-/doc/doxygen
-*.patch
-.DS_Store
-
-# Eclipse project files
-.project
-.cproject
-
-# SublimeText project files
-*.sublime-project
-*.sublime-workspace
-
-# Ninja output
-.ninja_deps
-.ninja_log
-
-# Visual Studio Code project files
-/.vscode/
-/.ccls-cache/
-
-# Qt Creator project files
-/CMakeLists.txt.user
-
-# clangd
-/.clangd/
-/compile_commands.json
-/.cache/
-
-# Visual Studio files
-/.vs/
-/out/
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index b901d7c..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,313 +0,0 @@
-cmake_minimum_required(VERSION 3.15)
-
-include(CheckSymbolExists)
-include(CheckIPOSupported)
-
-option(NINJA_BUILD_BINARY "Build ninja binary" ON)
-option(NINJA_FORCE_PSELECT "Use pselect() even on platforms that provide ppoll()" OFF)
-
-project(ninja CXX)
-
-# --- 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()
- 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})
- # Note that these settings are separately specified in configure.py, and
- # these lists should be kept in sync.
- add_compile_options(/W4 /wd4100 /wd4267 /wd4706 /wd4702 /wd4244 /GR- /Zc:__cplusplus)
- add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
-else()
- include(CheckCXXCompilerFlag)
- check_cxx_compiler_flag(-Wno-deprecated flag_no_deprecated)
- if(flag_no_deprecated)
- add_compile_options(-Wno-deprecated)
- endif()
- check_cxx_compiler_flag(-fdiagnostics-color flag_color_diag)
- if(flag_color_diag)
- add_compile_options(-fdiagnostics-color)
- endif()
-
- if(NOT NINJA_FORCE_PSELECT)
- # Check whether ppoll() is usable on the target platform.
- # Set -DUSE_PPOLL=1 if this is the case.
- #
- # NOTE: Use check_cxx_symbol_exists() instead of check_symbol_exists()
- # because on Linux, <poll.h> only exposes the symbol when _GNU_SOURCE
- # is defined.
- #
- # Both g++ and clang++ define the symbol by default, because the C++
- # standard library headers require it, but *not* gcc and clang, which
- # are used by check_symbol_exists().
- include(CheckCXXSymbolExists)
- check_cxx_symbol_exists(ppoll poll.h HAVE_PPOLL)
- check_cxx_symbol_exists(kqueue sys/event.h HAVE_KQUEUE)
- if (HAVE_KQUEUE)
- add_compile_definitions(USE_KQUEUE=1)
- elseif (HAVE_PPOLL)
- add_compile_definitions(USE_PPOLL=1)
- endif()
- endif()
-endif()
-
-# --- optional re2c
-find_program(RE2C re2c)
-if(RE2C)
- # the depfile parser and ninja lexers are generated using re2c.
- function(re2c IN OUT)
- add_custom_command(DEPENDS ${IN} OUTPUT ${OUT}
- COMMAND ${RE2C} -b -i --no-generation-date --no-version -o ${OUT} ${IN}
- )
- endfunction()
- 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 ${PROJECT_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)
- if(NOT WIN32)
- message(WARNING "browse feature omitted due to inline script failure")
- endif()
- return()
- endif()
-
- # Now check availability of the unistd header
- check_symbol_exists(fork "unistd.h" HAVE_FORK)
- check_symbol_exists(pipe "unistd.h" HAVE_PIPE)
- set(browse_supported 0)
- if (HAVE_FORK AND HAVE_PIPE)
- set(browse_supported 1)
- endif ()
- set(${RESULT} "${browse_supported}" PARENT_SCOPE)
- if(NOT browse_supported)
- message(WARNING "browse feature omitted due to missing `fork` and `pipe` functions")
- endif()
-
-endfunction()
-
-set(NINJA_PYTHON "python" CACHE STRING "Python interpreter to use for the browse tool")
-
-check_platform_supports_browse_mode(platform_supports_ninja_browse)
-
-# Core source files all build into ninja library.
-add_library(libninja OBJECT
- src/async_loop.cc
- src/build_config.cc
- src/build_log.cc
- src/build.cc
- src/clean.cc
- src/clparser.cc
- src/dyndep.cc
- src/dyndep_parser.cc
- src/debug_flags.cc
- src/deps_log.cc
- src/disk_interface.cc
- src/edit_distance.cc
- src/eval_env.cc
- src/graph.cc
- src/graphviz.cc
- src/ipc_utils.cc
- src/json.cc
- src/line_printer.cc
- src/manifest_parser.cc
- src/metrics.cc
- src/missing_deps.cc
- src/parser.cc
- src/persistent_mode.cc
- src/persistent_service.cc
- src/process_utils.cc
- src/state.cc
- src/status.cc
- src/stdio_redirection.cc
- src/string_piece_util.cc
- src/util.cc
- src/version.cc
-)
-if(WIN32)
- target_sources(libninja PRIVATE
- src/subprocess-win32.cc
- src/includes_normalize-win32.cc
- src/interrupt_handling-win32.cc
- src/ipc_handle-win32.cc
- src/msvc_helper-win32.cc
- src/msvc_helper_main-win32.cc
- src/getopt.c
- src/minidump-win32.cc
- )
- # Build getopt.c, which can be compiled as either C or C++, as C++
- # so that build environments which lack a C compiler, but have a C++
- # compiler may build ninja.
- set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX)
-else()
- target_sources(libninja PRIVATE
- src/interrupt_handling-posix.cc
- src/ipc_handle-posix.cc
- src/subprocess-posix.cc
- )
- if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
- target_sources(libninja PRIVATE src/getopt.c)
- # Build getopt.c, which can be compiled as either C or C++, as C++
- # so that build environments which lack a C compiler, but have a C++
- # compiler may build ninja.
- set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX)
- endif()
-
- # Needed for perfstat_cpu_total
- if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
- target_link_libraries(libninja PUBLIC "-lperfstat")
- endif()
-endif()
-
-if(WIN32)
- target_sources(libninja PRIVATE
- src/stat_cache-win32.cc
- )
-else()
- target_sources(libninja PRIVATE
- src/stat_cache-posix.cc
- )
-endif()
-
-target_compile_features(libninja PUBLIC cxx_std_11)
-
-#Fixes GetActiveProcessorCount on MinGW
-if(MINGW)
-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")
- add_compile_definitions(__STDC_FORMAT_MACROS)
-endif()
-
-# Main executable is library plus main() function.
-if(NINJA_BUILD_BINARY)
- add_executable(ninja src/ninja.cc)
- target_link_libraries(ninja PRIVATE libninja libninja-re2c)
-
- if(WIN32)
- target_sources(ninja PRIVATE windows/ninja.manifest)
- endif()
-endif()
-
-# 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 ${PROJECT_BINARY_DIR}/build
- COMMAND src/inline.sh kBrowsePy
- < src/browse.py
- > ${PROJECT_BINARY_DIR}/build/browse_py.h
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
- VERBATIM
- )
-
- if(NINJA_BUILD_BINARY)
- target_compile_definitions(ninja PRIVATE NINJA_HAVE_BROWSE)
- target_sources(ninja PRIVATE src/browse.cc)
- endif()
- set_source_files_properties(src/browse.cc
- PROPERTIES
- OBJECT_DEPENDS "${PROJECT_BINARY_DIR}/build/browse_py.h"
- INCLUDE_DIRECTORIES "${PROJECT_BINARY_DIR}"
- COMPILE_DEFINITIONS NINJA_PYTHON="${NINJA_PYTHON}"
- )
-endif()
-
-include(CTest)
-if(BUILD_TESTING)
- # Tests all build into ninja_test executable.
- add_executable(ninja_test
- src/async_loop_test.cc
- 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/interrupt_handling_test.cc
- src/ipc_handle_test.cc
- src/ipc_utils_test.cc
- src/json_test.cc
- src/lexer_test.cc
- src/manifest_parser_test.cc
- src/missing_deps_test.cc
- src/ninja_test.cc
- src/persistent_mode_test.cc
- src/persistent_service_test.cc
- src/process_utils_test.cc
- src/stat_cache_test.cc
- src/state_test.cc
- src/stdio_redirection_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
- persistent_mode_test_helper
- persistent_service_test_helper
- )
- 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_options(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000")
- target_link_options(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000")
- endif()
-
- add_test(NAME NinjaTest COMMAND ninja_test)
-endif()
-
-if(NINJA_BUILD_BINARY)
- install(TARGETS ninja)
-endif()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 37f6ebc..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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:
-
-* 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.
-* 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/COPYING b/COPYING
deleted file mode 100644
index 131cb1d..0000000
--- a/COPYING
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2010
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/README.fuchsia b/README.fuchsia
deleted file mode 100644
index 6251197..0000000
--- a/README.fuchsia
+++ /dev/null
@@ -1,87 +0,0 @@
-Name: Ninja
-URL: https://ninja-build.org
-Gerrit Git: https://fuchsia.googlesource.com/third_party/github.com/ninja-build/ninja
-Upstream Git: https://github.com/ninja-build/ninja
-Fuchsia RFC: https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0153_ninja_customization?hl=en
-License: Apache 2.0
-License File: COPYING
-Fuchsia Bug: https://fxbug.dev/91545
-Description:
-
-Ninja is a small build system with a focus on speed. As explained in the
-Fuchsia RFC-0153 document, this is a temporary custom branch of the upstream
-Ninja sources to provide various improvements for the Fuchsia build. All
-changes should be expressed as a set of small commits on top of the upstream
-main branch, periodically rebased according to the strategy described in the
-RFC document. Due to this, local modifications are not listed explictly here
-to reduce unnecessary merge conflicts in this file.
-
-Note that:
-
-- Development happens in the `fuchsia-rfc-0153` branch of
- `https://fuchsia.googlesource.com/third_party/github.com/ninja-build/ninja`
-
-- All patches need to be uploaded to Gerrit for review approval.
-
-- Sync branches can only be created on Gerrit, and require filing
- a ticket, see associated bug for details.
-
-Use `git log origin/upstream/master..fuchsia-rfc-0153` to see the detail log
-of changes instead.
-
-To facilite sync branch creation and management, the commit message of
-patches should following these conventions:
-
-- Patches that were cherry-picked from upstream should have a line that
- starts with `Upstream-Cherry-Pick: <COMMIT>` added to their original
- commit message.
-
-- Patches that were cherry-picked from the upstream GitHub pull requests
- should have a line that starts with `Upstream-GitHub-PR: <URL>`
- pointing to the corresponding pull request.
-
-- Patches that should not be sent upstream should have a line that
- starts with `Fuchsia-Only: <REASON>`. These should only matter for
- Fuchsia specific documentation (such as this file), or development
- scripts.
-
-- Non-trivial changes that address a specific feature should be provided
- as a series of small patches whose commit messages all include a line
- that starts with `Fuchsia-Topic: <name>`, where `<name>` is a small
- identifier describing the common feature (e.g. `faster-manifest-parser`)
- described by the list in the next section of this README.fuchsia file.
-
- This will allow collecting the commits to send them as upstream pull
- requests (after removing these lines from the commit message), or to
- re-order them during sync-branch rebase operations.
-
-Fuchsia-specific scripts or data files should go under `misc/fuchsia/`.
-
-List of Fuchsia-Topic items:
-
-multi-line-status
- Improve the status display in smart terminals to include a list of
- the oldest pending commands that are still running. This feature is
- controlled by setting NINJA_STATUS_MAX_COMMANDS in the
- environment to a decimal value that is >= 1.
-
-advanced-ipc
- Provide support for advanced inter-process communication using
- named pipes (on Win32) or unix domain sockets (on Posix). This
- will be used to implement both the `persistent-mode` feature
- and the `jobserver-support` feature.
-
-persistent-mode
- Allow the Ninja process to run in daemon mode after parsing the
- Ninja build plan, until a .ninja file is changed or some fixed
- expiration time, in order to start future `ninja` invocations
- immediately (instead of spending several seconds to re-load the
- build plan every time).
- Requires: advanced-ipc
-
-jobserver-support
- Allow Ninja to act as both a GNU Make jobserver client
- (when invoking from other build systems), and a server
- (when invoking sub-commands).
- Requires: advanced-ipc
-
diff --git a/README.md b/README.md
index 1ca56c5..629c892 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,10 @@
-# Ninja
+Development of the Fuchsia-specific Ninja fork
+described by Fuchsia RFC-0153 has moved from the
+`fuchsia-rfc-0153` branch to the `main` one on
+this git repository. For more details, see
-Ninja is a small build system with a focus on speed.
-https://ninja-build.org/
+ https://fxbug.dev/301120237
-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 on
- [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
-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
-```
+The previous state of this branch is reflected
+in commit dba41a7f80dc426364ae9238746f37ff7d06febf
+belonging to the main branch.
diff --git a/RELEASING.md b/RELEASING.md
deleted file mode 100644
index 4e3a4bd..0000000
--- a/RELEASING.md
+++ /dev/null
@@ -1,41 +0,0 @@
-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 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
- ```
- git commit -am 'mark this 1.5.0.git'
- ```
-5. git checkout release; git merge master
-6. Fix version number in src/version.cc (it will likely conflict in the above)
-7. Fix version in doc/manual.asciidoc (exists only on release branch)
-8. commit, tag, push (don't forget to push --tags)
- ```
- git commit -am v1.5.0; git push origin release
- git tag v1.5.0; git push --tags
- # Push the 1.5.0.git change on master too:
- git checkout master; git push origin master
- ```
-9. Construct release notes from prior notes
-
- credits: `git shortlog -s --no-merges REV..`
-
-
-### Release on GitHub:
-1. Go to [Tags](https://github.com/ninja-build/ninja/tags)
-2. Open the newly created tag and select "Create release from tag"
-3. Create the release which will trigger a build which automatically attaches
- the binaries
-
-### Make announcement on mailing list:
-1. copy old mail
-
-### Update website:
-1. Make sure your ninja checkout is on the v1.5.0 tag
-2. Clone https://github.com/ninja-build/ninja-build.github.io
-3. In that repo, `./update-docs.sh`
-4. Update index.html with newest version and link to release notes
-5. `git commit -m 'run update-docs.sh, 1.5.0 release'`
-6. `git push origin master`
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index f0b92b8..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-version: 1.0.{build}
-image:
- - Visual Studio 2017
- - Ubuntu1804
-
-environment:
- CLICOLOR_FORCE: 1
- CHERE_INVOKING: 1 # Tell Bash to inherit the current working directory
- matrix:
- - MSYSTEM: MINGW64
- - MSYSTEM: MSVC
- - MSYSTEM: LINUX
-
-matrix:
- exclude:
- - image: Visual Studio 2017
- MSYSTEM: LINUX
- - image: Ubuntu1804
- MSYSTEM: MINGW64
- - image: Ubuntu1804
- MSYSTEM: MSVC
-
-for:
- -
- matrix:
- only:
- - MSYSTEM: MINGW64
- build_script:
- ps: "C:\\msys64\\usr\\bin\\bash -lc @\"\n
- pacman -S --quiet --noconfirm --needed re2c 2>&1\n
- ./configure.py --bootstrap --platform mingw 2>&1\n
- ./ninja all\n
- ./ninja_test 2>&1\n
- ./misc/ninja_syntax_test.py 2>&1\n\"@"
- -
- matrix:
- only:
- - MSYSTEM: MSVC
- build_script:
- - cmd: >-
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
-
- python configure.py --bootstrap
-
- ninja.bootstrap.exe all
-
- ninja_test
-
- python misc/ninja_syntax_test.py
-
- - matrix:
- only:
- - image: Ubuntu1804
- build_script:
- - ./configure.py --bootstrap
- - ./ninja all
- - ./ninja_test
- - misc/ninja_syntax_test.py
- - misc/output_test.py
-
-test: off
diff --git a/configure.py b/configure.py
deleted file mode 100755
index 4a3c67c..0000000
--- a/configure.py
+++ /dev/null
@@ -1,774 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2001 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.
-
-"""Script that generates the build.ninja for ninja itself.
-
-Projects that use ninja themselves should either write a similar script
-or use a meta-build system that supports Ninja output."""
-
-from optparse import OptionParser
-import os
-import pipes
-import string
-import subprocess
-import sys
-
-sourcedir = os.path.dirname(os.path.realpath(__file__))
-sys.path.insert(0, os.path.join(sourcedir, 'misc'))
-import ninja_syntax
-
-
-class Platform(object):
- """Represents a host/target platform and its specific build attributes."""
- def __init__(self, platform):
- self._platform = platform
- if self._platform is not None:
- return
- self._platform = sys.platform
- if self._platform.startswith('linux'):
- self._platform = 'linux'
- elif self._platform.startswith('freebsd'):
- self._platform = 'freebsd'
- elif self._platform.startswith('gnukfreebsd'):
- self._platform = 'freebsd'
- elif self._platform.startswith('openbsd'):
- self._platform = 'openbsd'
- elif self._platform.startswith('solaris') or self._platform == 'sunos5':
- self._platform = 'solaris'
- elif self._platform.startswith('mingw'):
- self._platform = 'mingw'
- elif self._platform.startswith('win'):
- self._platform = 'msvc'
- elif self._platform.startswith('bitrig'):
- self._platform = 'bitrig'
- elif self._platform.startswith('netbsd'):
- self._platform = 'netbsd'
- elif self._platform.startswith('aix'):
- self._platform = 'aix'
- elif self._platform.startswith('os400'):
- self._platform = 'os400'
- elif self._platform.startswith('dragonfly'):
- self._platform = 'dragonfly'
-
- @staticmethod
- def known_platforms():
- return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5',
- 'mingw', 'msvc', 'gnukfreebsd', 'bitrig', 'netbsd', 'aix',
- 'dragonfly']
-
- def platform(self):
- return self._platform
-
- def is_linux(self):
- return self._platform == 'linux'
-
- def is_mingw(self):
- return self._platform == 'mingw'
-
- def is_msvc(self):
- return self._platform == 'msvc'
-
- def msvc_needs_fs(self):
- popen = subprocess.Popen(['cl', '/nologo', '/help'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- out, err = popen.communicate()
- return b'/FS' in out
-
- def is_windows(self):
- return self.is_mingw() or self.is_msvc()
-
- def is_solaris(self):
- return self._platform == 'solaris'
-
- def is_aix(self):
- return self._platform == 'aix'
-
- def is_os400_pase(self):
- return self._platform == 'os400' or os.uname().sysname.startswith('OS400')
-
- def uses_usr_local(self):
- return self._platform in ('freebsd', 'openbsd', 'bitrig', 'dragonfly', 'netbsd')
-
- def supports_ppoll(self):
- return self._platform in ('freebsd', 'linux', 'openbsd', 'bitrig',
- 'dragonfly')
- def supports_kqueue(self):
- return self._platform in ('freebsd', 'openbsd', 'dragonfly', 'darwin')
-
- def supports_ninja_browse(self):
- return (not self.is_windows()
- and not self.is_solaris()
- and not self.is_aix())
-
- def can_rebuild_in_place(self):
- return not (self.is_windows() or self.is_aix())
-
-class Bootstrap:
- """API shim for ninja_syntax.Writer that instead runs the commands.
-
- Used to bootstrap Ninja from scratch. In --bootstrap mode this
- class is used to execute all the commands to build an executable.
- It also proxies all calls to an underlying ninja_syntax.Writer, to
- behave like non-bootstrap mode.
- """
- def __init__(self, writer, verbose=False):
- self.writer = writer
- self.verbose = verbose
- # Map of variable name => expanded variable value.
- self.vars = {}
- # Map of rule name => dict of rule attributes.
- self.rules = {
- 'phony': {}
- }
-
- def comment(self, text):
- return self.writer.comment(text)
-
- def newline(self):
- return self.writer.newline()
-
- def variable(self, key, val):
- # In bootstrap mode, we have no ninja process to catch /showIncludes
- # output.
- self.vars[key] = self._expand(val).replace('/showIncludes', '')
- return self.writer.variable(key, val)
-
- def rule(self, name, **kwargs):
- self.rules[name] = kwargs
- return self.writer.rule(name, **kwargs)
-
- def build(self, outputs, rule, inputs=None, **kwargs):
- ruleattr = self.rules[rule]
- cmd = ruleattr.get('command')
- if cmd is None: # A phony rule, for example.
- return
-
- # Implement just enough of Ninja variable expansion etc. to
- # make the bootstrap build work.
- local_vars = {
- 'in': self._expand_paths(inputs),
- 'out': self._expand_paths(outputs)
- }
- for key, val in kwargs.get('variables', []):
- local_vars[key] = ' '.join(ninja_syntax.as_list(val))
-
- self._run_command(self._expand(cmd, local_vars))
-
- return self.writer.build(outputs, rule, inputs, **kwargs)
-
- def default(self, paths):
- return self.writer.default(paths)
-
- def _expand_paths(self, paths):
- """Expand $vars in an array of paths, e.g. from a 'build' block."""
- paths = ninja_syntax.as_list(paths)
- return ' '.join(map(self._shell_escape, (map(self._expand, paths))))
-
- def _expand(self, str, local_vars={}):
- """Expand $vars in a string."""
- return ninja_syntax.expand(str, self.vars, local_vars)
-
- def _shell_escape(self, path):
- """Quote paths containing spaces."""
- return '"%s"' % path if ' ' in path else path
-
- def _run_command(self, cmdline):
- """Run a subcommand, quietly. Prints the full command on error."""
- try:
- if self.verbose:
- print(cmdline)
- subprocess.check_call(cmdline, shell=True)
- except subprocess.CalledProcessError:
- print('when running: ', cmdline)
- raise
-
-
-parser = OptionParser()
-profilers = ['gmon', 'pprof']
-parser.add_option('--bootstrap', action='store_true',
- help='bootstrap a ninja binary from nothing')
-parser.add_option('--verbose', action='store_true',
- help='enable verbose build')
-parser.add_option('--platform',
- help='target platform (' +
- '/'.join(Platform.known_platforms()) + ')',
- choices=Platform.known_platforms())
-parser.add_option('--host',
- help='host platform (' +
- '/'.join(Platform.known_platforms()) + ')',
- choices=Platform.known_platforms())
-parser.add_option('--debug', action='store_true',
- help='enable debugging extras',)
-parser.add_option('--profile', metavar='TYPE',
- choices=profilers,
- help='enable profiling (' + '/'.join(profilers) + ')',)
-parser.add_option('--with-gtest', metavar='PATH', help='ignored')
-parser.add_option('--with-python', metavar='EXE',
- help='use EXE as the Python interpreter',
- default=os.path.basename(sys.executable))
-parser.add_option('--force-pselect', action='store_true',
- help='ppoll() is used by default where available, '
- 'but some platforms may need to use pselect instead',)
-(options, args) = parser.parse_args()
-if args:
- print('ERROR: extra unparsed command-line arguments:', args)
- sys.exit(1)
-
-platform = Platform(options.platform)
-if options.host:
- host = Platform(options.host)
-else:
- host = platform
-
-BUILD_FILENAME = 'build.ninja'
-ninja_writer = ninja_syntax.Writer(open(BUILD_FILENAME, 'w'))
-n = ninja_writer
-
-if options.bootstrap:
- # Make the build directory.
- try:
- os.mkdir('build')
- except OSError:
- pass
- # Wrap ninja_writer with the Bootstrapper, which also executes the
- # commands.
- print('bootstrapping ninja...')
- n = Bootstrap(n, verbose=options.verbose)
-
-n.comment('This file is used to build ninja itself.')
-n.comment('It is generated by ' + os.path.basename(__file__) + '.')
-n.newline()
-
-n.variable('ninja_required_version', '1.3')
-n.newline()
-
-n.comment('The arguments passed to configure.py, for rerunning it.')
-configure_args = sys.argv[1:]
-if '--bootstrap' in configure_args:
- configure_args.remove('--bootstrap')
-n.variable('configure_args', ' '.join(configure_args))
-env_keys = set(['CXX', 'AR', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS'])
-configure_env = dict((k, os.environ[k]) for k in os.environ if k in env_keys)
-if configure_env:
- config_str = ' '.join([k + '=' + pipes.quote(configure_env[k])
- for k in configure_env])
- n.variable('configure_env', config_str + '$ ')
-n.newline()
-
-CXX = configure_env.get('CXX', 'c++')
-objext = '.o'
-if platform.is_msvc():
- CXX = 'cl'
- objext = '.obj'
-
-def src(filename):
- return os.path.join('$root', 'src', filename)
-def built(filename):
- return os.path.join('$builddir', filename)
-def doc(filename):
- return os.path.join('$root', 'doc', filename)
-def cc(name, **kwargs):
- return n.build(built(name + objext), 'cxx', src(name + '.c'), **kwargs)
-def cxx(name, **kwargs):
- return n.build(built(name + objext), 'cxx', src(name + '.cc'), **kwargs)
-def binary(name):
- if platform.is_windows():
- exe = name + '.exe'
- n.build(name, 'phony', exe)
- return exe
- return name
-
-root = sourcedir
-if root == os.getcwd():
- # In the common case where we're building directly in the source
- # tree, simplify all the paths to just be cwd-relative.
- root = '.'
-n.variable('root', root)
-n.variable('builddir', 'build')
-n.variable('cxx', CXX)
-if platform.is_msvc():
- n.variable('ar', 'link')
-else:
- n.variable('ar', configure_env.get('AR', 'ar'))
-
-def search_system_path(file_name):
- """Find a file in the system path."""
- for dir in os.environ['path'].split(';'):
- path = os.path.join(dir, file_name)
- if os.path.exists(path):
- return path
-
-# Note that build settings are separately specified in CMakeLists.txt and
-# these lists should be kept in sync.
-if platform.is_msvc():
- if not search_system_path('cl.exe'):
- raise Exception('cl.exe not found. Run again from the Developer Command Prompt for VS')
- cflags = ['/showIncludes',
- '/nologo', # Don't print startup banner.
- '/Zi', # Create pdb with debug info.
- '/W4', # Highest warning level.
- '/WX', # Warnings as errors.
- '/wd4530', '/wd4100', '/wd4706', '/wd4244',
- '/wd4512', '/wd4800', '/wd4702', '/wd4819',
- # Disable warnings about constant conditional expressions.
- '/wd4127',
- # Disable warnings about passing "this" during initialization.
- '/wd4355',
- # Disable warnings about ignored typedef in DbgHelp.h
- '/wd4091',
- '/GR-', # Disable RTTI.
- '/Zc:__cplusplus',
- # Disable size_t -> int truncation warning.
- # We never have strings or arrays larger than 2**31.
- '/wd4267',
- '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS',
- '/D_HAS_EXCEPTIONS=0',
- '/DNINJA_PYTHON="%s"' % options.with_python]
- if platform.msvc_needs_fs():
- cflags.append('/FS')
- ldflags = ['/DEBUG', '/libpath:$builddir']
- if not options.debug:
- cflags += ['/Ox', '/DNDEBUG', '/GL']
- ldflags += ['/LTCG', '/OPT:REF', '/OPT:ICF']
-else:
- cflags = ['-g', '-Wall', '-Wextra',
- '-Wno-deprecated',
- '-Wno-missing-field-initializers',
- '-Wno-unused-parameter',
- '-fno-rtti',
- '-fno-exceptions',
- '-std=c++11',
- '-fvisibility=hidden', '-pipe',
- '-DNINJA_PYTHON="%s"' % options.with_python]
- if options.debug:
- cflags += ['-D_GLIBCXX_DEBUG', '-D_GLIBCXX_DEBUG_PEDANTIC']
- cflags.remove('-fno-rtti') # Needed for above pedanticness.
- else:
- cflags += ['-O2', '-DNDEBUG']
- try:
- proc = subprocess.Popen(
- [CXX, '-fdiagnostics-color', '-c', '-x', 'c++', '/dev/null',
- '-o', '/dev/null'],
- stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT)
- if proc.wait() == 0:
- cflags += ['-fdiagnostics-color']
- except:
- pass
- if platform.is_mingw():
- 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')
- ldflags.append('-L/usr/local/lib')
- if platform.is_aix():
- # printf formats for int64_t, uint64_t; large file support
- cflags.append('-D__STDC_FORMAT_MACROS')
- cflags.append('-D_LARGE_FILES')
-
-
-libs = []
-
-if platform.is_mingw():
- cflags.remove('-fvisibility=hidden');
- ldflags.append('-static')
-elif platform.is_solaris():
- cflags.remove('-fvisibility=hidden')
-elif platform.is_aix():
- cflags.remove('-fvisibility=hidden')
-elif platform.is_msvc():
- pass
-else:
- if options.profile == 'gmon':
- cflags.append('-pg')
- ldflags.append('-pg')
- elif options.profile == 'pprof':
- cflags.append('-fno-omit-frame-pointer')
- libs.extend(['-Wl,--no-as-needed', '-lprofiler'])
-
-if platform.supports_kqueue() and not options.force_pselect:
- cflags.append('-DUSE_KQUEUE')
-elif platform.supports_ppoll() and not options.force_pselect:
- cflags.append('-DUSE_PPOLL')
-if platform.supports_ninja_browse():
- cflags.append('-DNINJA_HAVE_BROWSE')
-
-# Search for generated headers relative to build dir.
-cflags.append('-I.')
-
-def shell_escape(str):
- """Escape str such that it's interpreted as a single argument by
- the shell."""
-
- # This isn't complete, but it's just enough to make NINJA_PYTHON work.
- if platform.is_windows():
- return str
- if '"' in str:
- return "'%s'" % str.replace("'", "\\'")
- return str
-
-if 'CFLAGS' in configure_env:
- cflags.append(configure_env['CFLAGS'])
- ldflags.append(configure_env['CFLAGS'])
-if 'CXXFLAGS' in configure_env:
- cflags.append(configure_env['CXXFLAGS'])
- ldflags.append(configure_env['CXXFLAGS'])
-n.variable('cflags', ' '.join(shell_escape(flag) for flag in cflags))
-if 'LDFLAGS' in configure_env:
- ldflags.append(configure_env['LDFLAGS'])
-n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags))
-n.newline()
-
-if platform.is_msvc():
- n.rule('cxx',
- command='$cxx $cflags -c $in /Fo$out /Fd' + built('$pdb'),
- description='CXX $out',
- deps='msvc' # /showIncludes is included in $cflags.
- )
-else:
- n.rule('cxx',
- command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out',
- depfile='$out.d',
- deps='gcc',
- description='CXX $out')
-n.newline()
-
-if host.is_msvc():
- n.rule('ar',
- command='lib /nologo /ltcg /out:$out $in',
- description='LIB $out')
-elif host.is_mingw():
- n.rule('ar',
- command='$ar crs $out $in',
- description='AR $out')
-else:
- n.rule('ar',
- command='rm -f $out && $ar crs $out $in',
- description='AR $out')
-n.newline()
-
-if platform.is_msvc():
- n.rule('link',
- command='$cxx $in $libs /nologo /link $ldflags /out:$out',
- description='LINK $out')
-else:
- n.rule('link',
- command='$cxx $ldflags -o $out $in $libs',
- description='LINK $out')
-n.newline()
-
-objs = []
-
-if platform.supports_ninja_browse():
- n.comment('browse_py.h is used to inline browse.py.')
- n.rule('inline',
- command='"%s"' % src('inline.sh') + ' $varname < $in > $out',
- description='INLINE $out')
- n.build(built('browse_py.h'), 'inline', src('browse.py'),
- implicit=src('inline.sh'),
- variables=[('varname', 'kBrowsePy')])
- n.newline()
-
- objs += cxx('browse', order_only=built('browse_py.h'))
- n.newline()
-
-n.comment('the depfile parser and ninja lexers are generated using re2c.')
-def has_re2c():
- try:
- proc = subprocess.Popen(['re2c', '-V'], stdout=subprocess.PIPE)
- return int(proc.communicate()[0], 10) >= 1503
- except OSError:
- return False
-if has_re2c():
- n.rule('re2c',
- command='re2c -b -i --no-generation-date --no-version -o $out $in',
- description='RE2C $out')
- # Generate the .cc files in the source directory so we can check them in.
- n.build(src('depfile_parser.cc'), 're2c', src('depfile_parser.in.cc'))
- n.build(src('lexer.cc'), 're2c', src('lexer.in.cc'))
-else:
- print("warning: A compatible version of re2c (>= 0.15.3) was not found; "
- "changes to src/*.in.cc will not affect your build.")
-n.newline()
-
-cxxvariables = []
-if platform.is_msvc():
- cxxvariables = [('pdb', 'ninja.pdb')]
-
-n.comment('Generate a library for `ninja-re2c`.')
-re2c_objs = []
-for name in ['depfile_parser', 'lexer']:
- re2c_objs += cxx(name, variables=cxxvariables)
-if platform.is_msvc():
- n.build(built('ninja-re2c.lib'), 'ar', re2c_objs)
-else:
- n.build(built('libninja-re2c.a'), 'ar', re2c_objs)
-n.newline()
-
-n.comment('Core source files all build into ninja library.')
-objs.extend(re2c_objs)
-for name in ['async_loop',
- 'build',
- 'build_config',
- 'build_log',
- 'clean',
- 'clparser',
- 'debug_flags',
- 'deps_log',
- 'disk_interface',
- 'dyndep',
- 'dyndep_parser',
- 'edit_distance',
- 'eval_env',
- 'graph',
- 'graphviz',
- 'ipc_utils',
- 'json',
- 'line_printer',
- 'manifest_parser',
- 'metrics',
- 'missing_deps',
- 'parser',
- 'persistent_mode',
- 'persistent_service',
- 'process_utils',
- 'state',
- 'status',
- 'stdio_redirection',
- 'string_piece_util',
- 'util',
- 'version']:
- objs += cxx(name, variables=cxxvariables)
-if platform.is_windows():
- for name in ['includes_normalize-win32',
- 'interrupt_handling-win32',
- 'ipc_handle-win32',
- 'msvc_helper-win32',
- 'msvc_helper_main-win32',
- 'subprocess-win32']:
- objs += cxx(name, variables=cxxvariables)
- if platform.is_msvc():
- objs += cxx('minidump-win32', variables=cxxvariables)
- objs += cc('getopt')
-else:
- for name in ['interrupt_handling-posix',
- 'ipc_handle-posix',
- 'subprocess-posix']:
- objs += cxx(name, variables=cxxvariables)
-
-if platform.is_windows():
- objs += cxx('stat_cache-win32', variables=cxxvariables)
-else:
- objs += cxx('stat_cache-posix', variables=cxxvariables)
-
-if platform.is_aix():
- objs += cc('getopt')
-if platform.is_msvc():
- ninja_lib = n.build(built('ninja.lib'), 'ar', objs)
-else:
- ninja_lib = n.build(built('libninja.a'), 'ar', objs)
-n.newline()
-
-if platform.is_msvc():
- libs.append('ninja.lib')
-else:
- libs.append('-lninja')
-
-if platform.is_aix() and not platform.is_os400_pase():
- libs.append('-lperfstat')
-
-all_targets = []
-
-n.comment('Main executable is library plus main() function.')
-objs = cxx('ninja', variables=cxxvariables)
-ninja = n.build(binary('ninja'), 'link', objs, implicit=ninja_lib,
- variables=[('libs', libs)])
-n.newline()
-all_targets += ninja
-
-if options.bootstrap:
- # We've built the ninja binary. Don't run any more commands
- # through the bootstrap executor, but continue writing the
- # build.ninja file.
- n = ninja_writer
-
-n.comment('Tests all build into ninja_test executable.')
-
-objs = []
-if platform.is_msvc():
- cxxvariables = [('pdb', 'ninja_test.pdb')]
-
-for name in ['async_loop_test',
- 'build_log_test',
- 'build_test',
- 'clean_test',
- 'clparser_test',
- 'depfile_parser_test',
- 'deps_log_test',
- 'dyndep_parser_test',
- 'disk_interface_test',
- 'edit_distance_test',
- 'graph_test',
- 'interrupt_handling_test',
- 'ipc_handle_test',
- 'ipc_utils_test',
- 'json_test',
- 'lexer_test',
- 'manifest_parser_test',
- 'missing_deps_test',
- 'ninja_test',
- 'persistent_mode_test',
- 'persistent_service_test',
- 'process_utils_test',
- 'stat_cache_test',
- 'state_test',
- 'status_test',
- 'stdio_redirection_test',
- 'string_piece_util_test',
- 'subprocess_test',
- 'test',
- 'util_test']:
- objs += cxx(name, variables=cxxvariables)
-if platform.is_windows():
- for name in ['includes_normalize_test', 'msvc_helper_test']:
- objs += cxx(name, variables=cxxvariables)
-
-ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib,
- variables=[('libs', libs)])
-n.newline()
-all_targets += ninja_test
-
-
-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',
- 'hash_collision_bench',
- 'manifest_parser_perftest',
- 'persistent_mode_test_helper',
- 'persistent_service_test_helper',
- 'clparser_perftest']:
- if platform.is_msvc():
- cxxvariables = [('pdb', name + '.pdb')]
- objs = cxx(name, variables=cxxvariables)
- all_targets += n.build(binary(name), 'link', objs,
- implicit=ninja_lib, variables=[('libs', libs)])
-
-n.newline()
-
-n.comment('Generate a graph using the "graph" tool.')
-n.rule('gendot',
- command='./ninja -t graph all > $out')
-n.rule('gengraph',
- command='dot -Tpng $in > $out')
-dot = n.build(built('graph.dot'), 'gendot', ['ninja', 'build.ninja'])
-n.build('graph.png', 'gengraph', dot)
-n.newline()
-
-n.comment('Generate the manual using asciidoc.')
-n.rule('asciidoc',
- command='asciidoc -b docbook -d book -o $out $in',
- description='ASCIIDOC $out')
-n.rule('xsltproc',
- command='xsltproc --nonet doc/docbook.xsl $in > $out',
- description='XSLTPROC $out')
-docbookxml = n.build(built('manual.xml'), 'asciidoc', doc('manual.asciidoc'))
-manual = n.build(doc('manual.html'), 'xsltproc', docbookxml,
- implicit=[doc('style.css'), doc('docbook.xsl')])
-n.build('manual', 'phony',
- order_only=manual)
-n.newline()
-
-n.rule('dblatex',
- command='dblatex -q -o $out -p doc/dblatex.xsl $in',
- description='DBLATEX $out')
-n.build(doc('manual.pdf'), 'dblatex', docbookxml,
- implicit=[doc('dblatex.xsl')])
-
-n.comment('Generate Doxygen.')
-n.rule('doxygen',
- command='doxygen $in',
- description='DOXYGEN $in')
-n.variable('doxygen_mainpage_generator',
- src('gen_doxygen_mainpage.sh'))
-n.rule('doxygen_mainpage',
- command='$doxygen_mainpage_generator $in > $out',
- description='DOXYGEN_MAINPAGE $out')
-mainpage = n.build(built('doxygen_mainpage'), 'doxygen_mainpage',
- ['README.md', 'COPYING'],
- implicit=['$doxygen_mainpage_generator'])
-n.build('doxygen', 'doxygen', doc('doxygen.config'),
- implicit=mainpage)
-n.newline()
-
-if not host.is_mingw():
- n.comment('Regenerate build files if build script changes.')
- n.rule('configure',
- command='${configure_env}%s $root/configure.py $configure_args' %
- options.with_python,
- generator=True)
- n.build('build.ninja', 'configure',
- implicit=['$root/configure.py',
- os.path.normpath('$root/misc/ninja_syntax.py')])
- n.newline()
-
-n.default(ninja)
-n.newline()
-
-if host.is_linux():
- n.comment('Packaging')
- n.rule('rpmbuild',
- command="misc/packaging/rpmbuild.sh",
- description='Building rpms..')
- n.build('rpm', 'rpmbuild')
- n.newline()
-
-n.build('all', 'phony', all_targets)
-
-n.close()
-print('wrote %s.' % BUILD_FILENAME)
-
-if options.bootstrap:
- print('bootstrap complete. rebuilding...')
-
- rebuild_args = []
-
- if platform.can_rebuild_in_place():
- rebuild_args.append('./ninja')
- else:
- if platform.is_windows():
- bootstrap_exe = 'ninja.bootstrap.exe'
- final_exe = 'ninja.exe'
- else:
- bootstrap_exe = './ninja.bootstrap'
- final_exe = './ninja'
-
- if os.path.exists(bootstrap_exe):
- os.unlink(bootstrap_exe)
- os.rename(final_exe, bootstrap_exe)
-
- rebuild_args.append(bootstrap_exe)
-
- if options.verbose:
- rebuild_args.append('-v')
-
- subprocess.check_call(rebuild_args)
diff --git a/doc/README.md b/doc/README.md
deleted file mode 100644
index 6afe5d4..0000000
--- a/doc/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-This directory contains the Ninja manual and support files used in
-building it. Here's a brief overview of how it works.
-
-The source text, `manual.asciidoc`, is written in the AsciiDoc format.
-AsciiDoc can generate HTML but it doesn't look great; instead, we use
-AsciiDoc to generate the Docbook XML format and then provide our own
-Docbook XSL tweaks to produce HTML from that.
-
-In theory using AsciiDoc and DocBook allows us to produce nice PDF
-documentation etc. In reality it's not clear anyone wants that, but the
-build rules are in place to generate it if you install dblatex.
diff --git a/doc/dblatex.xsl b/doc/dblatex.xsl
deleted file mode 100644
index c0da212..0000000
--- a/doc/dblatex.xsl
+++ /dev/null
@@ -1,7 +0,0 @@
-<!-- This custom XSL tweaks the dblatex XML settings. -->
-<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
- <!-- These parameters disable the list of collaborators and revisions.
- Together remove a useless page from the front matter. -->
- <xsl:param name='doc.collab.show'>0</xsl:param>
- <xsl:param name='latex.output.revhistory'>0</xsl:param>
-</xsl:stylesheet>
diff --git a/doc/docbook.xsl b/doc/docbook.xsl
deleted file mode 100644
index 2235be2..0000000
--- a/doc/docbook.xsl
+++ /dev/null
@@ -1,34 +0,0 @@
-<!-- This custom XSL tweaks the DocBook XML -> HTML settings to produce
- an OK-looking manual. -->
-<!DOCTYPE xsl:stylesheet [
-<!ENTITY css SYSTEM "style.css">
-]>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- version='1.0'>
- <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
-
- <!-- Embed our stylesheet as the user-provided <head> content. -->
- <xsl:template name="user.head.content"><style>&css;</style></xsl:template>
-
- <!-- Remove the body.attributes block, which specifies a bunch of
- useless bgcolor etc. attrs on the <body> tag. -->
- <xsl:template name="body.attributes"></xsl:template>
-
- <!-- Specify that in "book" form (which we're using), we only want a
- single table of contents at the beginning of the document. -->
- <xsl:param name="generate.toc">book toc</xsl:param>
-
- <!-- Don't put the "Chapter 1." prefix on the "chapters". -->
- <xsl:param name="chapter.autolabel">0</xsl:param>
-
- <!-- Make builds reproducible by generating the same IDs from the same inputs -->
- <xsl:param name="generate.consistent.ids">1</xsl:param>
-
- <!-- Use <ul> for the table of contents. By default DocBook uses a
- <dl>, which makes no semantic sense. I imagine they just did
- it because it looks nice? -->
- <xsl:param name="toc.list.type">ul</xsl:param>
-
- <xsl:output method="html" encoding="utf-8" indent="no"
- doctype-public=""/>
-</xsl:stylesheet>
diff --git a/doc/doxygen.config b/doc/doxygen.config
deleted file mode 100644
index d933021..0000000
--- a/doc/doxygen.config
+++ /dev/null
@@ -1,1250 +0,0 @@
-# Doxyfile 1.4.5
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
-#
-# All text after a hash (#) is considered a comment and will be ignored
-# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
-
-PROJECT_NAME = "Ninja"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-# PROJECT_NUMBER = "0"
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = "doc/doxygen/"
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish,
-# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese,
-# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish,
-# Swedish, and Ukrainian.
-
-OUTPUT_LANGUAGE = English
-
-# This tag can be used to specify the encoding used in the generated output.
-# The encoding is not always determined by the language that is chosen,
-# but also whether or not the output is meant for Windows or non-Windows users.
-# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
-# forces the Windows encoding (this is the default for the Windows binary),
-# whereas setting the tag to NO uses a Unix-style encoding (the default for
-# all platforms other than Windows).
-
-# Obsolet option.
-#USE_WINDOWS_ENCODING = YES
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-
-INLINE_INHERITED_MEMB = YES
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES = YES
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
-
-STRIP_FROM_PATH = src
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
-
-STRIP_FROM_INC_PATH = src/
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
-# doesn't support long names like on DOS, Mac, or CD-ROM.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like the Qt-style comments (thus requiring an
-# explicit @brief command for a brief description.
-
-JAVADOC_AUTOBRIEF = YES
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the DETAILS_AT_TOP tag is set to YES then Doxygen
-# will output the detailed description near the top, like JavaDoc.
-# If set to NO, the detailed description appears after the member
-# documentation.
-
-# Has become obsolete.
-#DETAILS_AT_TOP = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE = 2
-
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
-
-ALIASES =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for Java.
-# For instance, namespaces will be presented as packages, qualified scopes
-# will look different, etc.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to
-# include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-
-# BUILTIN_STL_SUPPORT = NO
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
-
-SUBGROUPING = YES
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL = YES
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE = YES
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC = YES
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
-
-SORT_BRIEF_DOCS = YES
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
-
-SORT_BY_SCOPE_NAME = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
-
-SHOW_USED_FILES = YES
-
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is YES.
-
-SHOW_DIRECTORIES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from the
-# version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
-
-FILE_VERSION_FILTER =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
-
-WARN_FORMAT = "$file:$line: $text "
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT = src \
- build/doxygen_mainpage
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py
-
-FILE_PATTERNS = *.cc \
- *.h
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
-# from the input.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH = src
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS = *.cpp \
- *.cc \
- *.h \
- *.hh \
- INSTALL DEPENDENCIES CHANGELOG LICENSE LGPL
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
-
-EXAMPLE_RECURSIVE = YES
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH = src
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output. If FILTER_PATTERNS is specified, this tag will be
-# ignored.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
-
-FILTER_SOURCE_FILES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
-
-SOURCE_BROWSER = YES
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
-
-STRIP_CODE_COMMENTS = NO
-
-# If the REFERENCED_BY_RELATION tag is set to YES (the default)
-# then for each documented function all documented
-# functions referencing it will be listed.
-
-REFERENCED_BY_RELATION = YES
-
-# If the REFERENCES_RELATION tag is set to YES (the default)
-# then for each documented function all documented entities
-# called/used by that function will be listed.
-
-REFERENCES_RELATION = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX = YES
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX = 2
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header.
-HTML_HEADER =
-
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
-
-HTML_STYLESHEET =
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP = YES
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
-# written to the html output directory.
-
-CHM_FILE =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
-
-HHC_LOCATION =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
-
-GENERATE_CHI = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
-
-TOC_EXPAND = NO
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
-
-DISABLE_INDEX = NO
-
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
-# generated containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
-# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
-# probably better off using the HTML help feature.
-
-GENERATE_TREEVIEW = YES
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
-
-TREEVIEW_WIDTH = 250
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-GENERATE_LATEX = NO
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-
-LATEX_CMD_NAME =
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
-
-MAKEINDEX_CMD_NAME =
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS = YES
-
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE = YES
-
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
-
-LATEX_HIDE_INDICES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader. This is useful
-# if you want to understand what is going on. On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
-
-MACRO_EXPANSION = YES
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
-
-EXPAND_ONLY_PREDEF = YES
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE = doc/doxygen/html/Ninja.TAGFILE
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS = YES
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
-
-EXTERNAL_GROUPS = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
-# powerful graphs.
-
-CLASS_DIAGRAMS = YES
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT = YES
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH = NO
-
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-
-UML_LOOK = NO
-# UML_LOOK = YES
-
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
-
-TEMPLATE_RELATIONS = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
-
-INCLUDE_GRAPH = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
-# generate a call dependency graph for every global function or class method.
-# Note that enabling this option will significantly increase the time of a run.
-# So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command.
-
-CALL_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
-
-DOT_IMAGE_FORMAT = png
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
-
-DOTFILE_DIRS =
-
-# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
-# (in pixels) of the graphs generated by dot. If a graph becomes larger than
-# this value, doxygen will try to truncate the graph, so that it fits within
-# the specified constraint. Beware that most browsers cannot cope with very
-# large images.
-
-# Obsolet option.
-#MAX_DOT_GRAPH_WIDTH = 1280
-
-# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
-# (in pixels) of the graphs generated by dot. If a graph becomes larger than
-# this value, doxygen will try to truncate the graph, so that it fits within
-# the specified constraint. Beware that most browsers cannot cope with very
-# large images.
-
-# Obsolet option.
-#MAX_DOT_GRAPH_HEIGHT = 1024
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that a graph may be further truncated if the graph's
-# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH
-# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default),
-# the graph is not depth-constrained.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, which results in a white background.
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
-# JW
-# DOT_MULTI_TARGETS = NO
-DOT_MULTI_TARGETS = YES
-
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
-
-DOT_CLEANUP = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be
-# used. If set to NO the values of all tags below this one will be ignored.
-
-# JW SEARCHENGINE = NO
-SEARCHENGINE = YES
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
deleted file mode 100644
index 45db7e2..0000000
--- a/doc/manual.asciidoc
+++ /dev/null
@@ -1,1346 +0,0 @@
-The Ninja build system
-======================
-
-
-Introduction
-------------
-
-Ninja is yet another build system. It takes as input the
-interdependencies of files (typically source code and output
-executables) and orchestrates building them, _quickly_.
-
-Ninja joins a sea of other build systems. Its distinguishing goal is
-to be fast. It is born from
-http://neugierig.org/software/chromium/notes/2011/02/ninja.html[my
-work on the Chromium browser project], which has over 30,000 source
-files and whose other build systems (including one built from custom
-non-recursive Makefiles) would take ten seconds to start building
-after changing one file. Ninja is under a second.
-
-Philosophical overview
-~~~~~~~~~~~~~~~~~~~~~~
-
-Where other build systems are high-level languages, Ninja aims to be
-an assembler.
-
-Build systems get slow when they need to make decisions. When you are
-in an edit-compile cycle you want it to be as fast as possible -- you
-want the build system to do the minimum work necessary to figure out
-what needs to be built immediately.
-
-Ninja contains the barest functionality necessary to describe
-arbitrary dependency graphs. Its lack of syntax makes it impossible
-to express complex decisions.
-
-Instead, Ninja is intended to be used with a separate program
-generating its input files. The generator program (like the
-`./configure` found in autotools projects) can analyze system
-dependencies and make as many decisions as possible up front so that
-incremental builds stay fast. Going beyond autotools, even build-time
-decisions like "which compiler flags should I use?" or "should I
-build a debug or release-mode binary?" belong in the `.ninja` file
-generator.
-
-Design goals
-~~~~~~~~~~~~
-
-Here are the design goals of Ninja:
-
-* very fast (i.e., instant) incremental builds, even for very large
- projects.
-
-* very little policy about how code is built. Different projects and
- higher-level build systems have different opinions about how code
- should be built; for example, should built objects live alongside
- the sources or should all build output go into a separate directory?
- Is there a "package" rule that builds a distributable package of
- the project? Sidestep these decisions by trying to allow either to
- be implemented, rather than choosing, even if that results in
- more verbosity.
-
-* get dependencies correct, and in particular situations that are
- difficult to get right with Makefiles (e.g. outputs need an implicit
- dependency on the command line used to generate them; to build C
- source code you need to use gcc's `-M` flags for header
- dependencies).
-
-* when convenience and speed are in conflict, prefer speed.
-
-Some explicit _non-goals_:
-
-* convenient syntax for writing build files by hand. _You should
- generate your ninja files using another program_. This is how we
- can sidestep many policy decisions.
-
-* built-in rules. _Out of the box, Ninja has no rules for
- e.g. compiling C code._
-
-* build-time customization of the build. _Options belong in
- the program that generates the ninja files_.
-
-* build-time decision-making ability such as conditionals or search
- paths. _Making decisions is slow._
-
-To restate, Ninja is faster than other build systems because it is
-painfully simple. You must tell Ninja exactly what to do when you
-create your project's `.ninja` files.
-
-Comparison to Make
-~~~~~~~~~~~~~~~~~~
-
-Ninja is closest in spirit and functionality to Make, relying on
-simple dependencies between file timestamps.
-
-But fundamentally, make has a lot of _features_: suffix rules,
-functions, built-in rules that e.g. search for RCS files when building
-source. Make's language was designed to be written by humans. Many
-projects find make alone adequate for their build problems.
-
-In contrast, Ninja has almost no features; just those necessary to get
-builds correct while punting most complexity to generation of the
-ninja input files. Ninja by itself is unlikely to be useful for most
-projects.
-
-Here are some of the features Ninja adds to Make. (These sorts of
-features can often be implemented using more complicated Makefiles,
-but they are not part of make itself.)
-
-* Ninja has special support for discovering extra dependencies at build
- time, making it easy to get <<ref_headers,header dependencies>>
- correct for C/C++ code.
-
-* A build edge may have multiple outputs.
-
-* Outputs implicitly depend on the command line that was used to generate
- them, which means that changing e.g. compilation flags will cause
- the outputs to rebuild.
-
-* Output directories are always implicitly created before running the
- command that relies on them.
-
-* Rules can provide shorter descriptions of the command being run, so
- you can print e.g. `CC foo.o` instead of a long command line while
- building.
-
-* Builds are always run in parallel, based by default on the number of
- CPUs your system has. Underspecified build dependencies will result
- in incorrect builds.
-
-* Command output is always buffered. This means commands running in
- parallel don't interleave their output, and when a command fails we
- can print its failure output next to the full command line that
- produced the failure.
-
-
-Using Ninja for your project
-----------------------------
-
-Ninja currently works on Unix-like systems and Windows. It's seen the
-most testing on Linux (and has the best performance there) but it runs
-fine on Mac OS X and FreeBSD.
-
-If your project is small, Ninja's speed impact is likely unnoticeable.
-(However, even for small projects it sometimes turns out that Ninja's
-limited syntax forces simpler build rules that result in faster
-builds.) Another way to say this is that if you're happy with the
-edit-compile cycle time of your project already then Ninja won't help.
-
-There are many other build systems that are more user-friendly or
-featureful than Ninja itself. For some recommendations: the Ninja
-author found http://gittup.org/tup/[the tup build system] influential
-in Ninja's design, and thinks https://github.com/apenwarr/redo[redo]'s
-design is quite clever.
-
-Ninja's benefit comes from using it in conjunction with a smarter
-meta-build system.
-
-https://gn.googlesource.com/gn/[gn]:: The meta-build system used to
-generate build files for Google Chrome and related projects (v8,
-node.js), as well as Google Fuchsia. gn can generate Ninja files for
-all platforms supported by Chrome.
-
-https://cmake.org/[CMake]:: A widely used meta-build system that
-can generate Ninja files on Linux as of CMake version 2.8.8. Newer versions
-of CMake support generating Ninja files on Windows and Mac OS X too.
-
-https://github.com/ninja-build/ninja/wiki/List-of-generators-producing-ninja-build-files[others]:: Ninja ought to fit perfectly into other meta-build software
-like https://premake.github.io/[premake]. If you do this work,
-please let us know!
-
-Running Ninja
-~~~~~~~~~~~~~
-
-Run `ninja`. By default, it looks for a file named `build.ninja` in
-the current directory and builds all out-of-date targets. You can
-specify which targets (files) to build as command line arguments.
-
-There is also a special syntax `target^` for specifying a target
-as the first output of some rule containing the source you put in
-the command line, if one exists. For example, if you specify target as
-`foo.c^` then `foo.o` will get built (assuming you have those targets
-in your build files).
-
-`ninja -h` prints help output. Many of Ninja's flags intentionally
-match those of Make; e.g `ninja -C build -j 20` changes into the
-`build` directory and runs 20 build commands in parallel. (Note that
-Ninja defaults to running commands in parallel anyway, so typically
-you don't need to pass `-j`.)
-
-
-Environment variables
-~~~~~~~~~~~~~~~~~~~~~
-
-Ninja supports a few environment variables to control its behavior:
-
-`NINJA_STATUS`, the progress status printed before the rule being run.
-
-Several placeholders are available:
-
-`%s`:: The number of started edges.
-`%t`:: The total number of edges that must be run to complete the build.
-`%p`:: The percentage of started edges.
-`%r`:: The number of currently running edges.
-`%u`:: The number of remaining edges to start.
-`%f`:: The number of finished edges.
-`%o`:: Overall rate of finished edges per second
-`%c`:: Current rate of finished edges per second (average over builds
-specified by `-j` or its default)
-`%e`:: Elapsed time in seconds. _(Available since Ninja 1.2.)_
-`%%`:: A plain `%` character.
-
-The default progress status is `"[%f/%t] "` (note the trailing space
-to separate from the build rule). Another example of possible progress status
-could be `"[%u/%r/%f] "`.
-
-`NINJA_STATUS_MAX_COMMANDS` can be set to a decimal integer value.
-When it is greater than 0, then Ninja will display, below the status line,
-a list of commands that are still running, with their elapsed times, sorted
-from oldest to newest. The value determines the maximum number of pending
-commands to display.For example, with a value of 4, it can look like:
-
-
----------------------------------
-[2617/70332] STAMP obj/zircon/system/...c_sdk_verify_api.generated_file.stamp
- 1.5s | RUST obj/third_party/rust_cr...ibdata_encoding-182ebdb900ce1527.rlib
- 1.4s | RUST obj/third_party/rust_crates/liblibc-49c4578e69d66647.rlib
- 1.3s | RUST obj/third_party/rust_crates/libnum_traits-eace97eec9f18ef0.rlib
- 1.1s | RUST obj/third_party/rust_cr...libregex_syntax-49dcde6b7db9cfdf.rlib
----------------------------------
-
-The commands list is refreshed every 100ms by default, but this period can be
-changed using `NINJA_STATUS_REFRESH_MILLIS`, whose value must be a period
-in milliseconds that is >= 100 (there is no point in using lower values
-since all elapsed times are printed up to a single decimal digit, so anything
-lower will be ignored).
-
-Note that:
-
-- Pending commands are never displayed when using `-n` (i.e. dry-run)
- or `--verbose`, or on dumb terminals, so it should not affect scripts
- parsing Ninja output.
-
-- The commands list is cleared automatically before Ninja prints
- any command-specific output. This includes the output of edges with
- `pool = console`.
-
-- Finally, the status line itself prints the same thing, i.e. the last
- completed command only.
-
-`NINJA_PERSISTENT_MODE` can be set to `1` or `on` to use Ninja's persistent
-mode, where the first invocation for a given build directory will spawn a
-background server process that will load the build graph in memory, then
-answers multiple queries in succession. For very large build graphs, this
-allows Ninja to restart much faster.
-
-Note that this feature should be transparent to the user, except on Win32
-where it currently does not work(!).
-
-Each server process will shutdown gracefully after 5 minutes of idle time,
-or if any of the input `.ninja` file changed (to ensure correctness). The
-`server` tool can be used to interact with the server (see below).
-
-For debugging, it is possible to launch a server process in the foreground
-by setting `NINJA_PERSISTENT_MODE=server` in the environment. Server logs
-will then be printed directly to the current terminal stdout/stderr.
-
-By default, the server will shutdown gracefully after waiting for client
-connections for 5 minutes. This delay can be changed by setting the
-`NINJA_PERSISTENT_MODE_TIMEOUT_SECONDS` environment variable, which
-should receive a strictly positive timeout in seconds. For example,
-use 3600 for an hour.
-
-`NINJA_PERSISTENT_LOG_FILE` can be set to specify a file where the logs of
-new background server processes are written too, which can be useful for
-debugging.
-
-All environment variables from the client process are passed to the server
-on each incremental build, then used when spawning new commands. Note that
-this does not affect the server, so the `NINJA_PERSISTENT_XXX` variables
-only take effect when a server starts.
-
-Extra tools
-~~~~~~~~~~~
-
-The `-t` flag on the Ninja command line runs some tools that we have
-found useful during Ninja's development. The current tools are:
-
-[horizontal]
-`query`:: dump the inputs and outputs of a given target.
-
-`browse`:: browse the dependency graph in a web browser. Clicking a
-file focuses the view on that file, showing inputs and outputs. This
-feature requires a Python installation. By default, port 8000 is used
-and a web browser will be opened. This can be changed as follows:
-+
-----
-ninja -t browse --port=8000 --no-browser mytarget
-----
-+
-`graph`:: output a file in the syntax used by `graphviz`, an automatic
-graph layout tool. Use it like:
-+
-----
-ninja -t graph mytarget | dot -Tpng -ograph.png
-----
-+
-In the Ninja source tree, `ninja graph.png`
-generates an image for Ninja itself. If no target is given generate a
-graph for all root targets.
-
-`targets`:: output a list of targets either by rule or by depth. If used
-like +ninja -t targets rule _name_+ it prints the list of targets
-using the given rule to be built. If no rule is given, it prints the source
-files (the leaves of the graph). If used like
-+ninja -t targets depth _digit_+ it
-prints the list of targets in a depth-first manner starting by the root
-targets (the ones with no outputs). Indentation is used to mark dependencies.
-If the depth is zero it prints all targets. If no arguments are provided
-+ninja -t targets depth 1+ is assumed. In this mode targets may be listed
-several times. If used like this +ninja -t targets all+ it
-prints all the targets available without indentation and it is faster
-than the _depth_ mode.
-
-`commands`:: given a list of targets, print a list of commands which, if
-executed in order, may be used to rebuild those targets, assuming that all
-output files are out of date.
-
-`inputs`:: given a list of targets, print a list of all inputs used to
-rebuild those targets.
-_Available since Ninja 1.11._
-
-`clean`:: remove built files. By default, it removes all built files
-except for those created by the generator. Adding the `-g` flag also
-removes built files created by the generator (see <<ref_rule,the rule
-reference for the +generator+ attribute>>). Additional arguments are
-targets, which removes the given targets and recursively all files
-built for them.
-+
-If used like +ninja -t clean -r _rules_+ it removes all files built using
-the given rules.
-+
-Files created but not referenced in the graph are not removed. This
-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
-http://clang.llvm.org/docs/JSONCompilationDatabase.html[JSON format] expected
-by the Clang tooling interface.
-_Available since Ninja 1.2._
-
-`deps`:: show all dependencies stored in the `.ninja_deps` file. When given a
-target, show just the target's dependencies. _Available since Ninja 1.4._
-
-`missingdeps`:: given a list of targets, look for targets that depend on
-a generated file, but do not have a properly (possibly transitive) dependency
-on the generator. Such targets may cause build flakiness on clean builds.
-+
-The broken targets can be found assuming deps log / depfile dependency
-information is correct. Any target that depends on a generated file (output
-of a generator-target) implicitly, but does not have an explicit or order-only
-dependency path to the generator-target, is considered broken.
-+
-The tool's findings can be verified by trying to build the listed targets in
-a clean outdir without building any other targets. The build should fail for
-each of them with a missing include error or equivalent pointing to the
-generated file.
-_Available since Ninja 1.11._
-
-`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. It can be used to know which rule name
-to pass to +ninja -t targets rule _name_+ or +ninja -t compdb+. Adding the `-d`
-flag also prints the description of the rules.
-
-`msvc`:: Available on Windows hosts only.
-Helper tool to invoke the `cl.exe` compiler with a pre-defined set of
-environment variables, as in:
-+
-----
-ninja -t msvc -e ENVFILE -- cl.exe <arguments>
-----
-+
-Where `ENVFILE` is a binary file that contains an environment block suitable
-for CreateProcessA() on Windows (i.e. a series of zero-terminated strings that
-look like NAME=VALUE, followed by an extra zero terminator). Note that this uses
-the local codepage encoding.
-
-This tool also supports a deprecated way of parsing the compiler's output when
-the `/showIncludes` flag is used, and generating a GCC-compatible depfile from it.
-+
----
-ninja -t msvc -o DEPFILE [-p STRING] -- cl.exe /showIncludes <arguments>
----
-+
-
-When using this option, `-p STRING` can be used to pass the localized line prefix
-that `cl.exe` uses to output dependency information. For English-speaking regions
-this is `"Note: including file: "` without the double quotes, but will be different
-for other regions.
-
-Note that Ninja supports this natively now, with the use of `deps = msvc` and
-`msvc_deps_prefix` in Ninja files. Native support also avoids launching an extra
-tool process each time the compiler must be called, which can speed up builds
-noticeably on Windows.
-
-`wincodepage`:: Available on Windows hosts (_since Ninja 1.11_).
-Prints the Windows code page whose encoding is expected in the build file.
-The output has the form:
-+
-----
-Build file encoding: <codepage>
-----
-+
-Additional lines may be added in future versions of Ninja.
-+
-The `<codepage>` is one of:
-
-`UTF-8`::: Encode as UTF-8.
-
-`ANSI`::: Encode to the system-wide ANSI code page.
-
-
-`server`:: interact with a persistent server instance if there is one. Use
-+ninja -t server status+ to retrieve the status of the server, and use
-+ninja -t server stop+ to stop any existing server instance.
-
-Writing your own Ninja files
-----------------------------
-
-The remainder of this manual is only useful if you are constructing
-Ninja files yourself: for example, if you're writing a meta-build
-system or supporting a new language.
-
-Conceptual overview
-~~~~~~~~~~~~~~~~~~~
-
-Ninja evaluates a graph of dependencies between files, and runs
-whichever commands are necessary to make your build target up to date
-as determined by file modification times. If you are familiar with
-Make, Ninja is very similar.
-
-A build file (default name: `build.ninja`) provides a list of _rules_
--- short names for longer commands, like how to run the compiler --
-along with a list of _build_ statements saying how to build files
-using the rules -- which rule to apply to which inputs to produce
-which outputs.
-
-Conceptually, `build` statements describe the dependency graph of your
-project, while `rule` statements describe how to generate the files
-along a given edge of the graph.
-
-Syntax example
-~~~~~~~~~~~~~~
-
-Here's a basic `.ninja` file that demonstrates most of the syntax.
-It will be used as an example for the following sections.
-
----------------------------------
-cflags = -Wall
-
-rule cc
- command = gcc $cflags -c $in -o $out
-
-build foo.o: cc foo.c
----------------------------------
-
-Variables
-~~~~~~~~~
-Despite the non-goal of being convenient to write by hand, to keep
-build files readable (debuggable), Ninja supports declaring shorter
-reusable names for strings. A declaration like the following
-
-----------------
-cflags = -g
-----------------
-
-can be used on the right side of an equals sign, dereferencing it with
-a dollar sign, like this:
-
-----------------
-rule cc
- command = gcc $cflags -c $in -o $out
-----------------
-
-Variables can also be referenced using curly braces like `${in}`.
-
-Variables might better be called "bindings", in that a given variable
-cannot be changed, only shadowed. There is more on how shadowing works
-later in this document.
-
-Rules
-~~~~~
-
-Rules declare a short name for a command line. They begin with a line
-consisting of the `rule` keyword and a name for the rule. Then
-follows an indented set of `variable = value` lines.
-
-The basic example above declares a new rule named `cc`, along with the
-command to run. In the context of a rule, the `command` variable
-defines the command to run, `$in` expands to the list of
-input files (`foo.c`), and `$out` to the output files (`foo.o`) for the
-command. A full list of special variables is provided in
-<<ref_rule,the reference>>.
-
-Build statements
-~~~~~~~~~~~~~~~~
-
-Build statements declare a relationship between input and output
-files. They begin with the `build` keyword, and have the format
-+build _outputs_: _rulename_ _inputs_+. Such a declaration says that
-all of the output files are derived from the input files. When the
-output files are missing or when the inputs change, Ninja will run the
-rule to regenerate the outputs.
-
-The basic example above describes how to build `foo.o`, using the `cc`
-rule.
-
-In the scope of a `build` block (including in the evaluation of its
-associated `rule`), the variable `$in` is the list of inputs and the
-variable `$out` is the list of outputs.
-
-A build statement may be followed by an indented set of `key = value`
-pairs, much like a rule. These variables will shadow any variables
-when evaluating the variables in the command. For example:
-
-----------------
-cflags = -Wall -Werror
-rule cc
- command = gcc $cflags -c $in -o $out
-
-# If left unspecified, builds get the outer $cflags.
-build foo.o: cc foo.c
-
-# But you can shadow variables like cflags for a particular build.
-build special.o: cc special.c
- cflags = -Wall
-
-# The variable was only shadowed for the scope of special.o;
-# Subsequent build lines get the outer (original) cflags.
-build bar.o: cc bar.c
-
-----------------
-
-For more discussion of how scoping works, consult <<ref_scope,the
-reference>>.
-
-If you need more complicated information passed from the build
-statement to the rule (for example, if the rule needs "the file
-extension of the first input"), pass that through as an extra
-variable, like how `cflags` is passed above.
-
-If the top-level Ninja file is specified as an output of any build
-statement and it is out of date, Ninja will rebuild and reload it
-before building the targets requested by the user.
-
-Generating Ninja files from code
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-`misc/ninja_syntax.py` in the Ninja distribution is a tiny Python
-module to facilitate generating Ninja files. It allows you to make
-Python calls like `ninja.rule(name='foo', command='bar',
-depfile='$out.d')` and it will generate the appropriate syntax. Feel
-free to just inline it into your project's build system if it's
-useful.
-
-
-More details
-------------
-
-The `phony` rule
-~~~~~~~~~~~~~~~~
-
-The special rule name `phony` can be used to create aliases for other
-targets. For example:
-
-----------------
-build foo: phony some/file/in/a/faraway/subdir/foo
-----------------
-
-This makes `ninja foo` build the longer path. Semantically, the
-`phony` rule is equivalent to a plain rule where the `command` does
-nothing, but phony rules are handled specially in that they aren't
-printed when run, logged (see below), nor do they contribute to the
-command count printed as part of the build process.
-
-When a `phony` target is used as an input to another build rule, the
-other build rule will, semantically, consider the inputs of the
-`phony` rule as its own. Therefore, `phony` rules can be used to group
-inputs, e.g. header files.
-
-`phony` can also be used to create dummy targets for files which
-may not exist at build time. If a phony build statement is written
-without any dependencies, the target will be considered out of date if
-it does not exist. Without a phony build statement, Ninja will report
-an error if the file does not exist and is required by the build.
-
-To create a rule that never rebuilds, use a build rule without any input:
-----------------
-rule touch
- command = touch $out
-build file_that_always_exists.dummy: touch
-build dummy_target_to_follow_a_pattern: phony file_that_always_exists.dummy
-----------------
-
-
-Default target statements
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, if no targets are specified on the command line, Ninja
-will build every output that is not named as an input elsewhere.
-You can override this behavior using a default target statement.
-A default target statement causes Ninja to build only a given subset
-of output files if none are specified on the command line.
-
-Default target statements begin with the `default` keyword, and have
-the format +default _targets_+. A default target statement must appear
-after the build statement that declares the target as an output file.
-They are cumulative, so multiple statements may be used to extend
-the list of default targets. For example:
-
-----------------
-default foo bar
-default baz
-----------------
-
-This causes Ninja to build the `foo`, `bar` and `baz` targets by
-default.
-
-
-[[ref_log]]
-The Ninja log
-~~~~~~~~~~~~~
-
-For each built file, Ninja keeps a log of the command used to build
-it. Using this log Ninja can know when an existing output was built
-with a different command line than the build files specify (i.e., the
-command line changed) and knows to rebuild the file.
-
-The log file is kept in the build root in a file called `.ninja_log`.
-If you provide a variable named `builddir` in the outermost scope,
-`.ninja_log` will be kept in that directory instead.
-
-
-[[ref_versioning]]
-Version compatibility
-~~~~~~~~~~~~~~~~~~~~~
-
-_Available since Ninja 1.2._
-
-Ninja version labels follow the standard major.minor.patch format,
-where the major version is increased on backwards-incompatible
-syntax/behavioral changes and the minor version is increased on new
-behaviors. Your `build.ninja` may declare a variable named
-`ninja_required_version` that asserts the minimum Ninja version
-required to use the generated file. For example,
-
------
-ninja_required_version = 1.1
------
-
-declares that the build file relies on some feature that was
-introduced in Ninja 1.1 (perhaps the `pool` syntax), and that
-Ninja 1.1 or greater must be used to build. Unlike other Ninja
-variables, this version requirement is checked immediately when
-the variable is encountered in parsing, so it's best to put it
-at the top of the build file.
-
-Ninja always warns if the major versions of Ninja and the
-`ninja_required_version` don't match; a major version change hasn't
-come up yet so it's difficult to predict what behavior might be
-required.
-
-[[ref_headers]]
-C/C++ header dependencies
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To get C/C++ header dependencies (or any other build dependency that
-works in a similar way) correct Ninja has some extra functionality.
-
-The problem with headers is that the full list of files that a given
-source file depends on can only be discovered by the compiler:
-different preprocessor defines and include paths cause different files
-to be used. Some compilers can emit this information while building,
-and Ninja can use that to get its dependencies perfect.
-
-Consider: if the file has never been compiled, it must be built anyway,
-generating the header dependencies as a side effect. If any file is
-later modified (even in a way that changes which headers it depends
-on) the modification will cause a rebuild as well, keeping the
-dependencies up to date.
-
-When loading these special dependencies, Ninja implicitly adds extra
-build edges such that it is not an error if the listed dependency is
-missing. This allows you to delete a header file and rebuild without
-the build aborting due to a missing input.
-
-depfile
-^^^^^^^
-
-`gcc` (and other compilers like `clang`) support emitting dependency
-information in the syntax of a Makefile. (Any command that can write
-dependencies in this form can be used, not just `gcc`.)
-
-To bring this information into Ninja requires cooperation. On the
-Ninja side, the `depfile` attribute on the `build` must point to a
-path where this data is written. (Ninja only supports the limited
-subset of the Makefile syntax emitted by compilers.) Then the command
-must know to write dependencies into the `depfile` path.
-Use it like in the following example:
-
-----
-rule cc
- depfile = $out.d
- command = gcc -MD -MF $out.d [other gcc flags here]
-----
-
-The `-MD` flag to `gcc` tells it to output header dependencies, and
-the `-MF` flag tells it where to write them.
-
-deps
-^^^^
-
-_(Available since Ninja 1.3.)_
-
-It turns out that for large projects (and particularly on Windows,
-where the file system is slow) loading these dependency files on
-startup is slow.
-
-Ninja 1.3 can instead process dependencies just after they're generated
-and save a compacted form of the same information in a Ninja-internal
-database.
-
-Ninja supports this processing in two forms.
-
-1. `deps = gcc` specifies that the tool outputs `gcc`-style dependencies
- in the form of Makefiles. Adding this to the above example will
- cause Ninja to process the `depfile` immediately after the
- compilation finishes, then delete the `.d` file (which is only used
- as a temporary).
-
-2. `deps = msvc` specifies that the tool outputs header dependencies
- in the form produced by the Visual Studio compiler's
- http://msdn.microsoft.com/en-us/library/hdkef6tk(v=vs.90).aspx[`/showIncludes`
- flag]. Briefly, this means the tool outputs specially-formatted lines
- to its stdout. Ninja then filters these lines from the displayed
- output. No `depfile` attribute is necessary, but the localized string
- in front of the header file path should be globally defined. For instance,
- `msvc_deps_prefix = Note: including file:`
- for an English Visual Studio (the default).
-+
-----
-msvc_deps_prefix = Note: including file:
-rule cc
- deps = msvc
- command = cl /showIncludes -c $in /Fo$out
-----
-
-If the include directory directives are using absolute paths, your depfile
-may result in a mixture of relative and absolute paths. Paths used by other
-build rules need to match exactly. Therefore, it is recommended to use
-relative paths in these cases.
-
-[[ref_pool]]
-Pools
-~~~~~
-
-_Available since Ninja 1.1._
-
-Pools allow you to allocate one or more rules or edges a finite number
-of concurrent jobs which is more tightly restricted than the default
-parallelism.
-
-This can be useful, for example, to restrict a particular expensive rule
-(like link steps for huge executables), or to restrict particular build
-statements which you know perform poorly when run concurrently.
-
-Each pool has a `depth` variable which is specified in the build file.
-The pool is then referred to with the `pool` variable on either a rule
-or a build statement.
-
-No matter what pools you specify, ninja will never run more concurrent jobs
-than the default parallelism, or the number of jobs specified on the command
-line (with `-j`).
-
-----------------
-# No more than 4 links at a time.
-pool link_pool
- depth = 4
-
-# No more than 1 heavy object at a time.
-pool heavy_object_pool
- depth = 1
-
-rule link
- ...
- pool = link_pool
-
-rule cc
- ...
-
-# The link_pool is used here. Only 4 links will run concurrently.
-build foo.exe: link input.obj
-
-# A build statement can be exempted from its rule's pool by setting an
-# empty pool. This effectively puts the build statement back into the default
-# pool, which has infinite depth.
-build other.exe: link input.obj
- pool =
-
-# A build statement can specify a pool directly.
-# Only one of these builds will run at a time.
-build heavy_object1.obj: cc heavy_obj1.cc
- pool = heavy_object_pool
-build heavy_object2.obj: cc heavy_obj2.cc
- pool = heavy_object_pool
-
-----------------
-
-The `console` pool
-^^^^^^^^^^^^^^^^^^
-
-_Available since Ninja 1.5._
-
-There exists a pre-defined pool named `console` with a depth of 1. It has
-the special property that any task in the pool has direct access to the
-standard input, output and error streams provided to Ninja, which are
-normally connected to the user's console (hence the name) but could be
-redirected. This can be useful for interactive tasks or long-running tasks
-which produce status updates on the console (such as test suites).
-
-While a task in the `console` pool is running, Ninja's regular output (such
-as progress status and output from concurrent tasks) is buffered until
-it completes.
-
-[[ref_ninja_file]]
-Ninja file reference
---------------------
-
-A file is a series of declarations. A declaration can be one of:
-
-1. A rule declaration, which begins with +rule _rulename_+, and
- then has a series of indented lines defining variables.
-
-2. A build edge, which looks like +build _output1_ _output2_:
- _rulename_ _input1_ _input2_+. +
- Implicit dependencies may be tacked on the end with +|
- _dependency1_ _dependency2_+. +
- Order-only dependencies may be tacked on the end with +||
- _dependency1_ _dependency2_+. (See <<ref_dependencies,the reference on
- dependency types>>.)
- Validations may be taked on the end with +|@ _validation1_ _validation2_+.
- (See <<validations,the reference on validations>>.)
-+
-Implicit outputs _(available since Ninja 1.7)_ may be added before
-the `:` with +| _output1_ _output2_+ and do not appear in `$out`.
-(See <<ref_outputs,the reference on output types>>.)
-
-3. Variable declarations, which look like +_variable_ = _value_+.
-
-4. Default target statements, which look like +default _target1_ _target2_+.
-
-5. References to more files, which look like +subninja _path_+ or
- +include _path_+. The difference between these is explained below
- <<ref_scope,in the discussion about scoping>>.
-
-6. A pool declaration, which looks like +pool _poolname_+. Pools are explained
- <<ref_pool, in the section on pools>>.
-
-[[ref_lexer]]
-Lexical syntax
-~~~~~~~~~~~~~~
-
-Ninja is mostly encoding agnostic, as long as the bytes Ninja cares
-about (like slashes in paths) are ASCII. This means e.g. UTF-8 or
-ISO-8859-1 input files ought to work.
-
-Comments begin with `#` and extend to the end of the line.
-
-Newlines are significant. Statements like `build foo bar` are a set
-of space-separated tokens that end at the newline. Newlines and
-spaces within a token must be escaped.
-
-There is only one escape character, `$`, and it has the following
-behaviors:
-
-`$` followed by a newline:: escape the newline (continue the current line
-across a line break).
-
-`$` followed by text:: a variable reference.
-
-`${varname}`:: alternate syntax for `$varname`.
-
-`$` followed by space:: a space. (This is only necessary in lists of
-paths, where a space would otherwise separate filenames. See below.)
-
-`$:` :: a colon. (This is only necessary in `build` lines, where a colon
-would otherwise terminate the list of outputs.)
-
-`$$`:: a literal `$`.
-
-A `build` or `default` statement is first parsed as a space-separated
-list of filenames and then each name is expanded. This means that
-spaces within a variable will result in spaces in the expanded
-filename.
-
-----
-spaced = foo bar
-build $spaced/baz other$ file: ...
-# The above build line has two outputs: "foo bar/baz" and "other file".
-----
-
-In a `name = value` statement, whitespace at the beginning of a value
-is always stripped. Whitespace at the beginning of a line after a
-line continuation is also stripped.
-
-----
-two_words_with_one_space = foo $
- bar
-one_word_with_no_space = foo$
- bar
-----
-
-Other whitespace is only significant if it's at the beginning of a
-line. If a line is indented more than the previous one, it's
-considered part of its parent's scope; if it is indented less than the
-previous one, it closes the previous scope.
-
-[[ref_toplevel]]
-Top-level variables
-~~~~~~~~~~~~~~~~~~~
-
-Two variables are significant when declared in the outermost file scope.
-
-`builddir`:: a directory for some Ninja output files. See <<ref_log,the
- discussion of the build log>>. (You can also store other build output
- in this directory.)
-
-`ninja_required_version`:: the minimum version of Ninja required to process
- the build correctly. See <<ref_versioning,the discussion of versioning>>.
-
-
-[[ref_rule]]
-Rule variables
-~~~~~~~~~~~~~~
-
-A `rule` block contains a list of `key = value` declarations that
-affect the processing of the rule. Here is a full list of special
-keys.
-
-`command` (_required_):: the command line to run. Each `rule` may
- have only one `command` declaration. See <<ref_rule_command,the next
- section>> for more details on quoting and executing multiple commands.
-
-`depfile`:: path to an optional `Makefile` that contains extra
- _implicit dependencies_ (see <<ref_dependencies,the reference on
- dependency types>>). This is explicitly to support C/C++ header
- dependencies; see <<ref_headers,the full discussion>>.
-
-`deps`:: _(Available since Ninja 1.3.)_ if present, must be one of
- `gcc` or `msvc` to specify special dependency processing. See
- <<ref_headers,the full discussion>>. The generated database is
- stored as `.ninja_deps` in the `builddir`, see <<ref_toplevel,the
- discussion of `builddir`>>.
-
-`msvc_deps_prefix`:: _(Available since Ninja 1.5.)_ defines the string
- which should be stripped from msvc's /showIncludes output. Only
- needed when `deps = msvc` and no English Visual Studio version is used.
-
-`description`:: a short description of the command, used to pretty-print
- the command as it's running. The `-v` flag controls whether to print
- the full command or its description; if a command fails, the full command
- line will always be printed before the command's output.
-
-`dyndep`:: _(Available since Ninja 1.10.)_ Used only on build statements.
- If present, must name one of the build statement inputs. Dynamically
- discovered dependency information will be loaded from the file.
- See the <<ref_dyndep,dynamic dependencies>> section for details.
-
-`generator`:: if present, specifies that this rule is used to
- re-invoke the generator program. Files built using `generator`
- rules are treated specially in two ways: firstly, they will not be
- rebuilt if the command line changes; and secondly, they are not
- cleaned by default.
-
-`in`:: the space-separated list of files provided as inputs to the build line
- referencing this `rule`, shell-quoted if it appears in commands. (`$in` is
- provided solely for convenience; if you need some subset or variant of this
- list of files, just construct a new variable with that list and use
- that instead.)
-
-`in_newline`:: the same as `$in` except that multiple inputs are
- separated by newlines rather than spaces. (For use with
- `$rspfile_content`; this works around a bug in the MSVC linker where
- it uses a fixed-size buffer for processing input.)
-
-`out`:: the space-separated list of files provided as outputs to the build line
- referencing this `rule`, shell-quoted if it appears in commands.
-
-`restat`:: if present, causes Ninja to re-stat the command's outputs
- after execution of the command. Each output whose modification time
- the command did not change will be treated as though it had never
- needed to be built. This may cause the output's reverse
- dependencies to be removed from the list of pending build actions.
-
-`rspfile`, `rspfile_content`:: if present (both), Ninja will use a
- response file for the given command, i.e. write the selected string
- (`rspfile_content`) to the given file (`rspfile`) before calling the
- command and delete the file after successful execution of the
- command.
-+
-This is particularly useful on Windows OS, where the maximal length of
-a command line is limited and response files must be used instead.
-+
-Use it like in the following example:
-+
-----
-rule link
- command = link.exe /OUT$out [usual link flags here] @$out.rsp
- rspfile = $out.rsp
- rspfile_content = $in
-
-build myapp.exe: link a.obj b.obj [possibly many other .obj files]
-----
-
-[[ref_rule_command]]
-Interpretation of the `command` variable
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Fundamentally, command lines behave differently on Unixes and Windows.
-
-On Unixes, commands are arrays of arguments. The Ninja `command`
-variable is passed directly to `sh -c`, which is then responsible for
-interpreting that string into an argv array. Therefore, the quoting
-rules are those of the shell, and you can use all the normal shell
-operators, like `&&` to chain multiple commands, or `VAR=value cmd` to
-set environment variables.
-
-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 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
-prefixing the command with `cmd /c`. Ninja may error with "invalid parameter"
-which usually indicates that the command line length has been exceeded.
-
-[[ref_outputs]]
-Build outputs
-~~~~~~~~~~~~~
-
-There are two types of build outputs which are subtly different.
-
-1. _Explicit outputs_, as listed in a build line. These are
- available as the `$out` variable in the rule.
-+
-This is the standard form of output to be used for e.g. the
-object file of a compile command.
-
-2. _Implicit outputs_, as listed in a build line with the syntax +|
- _out1_ _out2_+ + before the `:` of a build line _(available since
- Ninja 1.7)_. The semantics are identical to explicit outputs,
- the only difference is that implicit outputs don't show up in the
- `$out` variable.
-+
-This is for expressing outputs that don't show up on the
-command line of the command.
-
-[[ref_dependencies]]
-Build dependencies
-~~~~~~~~~~~~~~~~~~
-
-There are three types of build dependencies which are subtly different.
-
-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 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
-source file of a compile command.
-
-2. _Implicit dependencies_, either as picked up from
- a `depfile` attribute on a rule or from the syntax +| _dep1_
- _dep2_+ on the end of a build line. The semantics are identical to
- explicit dependencies, the only difference is that implicit dependencies
- don't show up in the `$in` variable.
-+
-This is for expressing dependencies that don't show up on the
-command line of the command; for example, for a rule that runs a
-script that reads a hardcoded file, the hardcoded file should
-be an implicit dependency, as changes to the file should cause
-the output to rebuild, even though it doesn't show up in the arguments.
-+
-Note that dependencies as loaded through depfiles have slightly different
-semantics, as described in the <<ref_rule,rule reference>>.
-
-3. _Order-only dependencies_, expressed with the syntax +|| _dep1_
- _dep2_+ on the end of a build line. When these are out of date, the
- output is not rebuilt until they are built, but changes in order-only
- dependencies alone do not cause the output to be rebuilt.
-+
-Order-only dependencies can be useful for bootstrapping dependencies
-that are only discovered during build time: for example, to generate a
-header file before starting a subsequent compilation step. (Once the
-header is used in compilation, a generated dependency file will then
-express the implicit dependency.)
-
-File paths are compared as is, which means that an absolute path and a
-relative path, pointing to the same file, are considered different by Ninja.
-
-[[validations]]
-Validations
-~~~~~~~~~~~
-
-_Available since Ninja 1.11._
-
-Validations listed on the build line cause the specified files to be
-added to the top level of the build graph (as if they were specified
-on the Ninja command line) whenever the build line is a transitive
-dependency of one of the targets specified on the command line or a
-default target.
-
-Validations are added to the build graph regardless of whether the output
-files of the build statement are dirty are not, and the dirty state of
-the build statement that outputs the file being used as a validation
-has no effect on the dirty state of the build statement that requested it.
-
-A build edge can list another build edge as a validation even if the second
-edge depends on the first.
-
-Validations are designed to handle rules that perform error checking but
-don't produce any artifacts needed by the build, for example, static
-analysis tools. Marking the static analysis rule as an implicit input
-of the main build rule of the source files or of the rules that depend
-on the main build rule would slow down the critical path of the build,
-but using a validation would allow the build to proceed in parallel with
-the static analysis rule once the main build rule is complete.
-
-Variable expansion
-~~~~~~~~~~~~~~~~~~
-
-Variables are expanded in paths (in a `build` or `default` statement)
-and on the right side of a `name = value` statement.
-
-When a `name = value` statement is evaluated, its right-hand side is
-expanded immediately (according to the below scoping rules), and
-from then on `$name` expands to the static string as the result of the
-expansion. It is never the case that you'll need to "double-escape" a
-value to prevent it from getting expanded twice.
-
-All variables are expanded immediately as they're encountered in parsing,
-with one important exception: variables in `rule` blocks are expanded
-when the rule is _used_, not when it is declared. In the following
-example, the `demo` rule prints "this is a demo of bar".
-
-----
-rule demo
- command = echo "this is a demo of $foo"
-
-build out: demo
- foo = bar
-----
-
-[[ref_scope]]
-Evaluation and scoping
-~~~~~~~~~~~~~~~~~~~~~~
-
-Top-level variable declarations are scoped to the file they occur in.
-
-Rule declarations are also scoped to the file they occur in.
-_(Available since Ninja 1.6)_
-
-The `subninja` keyword, used to include another `.ninja` file,
-introduces a new scope. The included `subninja` file may use the
-variables and rules from the parent file, and shadow their values for the file's
-scope, but it won't affect values of the variables in the parent.
-
-To include another `.ninja` file in the current scope, much like a C
-`#include` statement, use `include` instead of `subninja`.
-
-Variable declarations indented in a `build` block are scoped to the
-`build` block. The full lookup order for a variable expanded in a
-`build` block (or the `rule` is uses) is:
-
-1. Special built-in variables (`$in`, `$out`).
-
-2. Build-level variables from the `build` block.
-
-3. Rule-level variables from the `rule` block (i.e. `$command`).
- (Note from the above discussion on expansion that these are
- expanded "late", and may make use of in-scope bindings like `$in`.)
-
-4. File-level variables from the file that the `build` line was in.
-
-5. Variables from the file that included that file using the
- `subninja` keyword.
-
-[[ref_dyndep]]
-Dynamic Dependencies
---------------------
-
-_Available since Ninja 1.10._
-
-Some use cases require implicit dependency information to be dynamically
-discovered from source file content _during the build_ in order to build
-correctly on the first run (e.g. Fortran module dependencies). This is
-unlike <<ref_headers,header dependencies>> which are only needed on the
-second run and later to rebuild correctly. A build statement may have a
-`dyndep` binding naming one of its inputs to specify that dynamic
-dependency information must be loaded from the file. For example:
-
-----
-build out: ... || foo
- dyndep = foo
-build foo: ...
-----
-
-This specifies that file `foo` is a dyndep file. Since it is an input,
-the build statement for `out` can never be executed before `foo` is built.
-As soon as `foo` is finished Ninja will read it to load dynamically
-discovered dependency information for `out`. This may include additional
-implicit inputs and/or outputs. Ninja will update the build graph
-accordingly and the build will proceed as if the information was known
-originally.
-
-Dyndep file reference
-~~~~~~~~~~~~~~~~~~~~~
-
-Files specified by `dyndep` bindings use the same <<ref_lexer,lexical syntax>>
-as <<ref_ninja_file,ninja build files>> and have the following layout.
-
-1. A version number in the form `<major>[.<minor>][<suffix>]`:
-+
-----
-ninja_dyndep_version = 1
-----
-+
-Currently the version number must always be `1` or `1.0` but may have
-an arbitrary suffix.
-
-2. One or more build statements of the form:
-+
-----
-build out | imp-outs... : dyndep | imp-ins...
-----
-+
-Every statement must specify exactly one explicit output and must use
-the rule name `dyndep`. The `| imp-outs...` and `| imp-ins...` portions
-are optional.
-
-3. An optional `restat` <<ref_rule,variable binding>> on each build statement.
-
-The build statements in a dyndep file must have a one-to-one correspondence
-to build statements in the <<ref_ninja_file,ninja build file>> that name the
-dyndep file in a `dyndep` binding. No dyndep build statement may be omitted
-and no extra build statements may be specified.
-
-Dyndep Examples
-~~~~~~~~~~~~~~~
-
-Fortran Modules
-^^^^^^^^^^^^^^^
-
-Consider a Fortran source file `foo.f90` that provides a module
-`foo.mod` (an implicit output of compilation) and another source file
-`bar.f90` that uses the module (an implicit input of compilation). This
-implicit dependency must be discovered before we compile either source
-in order to ensure that `bar.f90` never compiles before `foo.f90`, and
-that `bar.f90` recompiles when `foo.mod` changes. We can achieve this
-as follows:
-
-----
-rule f95
- command = f95 -o $out -c $in
-rule fscan
- command = fscan -o $out $in
-
-build foobar.dd: fscan foo.f90 bar.f90
-
-build foo.o: f95 foo.f90 || foobar.dd
- dyndep = foobar.dd
-build bar.o: f95 bar.f90 || foobar.dd
- dyndep = foobar.dd
-----
-
-In this example the order-only dependencies ensure that `foobar.dd` is
-generated before either source compiles. The hypothetical `fscan` tool
-scans the source files, assumes each will be compiled to a `.o` of the
-same name, and writes `foobar.dd` with content such as:
-
-----
-ninja_dyndep_version = 1
-build foo.o | foo.mod: dyndep
-build bar.o: dyndep | foo.mod
-----
-
-Ninja will load this file to add `foo.mod` as an implicit output of
-`foo.o` and implicit input of `bar.o`. This ensures that the Fortran
-sources are always compiled in the proper order and recompiled when
-needed.
-
-Tarball Extraction
-^^^^^^^^^^^^^^^^^^
-
-Consider a tarball `foo.tar` that we want to extract. The extraction time
-can be recorded with a `foo.tar.stamp` file so that extraction repeats if
-the tarball changes, but we also would like to re-extract if any of the
-outputs is missing. However, the list of outputs depends on the content
-of the tarball and cannot be spelled out explicitly in the ninja build file.
-We can achieve this as follows:
-
-----
-rule untar
- command = tar xf $in && touch $out
-rule scantar
- command = scantar --stamp=$stamp --dd=$out $in
-build foo.tar.dd: scantar foo.tar
- stamp = foo.tar.stamp
-build foo.tar.stamp: untar foo.tar || foo.tar.dd
- dyndep = foo.tar.dd
-----
-
-In this example the order-only dependency ensures that `foo.tar.dd` is
-built before the tarball extracts. The hypothetical `scantar` tool
-will read the tarball (e.g. via `tar tf`) and write `foo.tar.dd` with
-content such as:
-
-----
-ninja_dyndep_version = 1
-build foo.tar.stamp | file1.txt file2.txt : dyndep
- restat = 1
-----
-
-Ninja will load this file to add `file1.txt` and `file2.txt` as implicit
-outputs of `foo.tar.stamp`, and to mark the build statement for `restat`.
-On future builds, if any implicit output is missing the tarball will be
-extracted again. The `restat` binding tells Ninja to tolerate the fact
-that the implicit outputs may not have modification times newer than
-the tarball itself (avoiding re-extraction on every build).
diff --git a/doc/style.css b/doc/style.css
deleted file mode 100644
index 9976c03..0000000
--- a/doc/style.css
+++ /dev/null
@@ -1,29 +0,0 @@
-body {
- margin: 5ex 10ex;
- max-width: 80ex;
- line-height: 1.5;
- font-family: sans-serif;
-}
-h1, h2, h3 {
- font-weight: normal;
-}
-pre, code {
- font-family: x, monospace;
-}
-pre {
- padding: 1ex;
- background: #eee;
- border: solid 1px #ddd;
- min-width: 0;
- font-size: 90%;
-}
-code {
- color: #007;
-}
-div.chapter {
- margin-top: 4em;
- border-top: solid 2px black;
-}
-p {
- margin-top: 0;
-}
diff --git a/misc/afl-fuzz-tokens/kw_build b/misc/afl-fuzz-tokens/kw_build
deleted file mode 100644
index c795b05..0000000
--- a/misc/afl-fuzz-tokens/kw_build
+++ /dev/null
@@ -1 +0,0 @@
-build
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/kw_default b/misc/afl-fuzz-tokens/kw_default
deleted file mode 100644
index 331d858..0000000
--- a/misc/afl-fuzz-tokens/kw_default
+++ /dev/null
@@ -1 +0,0 @@
-default
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/kw_include b/misc/afl-fuzz-tokens/kw_include
deleted file mode 100644
index 2996fba..0000000
--- a/misc/afl-fuzz-tokens/kw_include
+++ /dev/null
@@ -1 +0,0 @@
-include
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/kw_pool b/misc/afl-fuzz-tokens/kw_pool
deleted file mode 100644
index e783591..0000000
--- a/misc/afl-fuzz-tokens/kw_pool
+++ /dev/null
@@ -1 +0,0 @@
-pool
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/kw_rule b/misc/afl-fuzz-tokens/kw_rule
deleted file mode 100644
index 841e840..0000000
--- a/misc/afl-fuzz-tokens/kw_rule
+++ /dev/null
@@ -1 +0,0 @@
-rule
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/kw_subninja b/misc/afl-fuzz-tokens/kw_subninja
deleted file mode 100644
index c4fe0c7..0000000
--- a/misc/afl-fuzz-tokens/kw_subninja
+++ /dev/null
@@ -1 +0,0 @@
-subninja
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_a b/misc/afl-fuzz-tokens/misc_a
deleted file mode 100644
index 2e65efe..0000000
--- a/misc/afl-fuzz-tokens/misc_a
+++ /dev/null
@@ -1 +0,0 @@
-a
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_b b/misc/afl-fuzz-tokens/misc_b
deleted file mode 100644
index 63d8dbd..0000000
--- a/misc/afl-fuzz-tokens/misc_b
+++ /dev/null
@@ -1 +0,0 @@
-b
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_colon b/misc/afl-fuzz-tokens/misc_colon
deleted file mode 100644
index 22ded55..0000000
--- a/misc/afl-fuzz-tokens/misc_colon
+++ /dev/null
@@ -1 +0,0 @@
-:
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_cont b/misc/afl-fuzz-tokens/misc_cont
deleted file mode 100644
index 857f13a..0000000
--- a/misc/afl-fuzz-tokens/misc_cont
+++ /dev/null
@@ -1 +0,0 @@
-$
diff --git a/misc/afl-fuzz-tokens/misc_dollar b/misc/afl-fuzz-tokens/misc_dollar
deleted file mode 100644
index 6f4f765..0000000
--- a/misc/afl-fuzz-tokens/misc_dollar
+++ /dev/null
@@ -1 +0,0 @@
-$
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_eq b/misc/afl-fuzz-tokens/misc_eq
deleted file mode 100644
index 851c75c..0000000
--- a/misc/afl-fuzz-tokens/misc_eq
+++ /dev/null
@@ -1 +0,0 @@
-=
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_indent b/misc/afl-fuzz-tokens/misc_indent
deleted file mode 100644
index 136d063..0000000
--- a/misc/afl-fuzz-tokens/misc_indent
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_pipe b/misc/afl-fuzz-tokens/misc_pipe
deleted file mode 100644
index a3871d4..0000000
--- a/misc/afl-fuzz-tokens/misc_pipe
+++ /dev/null
@@ -1 +0,0 @@
-|
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_pipepipe b/misc/afl-fuzz-tokens/misc_pipepipe
deleted file mode 100644
index 27cc728..0000000
--- a/misc/afl-fuzz-tokens/misc_pipepipe
+++ /dev/null
@@ -1 +0,0 @@
-||
\ No newline at end of file
diff --git a/misc/afl-fuzz-tokens/misc_space b/misc/afl-fuzz-tokens/misc_space
deleted file mode 100644
index 0519ecb..0000000
--- a/misc/afl-fuzz-tokens/misc_space
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/misc/afl-fuzz/build.ninja b/misc/afl-fuzz/build.ninja
deleted file mode 100644
index 52cd2f1..0000000
--- a/misc/afl-fuzz/build.ninja
+++ /dev/null
@@ -1,5 +0,0 @@
-rule b
- command = clang -MMD -MF $out.d -o $out -c $in
- description = building $out
-
-build a.o: b a.c
diff --git a/misc/bash-completion b/misc/bash-completion
deleted file mode 100644
index e604cd4..0000000
--- a/misc/bash-completion
+++ /dev/null
@@ -1,57 +0,0 @@
-# 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.
-
-# Add the following to your .bashrc to tab-complete ninja targets
-# . path/to/ninja/misc/bash-completion
-
-_ninja_target() {
- local cur prev targets dir line targets_command OPTIND
-
- # When available, use bash_completion to:
- # 1) Complete words when the cursor is in the middle of the word
- # 2) Complete paths with files or directories, as appropriate
- if _get_comp_words_by_ref cur prev &>/dev/null ; then
- case $prev in
- -f)
- _filedir
- return 0
- ;;
- -C)
- _filedir -d
- return 0
- ;;
- esac
- else
- cur="${COMP_WORDS[COMP_CWORD]}"
- fi
-
- if [[ "$cur" == "--"* ]]; then
- # there is currently only one argument that takes --
- COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}"))
- else
- dir="."
- line=$(echo ${COMP_LINE} | cut -d" " -f 2-)
- # filter out all non relevant arguments but keep C for dirs
- while getopts :C:f:j:l:k:nvd:t: opt $line; do
- case $opt in
- # eval for tilde expansion
- C) eval dir="$OPTARG" ;;
- esac
- done;
- targets_command="eval ninja -C \"${dir}\" -t targets all 2>/dev/null | cut -d: -f1"
- COMPREPLY=($(compgen -W '`${targets_command}`' -- "$cur"))
- fi
- return
-}
-complete -F _ninja_target ninja
diff --git a/misc/ci.py b/misc/ci.py
deleted file mode 100755
index 17cbf14..0000000
--- a/misc/ci.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-
-ignores = [
- '.git/',
- 'misc/afl-fuzz-tokens/',
- 'ninja_deps',
- 'src/depfile_parser.cc',
- 'src/lexer.cc',
-]
-
-error_count = 0
-
-def error(path, msg):
- global error_count
- error_count += 1
- print('\x1b[1;31m{}\x1b[0;31m{}\x1b[0m'.format(path, msg))
-
-for root, directory, filenames in os.walk('.'):
- for filename in filenames:
- path = os.path.join(root, filename)[2:]
- if any([path.startswith(x) for x in ignores]):
- continue
- with open(path, 'rb') as file:
- line_nr = 1
- try:
- for line in [x.decode() for x in file.readlines()]:
- if len(line) == 0 or line[-1] != '\n':
- error(path, ' missing newline at end of file.')
- if len(line) > 1:
- if line[-2] == '\r':
- error(path, ' has Windows line endings.')
- break
- if line[-2] == ' ' or line[-2] == '\t':
- error(path, ':{} has trailing whitespace.'.format(line_nr))
- line_nr += 1
- except UnicodeError:
- pass # binary file
-
-exit(error_count)
diff --git a/misc/fuchsia/build-ninja.sh b/misc/fuchsia/build-ninja.sh
deleted file mode 100755
index 0f5488d..0000000
--- a/misc/fuchsia/build-ninja.sh
+++ /dev/null
@@ -1,619 +0,0 @@
-#!/bin/bash
-# Copyright 2023 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.
-
-set -e
-
-SCRIPT_DIR="$(dirname "$0")"
-
-die () {
- echo >&2 "ERROR: $*"
- exit 1
-}
-
-_VERBOSE=
-
-run () {
- if [[ -n "${_VERBOSE}" ]]; then
- echo "@COMMAND: $*"
- fi
- "$@"
-}
-
-# Determine Fuchsia host_tag for prebuilt binaries.
-UNAME_OS="$(uname -s)"
-UNAME_ARCH="$(uname -m)"
-
-case "${UNAME_OS}" in
- Linux)
- HOST_OS=linux
- ;;
- Darwin)
- HOST_OS=mac
- ;;
- *)
- die "Unknown host operation system: ${UNAME_OS}"
- ;;
-esac
-
-case "${UNAME_ARCH}" in
- amd64|x86_64)
- HOST_ARCH=x64
- ;;
- arm64|aarch64)
- HOST_ARCH=arm64
- ;;
- *)
- die "Unknown host CPU architecture: ${UNAME_ARCH}"
- ;;
-esac
-
-DEFAULT_JEMALLOC_GIT_URL=https://fuchsia.googlesource.com/third_party/github.com/jemalloc/jemalloc.git
-DEFAULT_JEMALLOC_TAG=5.3.0
-
-HOST_TAG="${HOST_OS}-${HOST_ARCH}"
-
-ALLOCATOR=
-ALLOCATOR_LINK_ONLY=
-BUILD_DIR=
-CLANG_BINPREFIX=
-CMAKE=cmake
-DEBUG=
-HELP=
-INCREMENTAL=
-INSTALL_PATH=
-LTO=
-NINJA=
-NO_LTO=
-NO_CLANG=
-NO_TESTS=
-STATIC=
-SYSROOT=
-TARGET_FLAGS=
-USE_LIBCXX=
-USE_MINGW64=
-USE_WINE=
-WINDOWS_SSH=${NINJA_WINDOWS_SSH:-}
-for OPT; do
- case "${OPT}" in
- --help)
- HELP=true
- ;;
- --cmake=*)
- CMAKE="${OPT#--cmake=}"
- ;;
- --debug)
- DEBUG=true
- ;;
- --static)
- STATIC=true
- ;;
- -v|--verbose)
- _VERBOSE=1
- ;;
- -i|--incremental)
- INCREMENTAL=true
- ;;
- --no-clang)
- NO_CLANG=true
- ;;
- --build-dir=*)
- BUILD_DIR="${OPT#--build-dir=}"
- ;;
- --clang-binprefix=*)
- CLANG_BINPREFIX="${OPT#--clang-binprefix=}"
- ;;
- --sysroot=*)
- SYSROOT="${OPT#--sysroot=}"
- ;;
- --fuchsia-dir=*)
- FUCHSIA_DIR="${OPT#--fuchsia-dir=}"
- CLANG_BINPREFIX="${FUCHSIA_DIR}/prebuilt/third_party/clang/${HOST_TAG}/bin"
- if [[ "${HOST_OS}" == "linux" ]]; then
- SYSROOT="${FUCHSIA_DIR}/prebuilt/third_party/sysroot/linux"
- fi
- if [[ "${HOST_OS}" == "mac" ]]; then
- # NOTE: Required to link binaries properly, otherwise on MacOS
- # the prebuilt toolchain will try to link the system libc++.a
- # which is incompatible with the libc++ headers that come with
- # the prebuilt toolchain.
- USE_LIBCXX=true
- fi
- NINJA="${FUCHSIA_DIR}/prebuilt/third_party/ninja/${HOST_TAG}/ninja"
- CMAKE="${FUCHSIA_DIR}/prebuilt/third_party/cmake/${HOST_TAG}/bin/cmake"
- ;;
- --target=*)
- TARGET_FLAGS="${OPT}"
- ;;
- --ninja=*)
- NINJA="${OPT##--ninja=}"
- ;;
- --no-tests)
- NO_TESTS=true
- ;;
- --no-lto)
- NO_LTO=true
- ;;
- --allocator=*)
- ALLOCATOR="${OPT#--allocator=}"
- ALLOCATOR_LINK_ONLY=
- ;;
- --allocator-link-only=*)
- ALLOCATOR="${OPT#--allocator-link-only=}"
- ALLOCATOR_LINK_ONLY=true
- ;;
- --jemalloc-git-url=*)
- JEMALLOC_GIT_URL="${OPT#--jemalloc-git-url=}"
- ;;
- --jemalloc-tag=*)
- JEMALLOC_TAG="${OPT#--jemalloc-tag=}"
- ;;
- --use-libc++)
- USE_LIBCXX=true
- ;;
- --install-to=*)
- INSTALL_PATH="${OPT#--install-to=}"
- ;;
- --mingw64)
- USE_MINGW64=true
- ;;
- --wine)
- USE_WINE=true
- WINDOWS_SSH=
- ;;
- --windows-ssh=*)
- WINDOWS_SSH=${OPT##--windows-ssh=}
- USE_WINE=
- ;;
- -*)
- die "Unknown option $OPT, see --help."
- ;;
- *)
- die "This script does not take parameters [$OPT]. See --help."
- ;;
- esac
-done
-
-if [[ -n "$HELP" ]]; then
- PROGNAME="$(basename "$0")"
- cat <<EOF
-Usage: ${PROGNAME} [options]
-
-Rebuild an optimized Ninja binary locally, using settings similar to the ones
-use by the Fuchsia LUCI recipe (which generates the prebuilt used by the
-Fuchsia build). This allows better performance comparisons. This means:
-
-- Enabling link-time optimization
- (except if '--no-lto' is used).
-
-- Linking against the rpmalloc or mimalloc library
- (when '--allocator=<name>' or '--allocator-link-only=<name>' is used).
-
-- Use the Fuchsia prebuilt Clang toolchain for compilation
- (when '--fuchsia-dir=FUCHSIA_DIR' is specified).
-
-The script will also rebuild the test suite and run it to validate the
-generated binary. Use '--no-tests'to disable this.
-
-Use '--install-to=PATH' to copy the generated Ninja binary to a specific
-location. It will be under 'build-cmake/ninja/build/ninja' otherwise.
-
-Use '--no-clang' to use the default C++ compiler, otherwise the script will
-use 'clang++' instead, unless '--clang-binprefix=PREFIX' is used.
-
-Use '--ninja=PATH' to use Ninja as the CMake build generator (default is Make,
-which is considerably slower).
-
-Use '--fuchsia-dir=FUCHSIA_DIR' to use the prebuilt Clang toolchain, prebuilt
-sysroot and Ninja tool.
-
-Valid options:
-
- --help Print this message.
- --cmake=PATH Specify CMake executable path to use.
- --ninja=PATH Specify Ninja executable path, to use as a CMake
- generator. This results in faster build than the
- default Make generator.
- --no-lto Disable Link Time Optimization (LTO).
- --no-tests Do not run tests.
- --incremental, -i Perform incremental build.
- --debug Generate debug version.
- --install-to=FILE Copy the resulting binary to FILE.
- --no-clang Do not use Clang, only the system 'c++' and 'ar'.
- --clang-binprefix=DIR Specify directory with Clang toolchain binaries.
- --sysroot=DIR Specify sysroot directory.
- --fuchsia-dir=DIR Specify Fuchsia directory where to find Clang and sysroot prebuilts.
- --target=ARCH Specify clang target triple for cross-compilation..
-
- --build-dir=DIR Specify directory (default is ${BUILD_DIR}).
-
- --static Generate static executable. Note that this can be
- slower due to lack of SIMD-optimized memchr() and
- related functions.
-
- --mingw64 Use mingw64 on Linux to cross-compile Windows binaries,
-
- --wine Use Wine on Linux to run the tests. Requires
- --mingw64.
-
- --windows-ssh=<hostname>
- --windows-ssh=<hostname>:<port>
- Run Windows binaries on a remote Windows machine
- with ssh. Requires --mingw64. Note: setting the
- value in the NINJA_WINDOWS_SSH environment variable
- works too.
- --allocator=rpmalloc
- --allocator=mimalloc
- --allocator=jemalloc
- Build and link an alternative malloc library, which
- in many cases results in a 10% faster binary. Note
- that the allocator static library is cached in
- a user-specific directory to be reused later
- with --allocator-link-only (see below).
-
- --allocator-link-only=<name>
- Same as --allocator=<name> but do not rebuild the
- allocator, and instead reuse a cached version from
- a previous --allocator=<name> call.
-
- --jemalloc-git-url=URL
- jemalloc source git URL
- [$DEFAULT_JEMALLOC_GIT_URL]
- --jemalloc-tag=TAG
- jemalloc git tag [$DEFAULT_JEMALLOC_TAG]
-
-
- --use-libc++ Use libc++ (incompatible with --no-clang).
-EOF
- exit 0
-fi
-
-# Directory where to store cached elements (e.g. allocator libraries, gtest, etc)
-if [[ -z "${XDG_CACHE_HOME}" ]]; then
- XDG_CACHE_HOME=${HOME}/.cache
-fi
-CACHE_DIR="${XDG_CACHE_HOME}/build-ninja-cache"
-# Location of binaries specific to a given build type.
-# For now use a single directory, later, separate based on build options to
-# speed up incremental builds when switching between build types.
-BUILD_CACHE_DIR="${CACHE_DIR}/build"
-
-# Validate --allocator value is any,
-case "${ALLOCATOR}" in
- jemalloc|rpmalloc|mimalloc|"") # Note: "" selects for ALLOCATOR not being set.
- ;;
- *)
- die "Invalid --allocator value ${ALLOCATOR}, must be one of: jemalloc, rpmalloc, mimalloc"
- ;;
-esac
-
-CMAKE_ARGS=()
-if [[ -n "${NINJA}" ]]; then
- CMAKE_ARGS+=(-GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}")
-fi
-
-if [[ -n "${DEBUG}" ]]; then
- CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=Debug")
-else
- CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=Release")
-fi
-
-if [[ -n "${USE_MINGW64}" ]]; then
- if [[ -n "${ALLOCATOR}" ]]; then
- die "The --mingw64 option is not compatible with the --allocator option"
- fi
- MINGW64_TOOLCHAIN="${SCRIPT_DIR}/mingw64-toolchain.cmake"
- CMAKE_ARGS+=("-DCMAKE_TOOLCHAIN_FILE=${MINGW64_TOOLCHAIN}")
- if [[ -z "${BUILD_DIR}" ]]; then
- BUILD_DIR=build-mingw64
- fi
- if [[ -z "${USE_WINE}" && -z "${WINDOWS_SSH}" ]]; then
- echo >&2 "WARNING: Disabling tests due to lack of --wine or --windows-ssh option."
- NO_TESTS=true
- fi
-else
- if [[ -n "${USE_WINE}" ]]; then
- echo >&2 "ERROR: --wine requires --mingw64"
- exit 1
- fi
- if [[ -n "${WINDOWS_SSH}" ]]; then
- echo >&2 "ERROR: --windows-ssh requires --mingw64"
- exit 1
- fi
-fi
-
-if [[ -z "${BUILD_DIR}" ]]; then
- BUILD_DIR=build-cmake
-fi
-
-if [[ -n "${CLANG_BINPREFIX}" ]]; then
- # Ensure CLANG_BINPREFIX has a trailing separator to make
- # expressions like "${CLANG_BINPREFIX}clang" work properly.
- CLANG_BINPREFIX="${CLANG_BINPREFIX%/}/"
-fi
-
-if [[ -n "${NO_CLANG}" ]]; then
- CC=$(which cc)
- CXX=$(which c++)
- AR=$(which ar)
-else
- CC="${CLANG_BINPREFIX}clang"
- CXX="${CLANG_BINPREFIX}clang++"
- AR="${CLANG_BINPREFIX}llvm-ar"
-fi
-if [[ ! -f "${AR}" ]]; then
- unset AR
-fi
-CFLAGS="$TARGET_FLAGS"
-CXXFLAGS="$TARGET_FLAGS"
-LDFLAGS="$TARGET_FLAGS"
-
-CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-rtti"
-
-if [[ -n "${STATIC}" ]]; then
- CXXFLAGS="$CXXFLAGS -static"
- LDFLAGS="$LDFLAGS -static"
-fi
-
-LIBCXX_FLAGS="-static-libstdc++"
-if [[ -z "$USE_MINGW64" ]]; then
- LIBCXX_FLAGS="$LIBCXX_FLAGS -pthread"
-fi
-
-if [[ -n "${USE_MINGW64}" && -z "${NO_LTO}" ]]; then
- echo >&2 "WARNING: Disabling LTO for mingw64 toolchain since it produces broken binaries!"
- NO_LTO=true
-fi
-
-if [[ -n "${NO_LTO}" ]]; then
- CFLAGS="$CFLAGS -fno-lto"
- CXXFLAGS="$CXXFLAGS -fno-lto"
- LDFLAGS="$LDFLAGS -fno-lto"
-fi
-
-if [[ -n "${USE_LIBCXX}" ]]; then
- LIBCXX_A="$("${CLANG_BINPREFIX}clang++" --print-file-name=libc++.a)"
- if [[ "${LIBCXX_A}" == "libc++.a" ]]; then
- # NOTE: --print-file-name=libc++.a does not work on MacOS for some reason.
- LIBCXX_A="${CLANG_BINPREFIX}../lib/libc++.a"
- if [[ ! -f "${LIBCXX_A}" ]]; then
- die "Could not find libc++.a with your Clang toolchain!"
- fi
- fi
- CXXFLAGS="$CXXFLAGS -stdlib=libc++"
- LIBCXX_FLAGS="-stdlib=libc++ $LIBCXX_A"
- if [[ -z "$USE_MINGW64" ]]; then
- LIBCXX_FLAGS="$LIBCXX_FLAGS -lpthread -ldl"
- fi
-fi
-
-# Ensure we generate smaller executables on Linux
-if [[ "$OSTYPE" =~ linux* ]]; then
- CFLAGS="$CFLAGS -fPIE"
-fi
-
-export CC CXX CFLAGS CXXFLAGS LDFLAGS
-
-if [[ -n "${SYSROOT}" ]]; then
- CMAKE_ARGS+=("-DCMAKE_SYSROOT=$SYSROOT")
-fi
-
-if [[ "${ALLOCATOR}" == "rpmalloc" ]]; then
- RPMALLOC_LIB="${BUILD_CACHE_DIR}/librpmallocwrap.a"
-
- if [[ -z "${ALLOCATOR_LINK_ONLY}" ]]; then
- if [[ -z "${NINJA}" ]]; then
- die "Ninja is required to build rpmalloc, please use --ninja or --fuchsia-dir!"
- fi
- echo "Rebuilding rpmalloc library from scratch"
- RPMALLOC_GIT_URL="https://fuchsia.googlesource.com/third_party/github.com/mjansson/rpmalloc"
- RPMALLOC_BRANCH='+upstream/develop'
- RPMALLOC_REVISION='b097fd0916500439721a114bb9cd8d14bd998683'
- RPMALLOC_ARCH=x86-64
- RPMALLOC_OS=linux
- if [[ "$RPMALLOC_OS" == "macos" ]]; then
- RPMALLOC_LIBPATH=lib/$RPMALLOC_OS/release/librpmallocwrap.a
- else
- RPMALLOC_LIBPATH=lib/$RPMALLOC_OS/release/$RPMALLOC_ARCH/librpmallocwrap.a
- fi
-
- TMPDIR="$(mktemp -d /tmp/build-rpmalloc.XXXXX)"
- (
- run cd "$TMPDIR"
- run git init
- run git fetch --tags --quiet "$RPMALLOC_GIT_URL" "$RPMALLOC_BRANCH"
- run git checkout "$RPMALLOC_REVISION"
-
- # Clang will now complain when `-funit-at-a-time` is being used.
- run sed -i -e "s|-Wno-disabled-macro-expansion'|-Wno-disabled-macro-expansion', '-Wno-ignored-optimization-argument'|g" build/ninja/clang.py
-
- # Remove -Weverything
- run sed -i -e "s|-Weverything|-Wno-pedantic|g" build/ninja/clang.py
-
- export CC CXX AR CFLAGS CXXFLAGS LDFLAGS
- run ./configure.py -c release -a $RPMALLOC_ARCH --lto
- run ${NINJA} $RPMALLOC_LIBPATH
- )
-
- run mkdir -p "$(dirname "$RPMALLOC_LIB")"
- run cp -f "${TMPDIR}/${RPMALLOC_LIBPATH}" "${RPMALLOC_LIB}"
- else
- if [[ ! -f "$RPMALLOC_LIB" ]]; then
- die "rpmalloc library is missing, use --allocator=rpmalloc instead: $RPMALLOC_LIB"
- fi
- fi
-
- echo "Using rpmalloc library: $RPMALLOC_LIB"
-fi
-
-if [[ "${ALLOCATOR}" == "mimalloc" ]]; then
- MIMALLOC_OBJ="${BUILD_CACHE_DIR}/mimalloc.o"
-
- if [[ -z "${ALLOCATOR_LINK_ONLY}" ]]; then
- echo "Rebuilding mimalloc library from scratch"
- MIMALLOC_GIT_URL="https://github.com/microsoft/mimalloc.git"
- MIMALLOC_BRANCH='master'
- MIMALLOC_REVISION='5ac9e36'
- MIALLOC_CMAKE_OPTIONS=(\
- -DCMAKE_BUILD_TYPE=Release \
- -DMI_OVERRIDE=ON \
- -DMI_BUILD_SHARED=OFF \
- -DMI_BUILD_STATIC=OFF \
- -DMI_BUILD_OBJECT=ON \
- -DMI_BUILD_TESTS=OFF \
- )
- if [[ -n "${NINJA}" ]]; then
- MIALLOC_CMAKE_OPTIONS+=(-GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}")
- fi
-
- TMPDIR=$(mktemp -d /tmp/build-mimalloc.XXXXX)
- (
- run cd "$TMPDIR"
- run git init
- run git fetch --tags --quiet "$MIMALLOC_GIT_URL" "$MIMALLOC_BRANCH"
- run git checkout "$MIMALLOC_REVISION"
-
- run export CC CXX AR CFLAGS CXXFLAGS LDFLAGS
- run cmake -B build-cmake "${MIALLOC_CMAKE_OPTIONS[@]}" .
- run cmake --build build-cmake --parallel
- )
-
- run mkdir -p "$(dirname "$MIMALLOC_OBJ")"
- run cp -f "$TMPDIR/build-cmake/mimalloc.o" "$MIMALLOC_OBJ"
- else
- if [[ ! -f "$MIMALLOC_OBJ" ]]; then
- die "mimalloc library is missing, use --allocator=mimalloc: $MIMALLOC_OBJ"
- fi
- fi
-
- echo "Using mimalloc library: $MIMALLOC_OBJ"
- LDFLAGS="$MIMALLOC_OBJ $LDFLAGS"
-fi
-
-if [[ "${ALLOCATOR}" == "jemalloc" ]]; then
- JEMALLOC_LIB="${BUILD_CACHE_DIR}/libjemalloc.a"
-
- if [[ -z "${ALLOCATOR_LINK_ONLY}" ]]; then
- echo "Rebuilding jemalloc from scratch (this may take a minute or more)."
- JEMALLOC_SRC="$(mktemp -d /tmp/build-jemalloc.XXXXXX)"
- JEMALLOC_LOG="${TMPDIR:-/tmp}/jemalloc-build-$$.log"
- echo "Log file at: ${JEMALLOC_LOG}"
- if [[ -z "${JEMALLOC_TAG}" ]]; then
- JEMALLOC_TAG="${DEFAULT_JEMALLOC_TAG}"
- fi
- if [[ -z "$JEMALLOC_GIT_URL" ]]; then
- JEMALLOC_GIT_URL="${DEFAULT_JEMALLOC_GIT_URL}"
- fi
- JEMALLOC_CFLAGS="$CFLAGS -Wno-error"
- JEMALLOC_LDFLAGS="$LDFLAGS"
- if [[ -n "$LTO" ]]; then
- JEMALLOC_CFLAGS="$JEMALLOC_CFLAGS -flto"
- JEMALLOC_LDFLAGS="$JEMALLOC_LDFLAGS -flto"
- fi
- (
- run cd "${JEMALLOC_SRC}"
- run git init
- run git fetch "${JEMALLOC_GIT_URL}" refs/tags/"${JEMALLOC_TAG}" --depth=1
- run git checkout FETCH_HEAD
- CFLAGS="$JEMALLOC_CFLAGS" \
- CXXFLAGS="$JEMALLOC_CFLAGS" \
- LDFLAGS="$JEMALLOC_LDFLAGS" \
- run ./autogen.sh \
- --disable-shared \
- --enable-static \
- --disable-libdl \
- --disable-syscall \
- --disable-stats
- run make -j"$(nproc)" "lib/libjemalloc.a"
- ) > "${JEMALLOC_LOG}" 2>&1
- if [[ "$?" != 0 ]]; then
- die "jemalloc compilation failed, please look at: $JEMALLOC_LOG"
- fi
- run cp "${JEMALLOC_SRC}/lib/libjemalloc.a" "${JEMALLOC_LIB}"
- else
- if [[ ! -f "$JEMALLOC_LIB" ]]; then
- die "jemalloc library is missing, use --allocator=jemalloc: $JEMALLOC_LIB"
- fi
- fi
- echo "Using jemalloc library: $JEMALLOC_LIB"
- LDFLAGS="$JEMALLOC_LIB $LDFLAGS"
-fi
-
-LIBCXX_LDFLAGS="${LIBCXX_FLAGS}"
-LDFLAGS="$LDFLAGS $LIBCXX_LDFLAGS"
-
-# To link Ninja with rpmalloc, a top-level CMakeLists.txt, that includes the
-# Ninja one and adds a few directives, is needed.
-SRC_DIR=$(pwd 2>/dev/null)
-TEST_DIR="${BUILD_DIR}"
-run mkdir -p "${BUILD_DIR}"
-if [[ -z "${INCREMENTAL}" ]]; then
- run rm -rf "${BUILD_DIR}"/*
-fi
-BUILD_BUILD_DIR="${BUILD_DIR}"
-BUILD_SRC_DIR="${SRC_DIR}"
-if [[ -n "${RPMALLOC_LIB}" ]]; then
- BUILD_BUILD_DIR="${BUILD_DIR}/build"
- BUILD_SRC_DIR="${BUILD_DIR}"
- TEST_DIR="${BUILD_BUILD_DIR}/ninja"
- if [[ -z "${INCREMENTAL}" ]]; then
- cat > "${BUILD_DIR}"/CMakeLists.txt <<EOF
-cmake_minimum_required(VERSION 3.15)
-project(ninja-rpmalloc)
-include(CTest)
-add_subdirectory("${SRC_DIR}" ninja)
-target_link_libraries(ninja PRIVATE "${RPMALLOC_LIB}" -lpthread -ldl)
-EOF
- fi
-fi
-if [[ -z "$INCREMENTAL" ]]; then
- run "${CMAKE}" -S"${BUILD_SRC_DIR}" -B"${BUILD_BUILD_DIR}" "${CMAKE_ARGS[@]}"
-fi
-run "${CMAKE}" --build "${BUILD_BUILD_DIR}" --parallel
-if [[ -z "${NO_TESTS}" ]]; then
- if [[ -n "${USE_WINE}" ]]; then
- run "${SCRIPT_DIR}/mingw64-wine.sh" "${TEST_DIR}"/ninja_test.exe
- elif [[ -n "${WINDOWS_SSH}" ]]; then
- # Copy program binaries to Windows host with scp, then run them
- # with ssh. Print the corresponding command for verification.
- IFS=: read -ra _WINDOWS_ARGS <<< "${WINDOWS_SSH}"
- _WINDOWS_HOST="${_WINDOWS_ARGS[0]}"
- _WINDOWS_PORT="${_WINDOWS_ARGS[1]}"
- _WINDOWS_SSH_ARGS=(-t)
- _WINDOWS_SCP_ARGS=()
- if [[ -n "${_WINDOWS_PORT}" ]]; then
- _WINDOWS_SSH_ARGS+=(-p "${_WINDOWS_PORT}")
- _WINDOWS_SCP_ARGS+=(-P "${_WINDOWS_PORT}")
- fi
- # NOTE: Pass all executables because their names change all the time
- _SRC_FILES=("${BUILD_BUILD_DIR}"/*.exe)
- run scp "${_WINDOWS_SCP_ARGS[@]}" "${_SRC_FILES[@]}" "${_WINDOWS_HOST}":
- run ssh "${_WINDOWS_SSH_ARGS[@]}" "${_WINDOWS_HOST}" .\\ninja_test.exe
- else
- # Run the Ninja tests locally.
- # They are run in the test directory, but Python tests are collected from
- # the source tree, omitting those from CMake dependencies such
- # as ./<build_dir>/_deps/googletest-src/.
- #
- # The main problem with them occurs when there are several build directories
- # in the same top-level dir (e.g. `build-cmake/` and `build-mingw64/`
- # because pytest wouldi, by default, collect tests from both directories on
- # the next build, resulting in a very weird Python module import failure!
- mapfile -t PYTHON_TESTS <<< "$(find "${SRC_DIR}" -name '*_test.py' | grep -v _deps/)"
- (run cd "${TEST_DIR}" && run ./ninja_test && run pytest "${PYTHON_TESTS[@]}")
- fi
-fi
-if [[ -n "${INSTALL_PATH}" ]]; then
- run cp "${TEST_DIR}"/ninja "${INSTALL_PATH}"
-fi
diff --git a/misc/fuchsia/mingw64-toolchain.cmake b/misc/fuchsia/mingw64-toolchain.cmake
deleted file mode 100644
index 43ae329..0000000
--- a/misc/fuchsia/mingw64-toolchain.cmake
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2023 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Sample toolchain file for building for Windows from an Ubuntu Linux system.
-#
-# Typical usage:
-# *) install cross compiler: `sudo apt-get install mingw-w64`
-# *) cd build
-# *) cmake -DCMAKE_TOOLCHAIN_FILE=~/mingw-w64-x86_64.cmake ..
-
-set(CMAKE_SYSTEM_NAME Windows)
-set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
-
-# cross compilers to use for C, C++ and Fortran
-set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc-win32)
-set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++-win32)
-set(CMAKE_Fortran_COMPILER ${TOOLCHAIN_PREFIX}-gfortran)
-set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
-
-# target environment on the build host system
-set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
-
-# modify default behavior of FIND_XXX() commands
-set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/misc/fuchsia/mingw64-wine.sh b/misc/fuchsia/mingw64-wine.sh
deleted file mode 100755
index 26de6b9..0000000
--- a/misc/fuchsia/mingw64-wine.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-# Copyright 2023 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.
-
-set -e
-
-# Ensure WINEPATH is set to a value that allows the mingw64-generated
-# binaries to find runtime libraries for libstdc++ and others properly.
-if [[ -z "${WINEPATH}" ]]; then
- export WINEPATH="/usr/x86_64-w64-mingw32/lib/;/usr/lib/gcc/x86_64-w64-mingw32/12-posix"
- echo "Setting WINEPATH=\"${WINEPATH}\""
-fi
-wine "$@"
diff --git a/misc/fuchsia/sync-branch.py b/misc/fuchsia/sync-branch.py
deleted file mode 100755
index f8685bf..0000000
--- a/misc/fuchsia/sync-branch.py
+++ /dev/null
@@ -1,554 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2023 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""General tool to manage sync-branches as described in Fuchsia RFC-0153.
-
-This tool provides several commands that simplify creation and management
-of sync branches. A typical use case would be:
-
- 1) Use the `create` command to create a new local sync-branch, with
- the right cherry-picked commits in it.
-
- 2) Optional: resolve any conflicts that happen during the cherry-pick
- operation launched by the `create` command. Alternatively, it is possible
- to rebase the sync branch, for example to re-order commits or update
- documentation.
-
- 3) Use the `rebase` command to rebase the current sync-branch on top of
- the current upstream reference.
-
- 4) Optional: resolve any conflicts that happen during the rebase operation.
-
- 5) Use the `merge` command to merge the sync branch into the
- main development branch.
-
-Use `<command> --help` for command-specific details.
-
-"""
-
-import argparse
-import json
-import shlex
-import shutil
-import subprocess
-import sys
-import time
-from pathlib import Path
-from typing import Any, Iterable, List, Sequence
-
-_DEFAULT_UPSTREAM_REF = "origin/upstream/master"
-_DEFAULT_DEV_BRANCH = "fuchsia-rfc-0153"
-_DEFAULT_SYNC_BRANCH_NAME = "sync-branch-" + time.strftime("%Y-%m-%d", time.gmtime())
-
-
-_VERBOSE = False
-
-
-def log(msg: str):
- if _VERBOSE:
- print("LOG: " + msg, file=sys.stderr)
-
-
-def cmd_args_to_list(cmd_args: Iterable[Any]) -> Sequence[str]:
- return [str(c) for c in cmd_args]
-
-
-def cmd_quote(cmd_args: Iterable[Any]) -> str:
- return " ".join(shlex.quote(str(c)) for c in cmd_args)
-
-
-def run_command(cmd_args: Iterable[Any], **kwargs) -> subprocess.CompletedProcess:
- log("CMD: " + cmd_quote(cmd_args))
- return subprocess.run(cmd_args_to_list(cmd_args), **kwargs)
-
-
-def get_command_output(cmd_args: Iterable[Any], **kwargs) -> str:
- assert "capture_output" not in kwargs
- assert "text" not in kwargs
- kwargs["capture_output"] = True
- kwargs["text"] = True
- ret = run_command(cmd_args, **kwargs)
- ret.check_returncode()
- return ret.stdout.strip()
-
-
-def read_file(path: Path) -> str:
- with open(path) as f:
- return f.read()
-
-
-def write_file(path: Path, content: str):
- path.parent.mkdir(exist_ok=True, parents=True)
- with open(path, "w") as f:
- f.write(content)
-
-
-class CommandError(Exception):
- pass
-
-
-class GitDirectory(object):
- """A Git directory."""
-
- def __init__(self, git_dir: Path):
- assert git_dir.is_dir(), "Not a directory: %s" % git_dir
- assert (git_dir / ".git").is_dir(), (
- "Not a git directory (missing .git/): %s" % git_dir
- )
- self._git_dir = git_dir
- self._cfg_dir = git_dir / ".git" / "sync_branch"
-
- @property
- def git_dir(self) -> Path:
- return self._git_dir
-
- @property
- def cfg_dir(self) -> Path:
- return self._cfg_dir
-
- @property
- def cfg_file_path(self) -> Path:
- return self._cfg_dir / "config.json"
-
- def cmd(self, args: List[Any]):
- ret = run_command(["git", "-C", self._git_dir] + args)
- ret.check_returncode()
-
- def cmd_output(self, args: List) -> str:
- return get_command_output(["git", "-C", self._git_dir] + args)
-
-
-class PrintingGitDirectory(GitDirectory):
- """A GitDirectory subclass that prints commands instead of running them."""
-
- def __init__(self, parent: GitDirectory):
- self._parent = parent
-
- def cmd(self, args: Iterable[Any]) -> None:
- print(
- "git -C %s %s"
- % (self._parent.git_dir, " ".join(shlex.quote(a) for a in args))
- )
-
-
-class SyncBranchConfig(object):
- def __init__(self, git_dir: GitDirectory):
- self.git_dir = git_dir
- self.upstream_ref: str = _DEFAULT_UPSTREAM_REF
- self.dev_branch_name: str = _DEFAULT_DEV_BRANCH
- self.sync_branch_name: str = _DEFAULT_SYNC_BRANCH_NAME
- self.stem_commit: str = ""
- self.upstream_commit: str = ""
- self.src_commits: List[str] = []
-
- def init_from_create_args(self, args: argparse.Namespace) -> None:
- if args.name:
- self.sync_branch_name = args.name
- if args.upstream_ref:
- self.upstream_ref = args.upstream_ref
- if args.dev_branch:
- self.dev_branch_name = args.dev_branch
-
- # Verify that the branch does not exist yet.
- g = self.git_dir
- has_sync_branch = False
- on_sync_branch = False
- current_branch = g.cmd_output(["branch", "--show-current"])
- if current_branch and current_branch == self.sync_branch_name:
- has_sync_branch = True
- on_sync_branch = True
- else:
- has_sync_branch = bool(
- g.cmd_output(["branch", "--list", self.sync_branch_name])
- )
-
- if has_sync_branch:
- if not args.force:
- raise CommandError(
- "Cannot create new sync-branch over current one, use --force to override!"
- )
- if on_sync_branch:
- g.cmd(["checkout", "--force", self.dev_branch_name])
- g.cmd(["branch", "-D", self.sync_branch_name])
-
- self.upstream_commit = self.git_dir.cmd_output(["rev-parse", self.upstream_ref])
-
- # Compute stem commit, this the first common ancestor of
- # the upstream and development branch. For example in:
- #
- # upstream --A1-------A2---A3
- # \ \
- # \ +--B1"--B2"--C2"
- # \ \ \
- # \ B1'--B2' \
- # \ \ \
- # dev -------------B1---B2---------C1--C2--D1--D2
- #
- # The stem commit is A2
- #
- self.stem_commit = g.cmd_output(
- ["merge-base", self.upstream_ref, self.dev_branch_name]
- ).strip()
-
- if self.stem_commit == self.upstream_commit and not args.force:
- raise CommandError(
- "All upstream commits in current branch, no sync branch needed!"
- )
-
- # Compute source commits
- #
- # To do that, use --ancestry-path to limit the result to
- # commits that belong only to the most recent sync branch
- # is required.
- #
- # For example, without --ancestry-path, a command like
- # `git rev-list STEM..dev` would return the following commits
- # from the previous example history, which contains multiple
- # sync branches originating from the same stem:
- #
- # upstream A2
- # \
- # +--B1"--B2"--C2"
- # \ \
- # B1'--B2' \
- # \ \
- # dev C1--C2--D1--D2
- #
- # By using --ancestry-path=C2", the result is
- # limited to only the commits from the most recent sync branch, i.e.:
- #
- # upstream A2
- # \
- # +--B1"--B2"--C2"
- # \
- # \
- # \
- # dev D1--D2
- #
- # The commit to pass to --ancestry-path is the _second_
- # parent of the most recent merge in the dev branch.
- #
- # To find it, the command below uses `rev-list --merges --format=%P`
- # to print two lines per merge commit that is in the common ancestry
- # of the stem and the dev HEAD.
- #
- # In the example above, the stem is A2, the HEAD is D2, and
- # they share two merge commits C1 and D1, and the commands
- # prints the following:
- #
- # commit <D1>
- # <C2> <C2">
- # commit <C1>
- # <B2> <B2'>
- #
- # Taking the last hash of the second line of output gives us
- # the commit value for --ancestry-path.
- merge_commits_lines = g.cmd_output(
- [
- "rev-list",
- "%s..%s" % (self.upstream_ref, self.dev_branch_name),
- "--merges",
- "--format=%P",
- ]
- ).splitlines()
-
- if len(merge_commits_lines) == 0:
- # No merge commit means we never created a sync branch
- # in the past. In that case, do not use --ancestry-path
- ancestry_path_args = []
- else:
- if len(merge_commits_lines) < 2:
- raise CommandError(
- "Invalid git rev-list output!\n% %s\n"
- % " \n".join(merge_commits_lines)
- )
-
- parents = merge_commits_lines[1].split(" ")
- if len(parents) != 2:
- # We really don't know how to handle merges with more
- # than 2 parents. These were not created by the sync-branch tool.
- raise CommandError(
- "Most recent merge has more than 2 parents!\n %s\n"
- % " \n".join(merge_commits_lines)
- )
-
- ancestry_path_args = ["--ancestry-path=" + parents[1]]
-
- # Now list all the source commits.
- self.src_commits = g.cmd_output(
- [
- "rev-list",
- "--reverse",
- "--no-merges",
- "--pretty=oneline",
- "%s..%s" % (self.stem_commit, self.dev_branch_name),
- ]
- + ancestry_path_args
- ).splitlines()
-
- def has_config_file(self) -> bool:
- return self.git_dir.cfg_file_path.exists()
-
- def read_config_file(self):
- c = json.loads(read_file(self.git_dir.cfg_file_path))
- self.upstream_ref = c["upstream_ref"]
- self.dev_branch_name = c["dev_branch_name"]
- self.sync_branch_name = c["sync_branch_name"]
- self.stem_commit = c["stem_commit"]
- self.src_commits = c["src_commits"]
-
- def write_config_file(self) -> None:
- c = {
- "upstream_ref": self.upstream_ref,
- "dev_branch_name": self.dev_branch_name,
- "sync_branch_name": self.sync_branch_name,
- "stem_commit": self.stem_commit,
- "src_commits": self.src_commits,
- }
- write_file(self.git_dir.cfg_file_path, json.dumps(c, sort_keys=True, indent=2))
-
- def clear_config_file(self) -> None:
- cfg_dir = self.git_dir.cfg_dir
- if cfg_dir.exists():
- shutil.rmtree(cfg_dir)
-
-
-def get_git_directory(args: argparse.Namespace) -> GitDirectory:
- # Compute git directory.
- if args.git_dir:
- git_dir = Path(args.git_dir)
- else:
- git_dir = Path.cwd()
-
- if not git_dir.is_dir():
- raise CommandError("Not a directory: %s" % git_dir)
- if not (git_dir / ".git").is_dir():
- raise CommandError("Not a git directory (missing .git): %s" % git_dir)
-
- return GitDirectory(git_dir)
-
-
-def command_create(args: argparse.Namespace) -> None:
- git_dir = get_git_directory(args)
- sbc = SyncBranchConfig(git_dir)
- sbc.init_from_create_args(args)
- sbc.write_config_file()
-
- if args.print_only:
- git_dir = PrintingGitDirectory(git_dir)
-
- # Create a tag pointing to the stem commit, for debugging.
- git_dir.cmd(["tag", "-f", "SYNC_BRANCH_STEM", sbc.stem_commit])
- git_dir.cmd(["checkout", "-b", sbc.sync_branch_name, sbc.stem_commit])
- git_dir.cmd(["cherry-pick"] + [c.split(" ")[0] for c in sbc.src_commits])
-
-
-def command_rebase(args: argparse.Namespace) -> None:
- git_dir = get_git_directory(args)
- sbc = SyncBranchConfig(git_dir)
- if not sbc.has_config_file():
- raise CommandError("No current sync branch, please use `create` command first!")
-
- sbc.read_config_file()
- if args.print_only:
- git_dir = PrintingGitDirectory(git_dir)
-
- cmd_args = [
- "rebase",
- "--onto",
- sbc.upstream_ref,
- sbc.stem_commit,
- sbc.sync_branch_name,
- ]
- git_dir.cmd(cmd_args)
-
-
-def command_merge(args: argparse.Namespace) -> None:
- git_dir = get_git_directory(args)
- sbc = SyncBranchConfig(git_dir)
- if not sbc.has_config_file():
- raise CommandError("No current sync branch, please use `create` command first!")
-
- sbc.read_config_file()
- if args.print_only:
- git_dir = PrintingGitDirectory(git_dir)
-
- git_dir.cmd(["checkout", sbc.dev_branch_name])
-
- merge_cmd = ["merge", "--no-ff", "-X", "theirs", sbc.sync_branch_name]
- if args.no_commit:
- merge_cmd += ["--no-commit"]
-
- git_dir.cmd(merge_cmd)
-
-
-def main():
- parser = argparse.ArgumentParser(
- description=__doc__, formatter_class=argparse.RawTextHelpFormatter
- )
-
- parser.add_argument("--verbose", action="store_true", help="Enable verbose mode")
- parser.add_argument("--git-dir", help="Specify git directory.")
- parser.add_argument(
- "--print-only",
- action="store_true",
- help="Only print the git commands, do not run them.",
- )
-
- subparsers = parser.add_subparsers(required=True, help="Available commands")
-
- parser_create = subparsers.add_parser(
- "create",
- help="Create new local sync-branch",
- formatter_class=argparse.RawTextHelpFormatter,
- description=r"""
-Create a new local sync branch, which will start at the 'stem' commit that
-is common to both the upstream and development branches, and will include
-all commits between them (as cherry picks).
-
-For example, consider the following initial state:
-
- upstream ---A1--A2--A3
- \
- dev - - - - - B1--B2--B3
-
-The command will create a new branch starting from A2 that contains
-cherry picks of B1..B3, as in:
-
- sync-branch B1'--B2'--B3'
- /
- upstream ---A1--A2--A3
- \
- dev - - - - - B1--B2--B3
-
-Note that the cherry-pick command launched by `create` may fail in case of
-rare conflicts. If this happens, just fix the issue manually then use
-`git cherry-pick --continue` to complete the operation,
-
-After the cherry pick has completed, the user is free to rebase the branch,
-for example to reorder commits, or update documentation, before invoking
-the `rebase` command.
-
-Note that `create` will not work if a local sync branch already exists, or
-when upstream commits are already reachable in the developement branch.
-These conditions can be overriden by using the `--force` option.
-
-""",
- )
-
- parser_create.add_argument(
- "--force", action="store_true", help="Discard any existing sync branch."
- )
- parser_create.add_argument(
- "--name",
- default=_DEFAULT_SYNC_BRANCH_NAME,
- help=f"Specify sync branch name (default {_DEFAULT_SYNC_BRANCH_NAME})",
- )
- parser_create.add_argument(
- "--upstream-ref",
- default=_DEFAULT_UPSTREAM_REF,
- help=f"Specify upstream branch reference (default {_DEFAULT_UPSTREAM_REF})",
- )
- parser_create.add_argument(
- "--dev-branch",
- default=_DEFAULT_DEV_BRANCH,
- help=f"Specify development branch (default {_DEFAULT_DEV_BRANCH})",
- )
- parser_create.set_defaults(func=command_create)
-
- parser_rebase = subparsers.add_parser(
- "rebase",
- help="Rebase current sync-branch on top of upstream.",
- formatter_class=argparse.RawTextHelpFormatter,
- description=r"""
-Rebase the current sync branch on top of the current upstream commit.
-This must be called after the completion of a `create` operation.
-
-For example consider the following state resulting from a previous
-`create` operation:
-
- sync-branch B1'--B2'--B3'
- /
- upstream ---A1--A2--A3
- \
- dev - - - - - B1--B2--B3
-
-After the rebase, the sync branch should be something like:
-
- sync-branch B1''--B2''--B3''
- /
- upstream ---A1--A2--A3
- \
- dev - - - - - B1--B2--B3
-
-Where both B3 and B3'' correspond to the new desired sources.
-
-After the rebase, the user is free to inspect the branch, rebase it or do
-any necessary cleanups, and verify that everything still works as expected.
-
-The user will then launch the `merge` command to merge the result into the
-main development branch.
-
-""",
- )
- parser_rebase.set_defaults(func=command_rebase)
-
- parser_merge = subparsers.add_parser(
- "merge",
- help="Merge sync-branch to main development branch.",
- formatter_class=argparse.RawTextHelpFormatter,
- description=r"""
-Merge the current sync branch into the main development branch. This should
-only happen after a successful `rebase` command invocation.
-
-For example consider the following state resulting from a previous
-`rebase` operation:
-
- sync-branch B1''--B2''--B3''
- /
- upstream ---A1--A2--A3
- \
- dev - - - - - B1--B2--B3
-
-The end result will be:
-
- upstream ---A1--A2--A3
- \
- sync-branch B1''--B2''--B3''
- \
- dev - - - - - B1--B2--B3-----------C1
-
-Where C1 is the new merge commit on the main development branch.
-
-Note that A3 will become the stem commit for future 'create'
-operations.
-
-""",
- )
- parser_merge.add_argument(
- "--no-commit", action="store_true", help="Do not commit the merge."
- )
- parser_merge.set_defaults(func=command_merge)
-
- args = parser.parse_args()
-
- if args.verbose:
- global _VERBOSE
- _VERBOSE = True
-
- try:
- args.func(args)
- except CommandError as e:
- print(str(e), file=sys.stderr)
- return 1
- except subprocess.CalledProcessError as e:
- print("%s:\n%s\n" % (e, e.stderr), file=sys.stderr)
- return e.returncode
-
- return 0
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/misc/inherited-fds.ninja b/misc/inherited-fds.ninja
deleted file mode 100644
index 671155e..0000000
--- a/misc/inherited-fds.ninja
+++ /dev/null
@@ -1,23 +0,0 @@
-# This build file prints out a list of open file descriptors in
-# Ninja subprocesses, to help verify we don't accidentally leak
-# any.
-
-# Because one fd leak was in the code managing multiple subprocesses,
-# this test brings up multiple subprocesses and then dumps the fd
-# table of the last one.
-
-# Use like: ./ninja -f misc/inherited-fds.ninja
-
-rule sleep
- command = sleep 10000
-
-rule dump
- command = sleep 1; ls -l /proc/self/fd; exit 1
-
-build all: phony a b c d e
-
-build a: sleep
-build b: sleep
-build c: sleep
-build d: sleep
-build e: dump
diff --git a/misc/long-slow-build.ninja b/misc/long-slow-build.ninja
deleted file mode 100644
index 46af6ba..0000000
--- a/misc/long-slow-build.ninja
+++ /dev/null
@@ -1,38 +0,0 @@
-# An input file for running a "slow" build.
-# Use like: ninja -f misc/long-slow-build.ninja all
-
-rule sleep
- command = sleep 1
- description = SLEEP $out
-
-build 0: sleep README
-build 1: sleep README
-build 2: sleep README
-build 3: sleep README
-build 4: sleep README
-build 5: sleep README
-build 6: sleep README
-build 7: sleep README
-build 8: sleep README
-build 9: sleep README
-build 10: sleep 0
-build 11: sleep 1
-build 12: sleep 2
-build 13: sleep 3
-build 14: sleep 4
-build 15: sleep 5
-build 16: sleep 6
-build 17: sleep 7
-build 18: sleep 8
-build 19: sleep 9
-build 20: sleep 10
-build 21: sleep 11
-build 22: sleep 12
-build 23: sleep 13
-build 24: sleep 14
-build 25: sleep 15
-build 26: sleep 16
-build 27: sleep 17
-build 28: sleep 18
-build 29: sleep 19
-build all: phony 20 21 22 23 24 25 26 27 28 29
diff --git a/misc/manifest_fuzzer.cc b/misc/manifest_fuzzer.cc
deleted file mode 100644
index 0e1261a..0000000
--- a/misc/manifest_fuzzer.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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/measure.py b/misc/measure.py
deleted file mode 100755
index f3825ef..0000000
--- a/misc/measure.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python3
-
-# 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.
-
-"""measure the runtime of a command by repeatedly running it.
-"""
-
-import time
-import subprocess
-import sys
-
-devnull = open('/dev/null', 'w')
-
-def run(cmd, repeat=10):
- print('sampling:', end=' ')
- sys.stdout.flush()
-
- samples = []
- for _ in range(repeat):
- start = time.time()
- subprocess.call(cmd, stdout=devnull, stderr=devnull)
- end = time.time()
- dt = (end - start) * 1000
- print('%dms' % int(dt), end=' ')
- sys.stdout.flush()
- samples.append(dt)
- print()
-
- # We're interested in the 'pure' runtime of the code, which is
- # conceptually the smallest time we'd see if we ran it enough times
- # such that it got the perfect time slices / disk cache hits.
- best = min(samples)
- # Also print how varied the outputs were in an attempt to make it
- # more obvious if something has gone terribly wrong.
- err = sum(s - best for s in samples) / float(len(samples))
- print('estimate: %dms (mean err %.1fms)' % (best, err))
-
-if __name__ == '__main__':
- if len(sys.argv) < 2:
- print('usage: measure.py command args...')
- sys.exit(1)
- run(cmd=sys.argv[1:])
diff --git a/misc/ninja-mode.el b/misc/ninja-mode.el
deleted file mode 100644
index 8b975d5..0000000
--- a/misc/ninja-mode.el
+++ /dev/null
@@ -1,85 +0,0 @@
-;;; ninja-mode.el --- Major mode for editing .ninja files -*- lexical-binding: t -*-
-
-;; Package-Requires: ((emacs "24"))
-
-;; 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.
-
-;;; Commentary:
-
-;; Simple emacs mode for editing .ninja files.
-;; Just some syntax highlighting for now.
-
-;;; Code:
-
-(defvar ninja-keywords
- `((,(concat "^" (regexp-opt '("rule" "build" "subninja" "include"
- "pool" "default")
- 'words))
- . font-lock-keyword-face)
- ("\\([[:alnum:]_]+\\) =" 1 font-lock-variable-name-face)
- ;; Variable expansion.
- ("$[[:alnum:]_]+" . font-lock-variable-name-face)
- ("${[[:alnum:]._]+}" . font-lock-variable-name-face)
- ;; Rule names
- ("rule +\\([[:alnum:]_.-]+\\)" 1 font-lock-function-name-face)
- ;; Build Statement - highlight the rule used,
- ;; allow for escaped $,: in outputs.
- ("build +\\(?:[^:$\n]\\|$[:$]\\)+ *: *\\([[:alnum:]_.-]+\\)"
- 1 font-lock-function-name-face)))
-
-(defvar ninja-mode-syntax-table
- (let ((table (make-syntax-table)))
- (modify-syntax-entry ?\" "." table)
- table)
- "Syntax table used in `ninja-mode'.")
-
-(defun ninja-syntax-propertize (start end)
- (save-match-data
- (goto-char start)
- (while (search-forward "#" end t)
- (let ((match-pos (match-beginning 0)))
- (when (and
- ;; Is it the first non-white character on the line?
- (eq match-pos (save-excursion (back-to-indentation) (point)))
- (save-excursion
- (goto-char (line-end-position 0))
- (or
- ;; If we're continuing the previous line, it's not a
- ;; comment.
- (not (eq ?$ (char-before)))
- ;; Except if the previous line is a comment as well, as the
- ;; continuation dollar is ignored then.
- (nth 4 (syntax-ppss)))))
- (put-text-property match-pos (1+ match-pos) 'syntax-table '(11))
- (let ((line-end (line-end-position)))
- ;; Avoid putting properties past the end of the buffer.
- ;; Otherwise we get an `args-out-of-range' error.
- (unless (= line-end (1+ (buffer-size)))
- (put-text-property line-end (1+ line-end) 'syntax-table '(12)))))))))
-
-;;;###autoload
-(define-derived-mode ninja-mode prog-mode "ninja"
- (set (make-local-variable 'comment-start) "#")
- (set (make-local-variable 'parse-sexp-lookup-properties) t)
- (set (make-local-variable 'syntax-propertize-function) #'ninja-syntax-propertize)
- (setq font-lock-defaults '(ninja-keywords)))
-
-;; Run ninja-mode for files ending in .ninja.
-;;;###autoload
-(add-to-list 'auto-mode-alist '("\\.ninja$" . ninja-mode))
-
-(provide 'ninja-mode)
-
-;;; ninja-mode.el ends here
diff --git a/misc/ninja.vim b/misc/ninja.vim
deleted file mode 100644
index c1ffd50..0000000
--- a/misc/ninja.vim
+++ /dev/null
@@ -1,87 +0,0 @@
-" ninja build file syntax.
-" Language: ninja build file as described at
-" http://ninja-build.org/manual.html
-" Version: 1.5
-" Last Change: 2018/04/05
-" Maintainer: Nicolas Weber <nicolasweber@gmx.de>
-" Version 1.4 of this script is in the upstream vim repository and will be
-" included in the next vim release. If you change this, please send your change
-" upstream.
-
-" ninja lexer and parser are at
-" https://github.com/ninja-build/ninja/blob/master/src/lexer.in.cc
-" https://github.com/ninja-build/ninja/blob/master/src/manifest_parser.cc
-
-if exists("b:current_syntax")
- finish
-endif
-
-let s:cpo_save = &cpo
-set cpo&vim
-
-syn case match
-
-" Comments are only matched when the # is at the beginning of the line (with
-" optional whitespace), as long as the prior line didn't end with a $
-" continuation.
-syn match ninjaComment /\(\$\n\)\@<!\_^\s*#.*$/ contains=@Spell
-
-" Toplevel statements are the ones listed here and
-" toplevel variable assignments (ident '=' value).
-" lexer.in.cc, ReadToken() and manifest_parser.cc, Parse()
-syn match ninjaKeyword "^build\>"
-syn match ninjaKeyword "^rule\>"
-syn match ninjaKeyword "^pool\>"
-syn match ninjaKeyword "^default\>"
-syn match ninjaKeyword "^include\>"
-syn match ninjaKeyword "^subninja\>"
-
-" Both 'build' and 'rule' begin a variable scope that ends
-" on the first line without indent. 'rule' allows only a
-" limited set of magic variables, 'build' allows general
-" let assignments.
-" manifest_parser.cc, ParseRule()
-syn region ninjaRule start="^rule" end="^\ze\S" contains=TOP transparent
-syn keyword ninjaRuleCommand contained containedin=ninjaRule command
- \ deps depfile description generator
- \ pool restat rspfile rspfile_content
-
-syn region ninjaPool start="^pool" end="^\ze\S" contains=TOP transparent
-syn keyword ninjaPoolCommand contained containedin=ninjaPool depth
-
-" Strings are parsed as follows:
-" lexer.in.cc, ReadEvalString()
-" simple_varname = [a-zA-Z0-9_-]+;
-" varname = [a-zA-Z0-9_.-]+;
-" $$ -> $
-" $\n -> line continuation
-" '$ ' -> escaped space
-" $simple_varname -> variable
-" ${varname} -> variable
-
-syn match ninjaDollar "\$\$"
-syn match ninjaWrapLineOperator "\$$"
-syn match ninjaSimpleVar "\$[a-zA-Z0-9_-]\+"
-syn match ninjaVar "\${[a-zA-Z0-9_.-]\+}"
-
-" operators are:
-" variable assignment =
-" rule definition :
-" implicit dependency |
-" order-only dependency ||
-syn match ninjaOperator "\(=\|:\||\|||\)\ze\s"
-
-hi def link ninjaComment Comment
-hi def link ninjaKeyword Keyword
-hi def link ninjaRuleCommand Statement
-hi def link ninjaPoolCommand Statement
-hi def link ninjaDollar ninjaOperator
-hi def link ninjaWrapLineOperator ninjaOperator
-hi def link ninjaOperator Operator
-hi def link ninjaSimpleVar ninjaVar
-hi def link ninjaVar Identifier
-
-let b:current_syntax = "ninja"
-
-let &cpo = s:cpo_save
-unlet s:cpo_save
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
deleted file mode 100644
index ca73b5b..0000000
--- a/misc/ninja_syntax.py
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/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
-just a helpful utility for build-file-generation systems that already
-use Python.
-"""
-
-import re
-import textwrap
-
-def escape_path(word):
- return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
-
-class Writer(object):
- def __init__(self, output, width=78):
- self.output = output
- self.width = width
-
- def newline(self):
- self.output.write('\n')
-
- def comment(self, text):
- for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
- break_on_hyphens=False):
- self.output.write('# ' + line + '\n')
-
- def variable(self, key, value, indent=0):
- if value is None:
- return
- if isinstance(value, list):
- value = ' '.join(filter(None, value)) # Filter out empty strings.
- self._line('%s = %s' % (key, value), indent)
-
- def pool(self, name, depth):
- self._line('pool %s' % name)
- self.variable('depth', depth, indent=1)
-
- def rule(self, name, command, description=None, depfile=None,
- generator=False, pool=None, restat=False, rspfile=None,
- rspfile_content=None, deps=None):
- self._line('rule %s' % name)
- self.variable('command', command, indent=1)
- if description:
- self.variable('description', description, indent=1)
- if depfile:
- self.variable('depfile', depfile, indent=1)
- if generator:
- self.variable('generator', '1', indent=1)
- if pool:
- self.variable('pool', pool, indent=1)
- if restat:
- self.variable('restat', '1', indent=1)
- if rspfile:
- self.variable('rspfile', rspfile, indent=1)
- if rspfile_content:
- self.variable('rspfile_content', rspfile_content, indent=1)
- if deps:
- self.variable('deps', deps, indent=1)
-
- def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
- variables=None, implicit_outputs=None, pool=None, dyndep=None):
- outputs = as_list(outputs)
- out_outputs = [escape_path(x) for x in outputs]
- all_inputs = [escape_path(x) for x in as_list(inputs)]
-
- if implicit:
- implicit = [escape_path(x) for x in as_list(implicit)]
- all_inputs.append('|')
- all_inputs.extend(implicit)
- if order_only:
- order_only = [escape_path(x) for x in as_list(order_only)]
- all_inputs.append('||')
- all_inputs.extend(order_only)
- if implicit_outputs:
- implicit_outputs = [escape_path(x)
- for x in as_list(implicit_outputs)]
- out_outputs.append('|')
- out_outputs.extend(implicit_outputs)
-
- self._line('build %s: %s' % (' '.join(out_outputs),
- ' '.join([rule] + all_inputs)))
- if pool is not None:
- self._line(' pool = %s' % pool)
- if dyndep is not None:
- self._line(' dyndep = %s' % dyndep)
-
- if variables:
- if isinstance(variables, dict):
- iterator = iter(variables.items())
- else:
- iterator = iter(variables)
-
- for key, val in iterator:
- self.variable(key, val, indent=1)
-
- return outputs
-
- def include(self, path):
- self._line('include %s' % path)
-
- def subninja(self, path):
- self._line('subninja %s' % path)
-
- def default(self, paths):
- self._line('default %s' % ' '.join(as_list(paths)))
-
- def _count_dollars_before_index(self, s, i):
- """Returns the number of '$' characters right in front of s[i]."""
- dollar_count = 0
- dollar_index = i - 1
- while dollar_index > 0 and s[dollar_index] == '$':
- dollar_count += 1
- dollar_index -= 1
- return dollar_count
-
- def _line(self, text, indent=0):
- """Write 'text' word-wrapped at self.width characters."""
- leading_space = ' ' * indent
- while len(leading_space) + len(text) > self.width:
- # The text is too wide; wrap if possible.
-
- # Find the rightmost space that would obey our width constraint and
- # that's not an escaped space.
- available_space = self.width - len(leading_space) - len(' $')
- space = available_space
- while True:
- space = text.rfind(' ', 0, space)
- if (space < 0 or
- self._count_dollars_before_index(text, space) % 2 == 0):
- break
-
- if space < 0:
- # No such space; just use the first unescaped space we can find.
- space = available_space - 1
- while True:
- space = text.find(' ', space + 1)
- if (space < 0 or
- self._count_dollars_before_index(text, space) % 2 == 0):
- break
- if space < 0:
- # Give up on breaking.
- break
-
- self.output.write(leading_space + text[0:space] + ' $\n')
- text = text[space+1:]
-
- # Subsequent lines are continuations, so indent them.
- leading_space = ' ' * (indent+2)
-
- self.output.write(leading_space + text + '\n')
-
- def close(self):
- self.output.close()
-
-
-def as_list(input):
- if input is None:
- return []
- if isinstance(input, list):
- return input
- return [input]
-
-
-def escape(string):
- """Escape a string such that it can be embedded into a Ninja file without
- further interpretation."""
- assert '\n' not in string, 'Ninja syntax does not allow newlines'
- # We only have one special metacharacter: '$'.
- return string.replace('$', '$$')
-
-
-def expand(string, vars, local_vars={}):
- """Expand a string containing $vars as Ninja would.
-
- Note: doesn't handle the full Ninja variable syntax, but it's enough
- to make configure.py's use of it work.
- """
- def exp(m):
- var = m.group(1)
- if var == '$':
- return '$'
- return local_vars.get(var, vars.get(var, ''))
- return re.sub(r'\$(\$|\w*)', exp, string)
diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py
deleted file mode 100755
index 61fb177..0000000
--- a/misc/ninja_syntax_test.py
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/usr/bin/env python3
-
-# 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.
-
-import unittest
-
-try:
- from StringIO import StringIO
-except ImportError:
- from io import StringIO
-
-import ninja_syntax
-
-LONGWORD = 'a' * 10
-LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5
-INDENT = ' '
-
-class TestLineWordWrap(unittest.TestCase):
- def setUp(self):
- self.out = StringIO()
- self.n = ninja_syntax.Writer(self.out, width=8)
-
- def test_single_long_word(self):
- # We shouldn't wrap a single long word.
- self.n._line(LONGWORD)
- self.assertEqual(LONGWORD + '\n', self.out.getvalue())
-
- def test_few_long_words(self):
- # We should wrap a line where the second word is overlong.
- self.n._line(' '.join(['x', LONGWORD, 'y']))
- self.assertEqual(' $\n'.join(['x',
- INDENT + LONGWORD,
- INDENT + 'y']) + '\n',
- self.out.getvalue())
-
- def test_comment_wrap(self):
- # Filenames should not be wrapped
- self.n.comment('Hello /usr/local/build-tools/bin')
- self.assertEqual('# Hello\n# /usr/local/build-tools/bin\n',
- self.out.getvalue())
-
- def test_short_words_indented(self):
- # Test that indent is taking into account when breaking subsequent lines.
- # The second line should not be ' to tree', as that's longer than the
- # test layout width of 8.
- self.n._line('line_one to tree')
- self.assertEqual('''\
-line_one $
- to $
- tree
-''',
- self.out.getvalue())
-
- def test_few_long_words_indented(self):
- # Check wrapping in the presence of indenting.
- self.n._line(' '.join(['x', LONGWORD, 'y']), indent=1)
- self.assertEqual(' $\n'.join([' ' + 'x',
- ' ' + INDENT + LONGWORD,
- ' ' + INDENT + 'y']) + '\n',
- self.out.getvalue())
-
- def test_escaped_spaces(self):
- self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y']))
- self.assertEqual(' $\n'.join(['x',
- INDENT + LONGWORDWITHSPACES,
- INDENT + 'y']) + '\n',
- self.out.getvalue())
-
- def test_fit_many_words(self):
- self.n = ninja_syntax.Writer(self.out, width=78)
- self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1)
- self.assertEqual('''\
- command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $
- ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $
- ../out/Debug/gen/chrome/theme_resources_large.pak
-''',
- self.out.getvalue())
-
- def test_leading_space(self):
- self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
- self.n.variable('foo', ['', '-bar', '-somethinglong'], 0)
- self.assertEqual('''\
-foo = -bar $
- -somethinglong
-''',
- self.out.getvalue())
-
- def test_embedded_dollar_dollar(self):
- self.n = ninja_syntax.Writer(self.out, width=15) # force wrapping
- self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
- self.assertEqual('''\
-foo = a$$b $
- -somethinglong
-''',
- self.out.getvalue())
-
- def test_two_embedded_dollar_dollars(self):
- self.n = ninja_syntax.Writer(self.out, width=17) # force wrapping
- self.n.variable('foo', ['a$$b', '-somethinglong'], 0)
- self.assertEqual('''\
-foo = a$$b $
- -somethinglong
-''',
- self.out.getvalue())
-
- def test_leading_dollar_dollar(self):
- self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
- self.n.variable('foo', ['$$b', '-somethinglong'], 0)
- self.assertEqual('''\
-foo = $$b $
- -somethinglong
-''',
- self.out.getvalue())
-
- def test_trailing_dollar_dollar(self):
- self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping
- self.n.variable('foo', ['a$$', '-somethinglong'], 0)
- self.assertEqual('''\
-foo = a$$ $
- -somethinglong
-''',
- self.out.getvalue())
-
-class TestBuild(unittest.TestCase):
- def setUp(self):
- self.out = StringIO()
- self.n = ninja_syntax.Writer(self.out)
-
- def test_variables_dict(self):
- self.n.build('out', 'cc', 'in', variables={'name': 'value'})
- self.assertEqual('''\
-build out: cc in
- name = value
-''',
- self.out.getvalue())
-
- def test_variables_list(self):
- self.n.build('out', 'cc', 'in', variables=[('name', 'value')])
- self.assertEqual('''\
-build out: cc in
- name = value
-''',
- self.out.getvalue())
-
- def test_implicit_outputs(self):
- self.n.build('o', 'cc', 'i', implicit_outputs='io')
- self.assertEqual('''\
-build o | io: cc i
-''',
- self.out.getvalue())
-
-class TestExpand(unittest.TestCase):
- def test_basic(self):
- vars = {'x': 'X'}
- self.assertEqual('foo', ninja_syntax.expand('foo', vars))
-
- def test_var(self):
- vars = {'xyz': 'XYZ'}
- self.assertEqual('fooXYZ', ninja_syntax.expand('foo$xyz', vars))
-
- def test_vars(self):
- vars = {'x': 'X', 'y': 'YYY'}
- self.assertEqual('XYYY', ninja_syntax.expand('$x$y', vars))
-
- def test_space(self):
- vars = {}
- self.assertEqual('x y z', ninja_syntax.expand('x$ y$ z', vars))
-
- def test_locals(self):
- vars = {'x': 'a'}
- local_vars = {'x': 'b'}
- self.assertEqual('a', ninja_syntax.expand('$x', vars))
- self.assertEqual('b', ninja_syntax.expand('$x', vars, local_vars))
-
- def test_double(self):
- self.assertEqual('a b$c', ninja_syntax.expand('a$ b$$c', {}))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/misc/oss-fuzz/build.sh b/misc/oss-fuzz/build.sh
deleted file mode 100644
index 4328feb..0000000
--- a/misc/oss-fuzz/build.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/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
deleted file mode 100644
index 7b513be..0000000
--- a/misc/oss-fuzz/sample_ninja_build
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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
deleted file mode 100755
index 18c84d9..0000000
--- a/misc/output_test.py
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/env python3
-
-"""Runs ./ninja and checks if the output is correct.
-
-In order to simulate a smart terminal it uses the 'script' command.
-"""
-
-import os
-import platform
-import subprocess
-import sys
-import tempfile
-import unittest
-
-default_env = dict(os.environ)
-for varname in ('NINJA_STATUS', 'NINJA_STATUS_MAX_COMMANDS', 'NINJA_PERSISTENT_MODE', 'CLICOLOR_FORCE'):
- if varname in default_env:
- del default_env[varname]
-
-default_env['TERM'] = ''
-NINJA_PATH = os.path.abspath('./ninja')
-
-def run(build_ninja, flags='', pipe=False, env=default_env):
- with tempfile.TemporaryDirectory() as d:
- with open(os.path.join(d, '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, cwd=d, env=env)
- elif platform.system() == 'Darwin':
- output = subprocess.check_output(['script', '-q', '/dev/null', 'bash', '-c', ninja_cmd],
- cwd=d, env=env)
- else:
- output = subprocess.check_output(['script', '-qfec', ninja_cmd, '/dev/null'],
- cwd=d, env=env)
- except subprocess.CalledProcessError as err:
- sys.stdout.buffer.write(err.output)
- raise err
- final_output = ''
- for line in output.decode('utf-8').splitlines(True):
- if len(line) > 0 and line[-1] == '\r':
- continue
- 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):
- BUILD_SIMPLE_ECHO = '\n'.join((
- 'rule echo',
- ' command = printf "do thing"',
- ' description = echo $out',
- '',
- 'build a: echo',
- '',
- ))
-
- def test_issue_1418(self):
- self.assertEqual(run(
-'''rule echo
- command = sleep $delay && echo $out
- description = echo $out
-
-build a: echo
- delay = 3
-build b: echo
- delay = 2
-build c: echo
- delay = 1
-''', '-j3'),
-'''[1/3] echo c\x1b[K
-c
-[2/3] echo b\x1b[K
-b
-[3/3] echo a\x1b[K
-a
-''')
-
- def test_issue_1214(self):
- print_red = '''rule echo
- command = printf '\x1b[31mred\x1b[0m'
- description = echo $out
-
-build a: echo
-'''
- # Only strip color when ninja's output is piped.
- self.assertEqual(run(print_red),
-'''[1/1] echo a\x1b[K
-\x1b[31mred\x1b[0m
-''')
- self.assertEqual(run(print_red, pipe=True),
-'''[1/1] echo a
-red
-''')
- # Even in verbose mode, colors should still only be stripped when piped.
- self.assertEqual(run(print_red, flags='-v'),
-'''[1/1] printf '\x1b[31mred\x1b[0m'
-\x1b[31mred\x1b[0m
-''')
- self.assertEqual(run(print_red, flags='-v', pipe=True),
-'''[1/1] printf '\x1b[31mred\x1b[0m'
-red
-''')
-
- # CLICOLOR_FORCE=1 can be used to disable escape code stripping.
- env = default_env.copy()
- env['CLICOLOR_FORCE'] = '1'
- self.assertEqual(run(print_red, pipe=True, env=env),
-'''[1/1] echo a
-\x1b[31mred\x1b[0m
-''')
-
- def test_issue_1966(self):
- self.assertEqual(run(
-'''rule cat
- command = cat $rspfile $rspfile > $out
- rspfile = cat.rsp
- rspfile_content = a b c
-
-build a: cat
-''', '-j3'),
-'''[1/1] cat cat.rsp cat.rsp > a\x1b[K
-''')
-
-
- 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')
- self.assertEqual(run('', pipe=True), 'ninja: no work to do.\n')
-
- def test_ninja_status_default(self):
- 'Do we show the default status by default?'
- self.assertEqual(run(Output.BUILD_SIMPLE_ECHO), '[1/1] echo a\x1b[K\ndo thing\n')
-
- def test_ninja_status_quiet(self):
- 'Do we suppress the status information when --quiet is specified?'
- output = run(Output.BUILD_SIMPLE_ECHO, flags='--quiet')
- self.assertEqual(output, 'do thing\n')
-
- def test_entering_directory_on_stdout(self):
- output = run(Output.BUILD_SIMPLE_ECHO, flags='-C$PWD', pipe=True)
- self.assertEqual(output.splitlines()[0][:25], "ninja: Entering directory")
-
- def test_tool_inputs(self):
- plan = '''
-rule cat
- command = cat $in $out
-build out1 : cat in1
-build out2 : cat in2 out1
-build out3 : cat out2 out1 | implicit || order_only
-'''
- self.assertEqual(run(plan, flags='-t inputs out3'),
-'''implicit
-in1
-in2
-order_only
-out1
-out2
-''')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec
deleted file mode 100644
index 36e5181..0000000
--- a/misc/packaging/ninja.spec
+++ /dev/null
@@ -1,42 +0,0 @@
-Summary: Ninja is a small build system with a focus on speed.
-Name: ninja
-Version: %{ver}
-Release: %{rel}%{?dist}
-Group: Development/Tools
-License: Apache 2.0
-URL: https://github.com/ninja-build/ninja
-Source0: %{name}-%{version}-%{rel}.tar.gz
-BuildRoot: %{_tmppath}/%{name}-%{version}-%{rel}
-
-BuildRequires: asciidoc
-
-%description
-Ninja is yet another build system. It takes as input the interdependencies of files (typically source code and output executables) and
-orchestrates building them, quickly.
-
-Ninja joins a sea of other build systems. Its distinguishing goal is to be fast. It is born from my work on the Chromium browser project,
-which has over 30,000 source files and whose other build systems (including one built from custom non-recursive Makefiles) can take ten
-seconds to start building after changing one file. Ninja is under a second.
-
-%prep
-%setup -q -n %{name}-%{version}-%{rel}
-
-%build
-echo Building..
-./configure.py --bootstrap
-./ninja manual
-
-%install
-mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_docdir}
-cp -p ninja %{buildroot}%{_bindir}/
-
-%files
-%defattr(-, root, root)
-%doc COPYING README.md doc/manual.html
-%{_bindir}/*
-
-%clean
-rm -rf %{buildroot}
-
-#The changelog is built automatically from Git history
-%changelog
diff --git a/misc/packaging/rpmbuild.sh b/misc/packaging/rpmbuild.sh
deleted file mode 100755
index 9b74c65..0000000
--- a/misc/packaging/rpmbuild.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-echo Building ninja RPMs..
-GITROOT=$(git rev-parse --show-toplevel)
-cd $GITROOT
-
-VER=1.0
-REL=$(git rev-parse --short HEAD)git
-RPMTOPDIR=$GITROOT/rpm-build
-echo "Ver: $VER, Release: $REL"
-
-# Create tarball
-mkdir -p $RPMTOPDIR/{SOURCES,SPECS}
-git archive --format=tar --prefix=ninja-${VER}-${REL}/ HEAD | gzip -c > $RPMTOPDIR/SOURCES/ninja-${VER}-${REL}.tar.gz
-
-# Convert git log to RPM's ChangeLog format (shown with rpm -qp --changelog <rpm file>)
-sed -e "s/%{ver}/$VER/" -e "s/%{rel}/$REL/" misc/packaging/ninja.spec > $RPMTOPDIR/SPECS/ninja.spec
-git log --format="* %cd %aN%n- (%h) %s%d%n" --date=local | sed -r 's/[0-9]+:[0-9]+:[0-9]+ //' >> $RPMTOPDIR/SPECS/ninja.spec
-
-# Build SRC and binary RPMs
-rpmbuild --quiet \
- --define "_topdir $RPMTOPDIR" \
- --define "_rpmdir $PWD" \
- --define "_srcrpmdir $PWD" \
- --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \
- -ba $RPMTOPDIR/SPECS/ninja.spec &&
-
-rm -rf $RPMTOPDIR &&
-echo Done
diff --git a/misc/persistent_mode_test.py b/misc/persistent_mode_test.py
deleted file mode 100755
index f526d99..0000000
--- a/misc/persistent_mode_test.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/env python3
-
-"""Runs ./ninja in persistent mode and checks if the output is correct.
-
-In order to simulate a smart terminal it uses the 'script' command.
-"""
-import os
-import platform
-import subprocess
-import sys
-import tempfile
-import unittest
-
-default_env = dict(os.environ)
-for varname in ("NINJA_STATUS", "NINJA_STATUS_MAX_COMMANDS", "CLICOLOR_FORCE"):
- if varname in default_env:
- del default_env[varname]
-
-default_env["TERM"] = ""
-default_env["NINJA_PERSISTENT_MODE"] = "1"
-default_env["DEBUG_PERSISTENT_SERVICE_LOG_FILE"] = "/tmp/DLOG"
-default_env["NINJA_PERSISTENT_LOG_FILE"] = "/tmp/ELOG"
-NINJA_PATH = os.path.abspath("./ninja")
-
-
-class NinjaPersistentInstance(object):
- def __init__(self, build_ninja: str):
- """Initialize instance.
-
- This creates a temporary directory, cd to it, then writes a build.ninja
- plan in it that will be used by future run() calls.
-
- Args:
- build_ninja: The content of a "build.ninja" file
- """
- self._server_running = False
- self._tempdir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
- self._dir = self._tempdir.name
- self.write_build_ninja(build_ninja)
-
- def write_build_ninja(self, build_ninja):
- """Rewrite the build.ninja file with new content."""
- self.write_file("build.ninja", build_ninja)
-
- def write_file(self, path, content):
- with open(os.path.join(self._dir, path), "w") as f:
- f.write(content)
- f.flush()
-
- def close(self):
- """Close the instance, ensuring the persistent server is stopped."""
- if self._server_running:
- self.run(flags="-t server stop")
- self._server_running = False
-
- if self._tempdir:
- self._tempdir.cleanup()
- self._tempdir = None
-
- def server_status(self):
- return self.run(flags="-t server status")
-
- def has_server(self):
- status = self.server_status()
- return status == f"server is running for {self._dir}\n"
-
- def server_pid(self):
- """Return PID of server process, or -1 if there is none."""
- return int(self.run(flags="-t server pid").strip())
-
- def run(self, flags="", pipe=False, env=default_env):
- """Run a Ninja command."""
- self._server_running = True
- ninja_cmd = "{} {}".format(NINJA_PATH, flags).strip()
- try:
- if pipe:
- output = subprocess.check_output(
- ninja_cmd, shell=True, cwd=self._dir, env=env
- )
- elif platform.system() == "Darwin":
- output = subprocess.check_output(
- ["script", "-q", "/dev/null", "bash", "-c", ninja_cmd],
- cwd=self._dir,
- env=env,
- )
- else:
- output = subprocess.check_output(
- ["script", "-qfec", ninja_cmd, "/dev/null"], cwd=self._dir, env=env
- )
- except subprocess.CalledProcessError as err:
- sys.stdout.buffer.write(err.output)
- raise err
-
- final_output = ""
- for line in output.decode("utf-8").splitlines(True):
- if len(line) > 0 and line[-1] == "\r":
- continue
- final_output += line.replace("\r", "")
- return final_output
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.close()
- return False
-
-
-@unittest.skipIf(
- platform.system() == "Windows", "These test methods do not work on Windows"
-)
-class Output(unittest.TestCase):
- BUILD_SIMPLE_ECHO = "\n".join(
- (
- "rule echo",
- ' command = printf "do thing"',
- " description = echo $out",
- "",
- "build a: echo",
- "",
- )
- )
-
- def test_start_persistent_server(self):
- with NinjaPersistentInstance(Output.BUILD_SIMPLE_ECHO) as ninja:
- self.assertFalse(ninja.has_server())
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- server_pid = ninja.server_pid()
- self.assertNotEqual(server_pid, -1)
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- self.assertEqual(server_pid, ninja.server_pid())
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- self.assertEqual(server_pid, ninja.server_pid())
-
- def test_stop_persistent_server(self):
- with NinjaPersistentInstance(Output.BUILD_SIMPLE_ECHO) as ninja:
- self.assertFalse(ninja.has_server())
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
-
- server_pid = ninja.server_pid()
- self.assertNotEqual(server_pid, -1)
-
- ninja.run(flags="-t server stop")
- self.assertFalse(ninja.has_server())
- self.assertEqual(ninja.server_pid(), -1)
-
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- new_server_pid = ninja.server_pid()
- self.assertNotEqual(new_server_pid, -1)
- self.assertNotEqual(new_server_pid, server_pid)
-
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- self.assertEqual(new_server_pid, ninja.server_pid())
-
- def test_ninja_status(self):
- env = default_env.copy()
- env["NINJA_STATUS"] = "STATUS [%f/%t] "
- with NinjaPersistentInstance(Output.BUILD_SIMPLE_ECHO) as ninja:
- self.assertFalse(ninja.has_server())
- non_persistent_env = env.copy()
- non_persistent_env["NINJA_PERSISTENT_MODE"] = "0"
- self.assertEqual(
- ninja.run(env=non_persistent_env),
- "STATUS [1/1] echo a\x1b[K\ndo thing\n",
- )
- self.assertFalse(ninja.has_server())
-
- env["NINJA_PERSISTENT_STATUS"] = "1"
- self.assertEqual(
- ninja.run(env=env), "STATUS [1/1] echo a\x1b[K\ndo thing\n"
- )
- self.assertTrue(ninja.has_server())
-
- env["NINJA_STATUS"] = "NOPE"
- self.assertEqual(ninja.run(env=env), "NOPEecho a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
-
- def test_environment_variables(self):
- build_ninja = """
-rule print_var
- command = printf "%s=[%s]" $varname $$$varname
- description = print $varname
-
-build foo: print_var
- varname = foo
-"""
- env = default_env.copy()
- if "foo" in env:
- del env["foo"]
-
- with NinjaPersistentInstance(build_ninja) as ninja:
- self.assertFalse(ninja.has_server())
- self.assertEqual(ninja.run(env=env), "[1/1] print foo\x1b[K\nfoo=[]\n")
- self.assertTrue(ninja.has_server())
- self.assertEqual(ninja.run(env=env), "[1/1] print foo\x1b[K\nfoo=[]\n")
- self.assertTrue(ninja.has_server())
- env["foo"] = "FOO"
- self.assertEqual(ninja.run(env=env), "[1/1] print foo\x1b[K\nfoo=[FOO]\n")
- self.assertTrue(ninja.has_server())
- del env["foo"]
- self.assertEqual(ninja.run(env=env), "[1/1] print foo\x1b[K\nfoo=[]\n")
- self.assertTrue(ninja.has_server())
-
- def test_server_restart_on_input_file_change(self):
- with NinjaPersistentInstance(Output.BUILD_SIMPLE_ECHO) as ninja:
- self.assertFalse(ninja.has_server())
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
-
- ninja.write_build_ninja(Output.BUILD_SIMPLE_ECHO)
- server_pid = ninja.server_pid()
-
- self.assertEqual(ninja.run(), "[1/1] echo a\x1b[K\ndo thing\n")
- self.assertTrue(ninja.has_server())
- self.assertNotEqual(server_pid, ninja.server_pid())
-
- def test_server_restart_on_generator_run(self):
- build_ninja = """
-rule echo
- command = printf "do thing"
- description = echo $out
-
-build a: echo
-
-rule regen
- command = cp input.depfile $out.d && touch build.ninja
- depfile = $out.d
- generator = 1
-
-build build.ninja: regen
-"""
- with NinjaPersistentInstance(build_ninja) as ninja:
- # Launch the server.
- ninja.write_file("CMakeLists.txt", "# Fake original config file")
- ninja.write_file("input.depfile", "build.ninja: CMakeLists.txt")
- self.assertFalse(ninja.has_server())
- ninja.run(flags="a")
- self.assertTrue(ninja.has_server())
- server_pid = ninja.server_pid()
-
- ninja.write_file("CMakeLists.txt", "# Fake update to config file")
- ninja.run(flags="a")
- self.assertTrue(ninja.has_server())
- self.assertNotEqual(ninja.server_pid(), server_pid)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/misc/write_fake_manifests.py b/misc/write_fake_manifests.py
deleted file mode 100755
index bf9cf7d..0000000
--- a/misc/write_fake_manifests.py
+++ /dev/null
@@ -1,272 +0,0 @@
-#!/usr/bin/env python3
-
-"""Writes large manifest files, for manifest parser performance testing.
-
-The generated manifest files are (eerily) similar in appearance and size to the
-ones used in the Chromium project.
-
-Usage:
- python misc/write_fake_manifests.py outdir # Will run for about 5s.
-
-The program contains a hardcoded random seed, so it will generate the same
-output every time it runs. By changing the seed, it's easy to generate many
-different sets of manifest files.
-"""
-
-import argparse
-import contextlib
-import os
-import random
-import sys
-
-import ninja_syntax
-
-
-def paretoint(avg, alpha):
- """Returns a random integer that's avg on average, following a power law.
- alpha determines the shape of the power curve. alpha has to be larger
- than 1. The closer alpha is to 1, the higher the variation of the returned
- numbers."""
- return int(random.paretovariate(alpha) * avg / (alpha / (alpha - 1)))
-
-
-# Based on http://neugierig.org/software/chromium/class-name-generator.html
-def moar(avg_options, p_suffix):
- kStart = ['render', 'web', 'browser', 'tab', 'content', 'extension', 'url',
- 'file', 'sync', 'content', 'http', 'profile']
- kOption = ['view', 'host', 'holder', 'container', 'impl', 'ref',
- 'delegate', 'widget', 'proxy', 'stub', 'context',
- 'manager', 'master', 'watcher', 'service', 'file', 'data',
- 'resource', 'device', 'info', 'provider', 'internals', 'tracker',
- 'api', 'layer']
- kOS = ['win', 'mac', 'aura', 'linux', 'android', 'unittest', 'browsertest']
- num_options = min(paretoint(avg_options, alpha=4), 5)
- # The original allows kOption to repeat as long as no consecutive options
- # repeat. This version doesn't allow any option repetition.
- name = [random.choice(kStart)] + random.sample(kOption, num_options)
- if random.random() < p_suffix:
- name.append(random.choice(kOS))
- return '_'.join(name)
-
-
-class GenRandom(object):
- def __init__(self, src_dir):
- self.seen_names = set([None])
- self.seen_defines = set([None])
- self.src_dir = src_dir
-
- def _unique_string(self, seen, avg_options=1.3, p_suffix=0.1):
- s = None
- while s in seen:
- s = moar(avg_options, p_suffix)
- seen.add(s)
- return s
-
- def _n_unique_strings(self, n):
- seen = set([None])
- return [self._unique_string(seen, avg_options=3, p_suffix=0.4)
- for _ in range(n)]
-
- def target_name(self):
- return self._unique_string(p_suffix=0, seen=self.seen_names)
-
- def path(self):
- return os.path.sep.join([
- self._unique_string(self.seen_names, avg_options=1, p_suffix=0)
- for _ in range(1 + paretoint(0.6, alpha=4))])
-
- def src_obj_pairs(self, path, name):
- num_sources = paretoint(55, alpha=2) + 1
- return [(os.path.join(self.src_dir, path, s + '.cc'),
- os.path.join('obj', path, '%s.%s.o' % (name, s)))
- for s in self._n_unique_strings(num_sources)]
-
- def defines(self):
- return [
- '-DENABLE_' + self._unique_string(self.seen_defines).upper()
- for _ in range(paretoint(20, alpha=3))]
-
-
-LIB, EXE = 0, 1
-class Target(object):
- def __init__(self, gen, kind):
- self.name = gen.target_name()
- self.dir_path = gen.path()
- self.ninja_file_path = os.path.join(
- 'obj', self.dir_path, self.name + '.ninja')
- self.src_obj_pairs = gen.src_obj_pairs(self.dir_path, self.name)
- if kind == LIB:
- self.output = os.path.join('lib' + self.name + '.a')
- elif kind == EXE:
- self.output = os.path.join(self.name)
- self.defines = gen.defines()
- self.deps = []
- self.kind = kind
- self.has_compile_depends = random.random() < 0.4
-
-
-def write_target_ninja(ninja, target, src_dir):
- compile_depends = None
- if target.has_compile_depends:
- compile_depends = os.path.join(
- 'obj', target.dir_path, target.name + '.stamp')
- ninja.build(compile_depends, 'stamp', target.src_obj_pairs[0][0])
- ninja.newline()
-
- ninja.variable('defines', target.defines)
- ninja.variable('includes', '-I' + src_dir)
- ninja.variable('cflags', ['-Wall', '-fno-rtti', '-fno-exceptions'])
- ninja.newline()
-
- for src, obj in target.src_obj_pairs:
- ninja.build(obj, 'cxx', src, implicit=compile_depends)
- ninja.newline()
-
- deps = [dep.output for dep in target.deps]
- libs = [dep.output for dep in target.deps if dep.kind == LIB]
- if target.kind == EXE:
- ninja.variable('libs', libs)
- if sys.platform == "darwin":
- ninja.variable('ldflags', '-Wl,-pie')
- link = { LIB: 'alink', EXE: 'link'}[target.kind]
- ninja.build(target.output, link, [obj for _, obj in target.src_obj_pairs],
- implicit=deps)
-
-
-def write_sources(target, root_dir):
- need_main = target.kind == EXE
-
- includes = []
-
- # Include siblings.
- for cc_filename, _ in target.src_obj_pairs:
- h_filename = os.path.basename(os.path.splitext(cc_filename)[0] + '.h')
- includes.append(h_filename)
-
- # Include deps.
- for dep in target.deps:
- for cc_filename, _ in dep.src_obj_pairs:
- h_filename = os.path.basename(
- os.path.splitext(cc_filename)[0] + '.h')
- includes.append("%s/%s" % (dep.dir_path, h_filename))
-
- for cc_filename, _ in target.src_obj_pairs:
- cc_path = os.path.join(root_dir, cc_filename)
- h_path = os.path.splitext(cc_path)[0] + '.h'
- namespace = os.path.basename(target.dir_path)
- class_ = os.path.splitext(os.path.basename(cc_filename))[0]
- try:
- os.makedirs(os.path.dirname(cc_path))
- except OSError:
- pass
-
- with open(h_path, 'w') as f:
- f.write('namespace %s { struct %s { %s(); }; }' % (namespace,
- class_, class_))
- with open(cc_path, 'w') as f:
- for include in includes:
- f.write('#include "%s"\n' % include)
- f.write('\n')
- f.write('namespace %s { %s::%s() {} }' % (namespace,
- class_, class_))
-
- if need_main:
- f.write('int main(int argc, char **argv) {}\n')
- need_main = False
-
-def write_master_ninja(master_ninja, targets):
- """Writes master build.ninja file, referencing all given subninjas."""
- master_ninja.variable('cxx', 'c++')
- master_ninja.variable('ld', '$cxx')
- if sys.platform == 'darwin':
- master_ninja.variable('alink', 'libtool -static')
- else:
- master_ninja.variable('alink', 'ar rcs')
- master_ninja.newline()
-
- master_ninja.pool('link_pool', depth=4)
- master_ninja.newline()
-
- master_ninja.rule('cxx', description='CXX $out',
- command='$cxx -MMD -MF $out.d $defines $includes $cflags -c $in -o $out',
- depfile='$out.d', deps='gcc')
- master_ninja.rule('alink', description='ARCHIVE $out',
- command='rm -f $out && $alink -o $out $in')
- master_ninja.rule('link', description='LINK $out', pool='link_pool',
- command='$ld $ldflags -o $out $in $libs')
- master_ninja.rule('stamp', description='STAMP $out', command='touch $out')
- master_ninja.newline()
-
- for target in targets:
- master_ninja.subninja(target.ninja_file_path)
- master_ninja.newline()
-
- master_ninja.comment('Short names for targets.')
- for target in targets:
- if target.name != target.output:
- master_ninja.build(target.name, 'phony', target.output)
- master_ninja.newline()
-
- master_ninja.build('all', 'phony', [target.output for target in targets])
- master_ninja.default('all')
-
-
-@contextlib.contextmanager
-def FileWriter(path):
- """Context manager for a ninja_syntax object writing to a file."""
- try:
- os.makedirs(os.path.dirname(path))
- except OSError:
- pass
- f = open(path, 'w')
- yield ninja_syntax.Writer(f)
- f.close()
-
-
-def random_targets(num_targets, src_dir):
- gen = GenRandom(src_dir)
-
- # N-1 static libraries, and 1 executable depending on all of them.
- targets = [Target(gen, LIB) for i in range(num_targets - 1)]
- for i in range(len(targets)):
- targets[i].deps = [t for t in targets[0:i] if random.random() < 0.05]
-
- last_target = Target(gen, EXE)
- last_target.deps = targets[:]
- last_target.src_obj_pairs = last_target.src_obj_pairs[0:10] # Trim.
- targets.append(last_target)
- return targets
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('-s', '--sources', nargs="?", const="src",
- help='write sources to directory (relative to output directory)')
- parser.add_argument('-t', '--targets', type=int, default=1500,
- help='number of targets (default: 1500)')
- parser.add_argument('-S', '--seed', type=int, help='random seed',
- default=12345)
- parser.add_argument('outdir', help='output directory')
- args = parser.parse_args()
- root_dir = args.outdir
-
- random.seed(args.seed)
-
- do_write_sources = args.sources is not None
- src_dir = args.sources if do_write_sources else "src"
-
- targets = random_targets(args.targets, src_dir)
- for target in targets:
- with FileWriter(os.path.join(root_dir, target.ninja_file_path)) as n:
- write_target_ninja(n, target, src_dir)
-
- if do_write_sources:
- write_sources(target, root_dir)
-
- with FileWriter(os.path.join(root_dir, 'build.ninja')) as master_ninja:
- master_ninja.width = 120
- write_master_ninja(master_ninja, targets)
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/misc/zsh-completion b/misc/zsh-completion
deleted file mode 100644
index d439df3..0000000
--- a/misc/zsh-completion
+++ /dev/null
@@ -1,75 +0,0 @@
-#compdef ninja
-# 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.
-
-# Add the following to your .zshrc to tab-complete ninja targets
-# fpath=(path/to/ninja/misc/zsh-completion $fpath)
-
-(( $+functions[_ninja-get-targets] )) || _ninja-get-targets() {
- dir="."
- if [ -n "${opt_args[-C]}" ];
- then
- eval dir="${opt_args[-C]}"
- fi
- file="build.ninja"
- if [ -n "${opt_args[-f]}" ];
- then
- eval file="${opt_args[-f]}"
- fi
- targets_command="ninja -f \"${file}\" -C \"${dir}\" -t targets all"
- eval ${targets_command} 2>/dev/null | cut -d: -f1
-}
-
-(( $+functions[_ninja-get-tools] )) || _ninja-get-tools() {
- # remove the first line; remove the leading spaces; replace spaces with colon
- ninja -t list 2> /dev/null | sed -e '1d;s/^ *//;s/ \+/:/'
-}
-
-(( $+functions[_ninja-get-modes] )) || _ninja-get-modes() {
- # remove the first line; remove the last line; remove the leading spaces; replace spaces with colon
- ninja -d list 2> /dev/null | sed -e '1d;$d;s/^ *//;s/ \+/:/'
-}
-
-(( $+functions[_ninja-modes] )) || _ninja-modes() {
- local -a modes
- modes=(${(fo)"$(_ninja-get-modes)"})
- _describe 'modes' modes
-}
-
-(( $+functions[_ninja-tools] )) || _ninja-tools() {
- local -a tools
- tools=(${(fo)"$(_ninja-get-tools)"})
- _describe 'tools' tools
-}
-
-(( $+functions[_ninja-targets] )) || _ninja-targets() {
- local -a targets
- targets=(${(fo)"$(_ninja-get-targets)"})
- _describe 'targets' targets
-}
-
-_arguments \
- '(- *)'{-h,--help}'[Show help]' \
- '(- *)--version[Print ninja version]' \
- '-C+[Change to directory before doing anything else]:directories:_directories' \
- '-f+[Specify input build file (default=build.ninja)]:files:_files' \
- '-j+[Run N jobs in parallel (default=number of CPUs available)]:number of jobs' \
- '-l+[Do not start new jobs if the load average is greater than N]:number of jobs' \
- '-k+[Keep going until N jobs fail (default=1)]:number of jobs' \
- '-n[Dry run (do not run commands but act like they succeeded)]' \
- '(-v --verbose --quiet)'{-v,--verbose}'[Show all command lines while building]' \
- "(-v --verbose --quiet)--quiet[Don't show progress status, just command output]" \
- '-d+[Enable debugging (use -d list to list modes)]:modes:_ninja-modes' \
- '-t+[Run a subtool (use -t list to list subtools)]:tools:_ninja-tools' \
- '*::targets:_ninja-targets'
diff --git a/src/async_loop-posix.h b/src/async_loop-posix.h
deleted file mode 100644
index 0bc517e..0000000
--- a/src/async_loop-posix.h
+++ /dev/null
@@ -1,840 +0,0 @@
-// Copyright 2023 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_ASYNC_LOOP_POSIX_H
-#define NINJA_ASYNC_LOOP_POSIX_H
-
-// Posix implementation of the AsyncLoop class.
-// This contains specialized code paths for Linux ppoll() and MacOS kqueue()
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
-#include <signal.h>
-#include <stddef.h>
-#include <string.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#ifdef USE_KQUEUE
-#include <sys/event.h>
-#endif
-
-#include <algorithm>
-#include <unordered_map>
-
-#include "async_loop.h"
-#include "async_loop_timers.h"
-#include "interrupt_handling.h"
-#include "metrics.h" // For GetTimeMillis()
-#include "util.h" // For Fatal() and Win32Fatal().
-
-#ifdef USE_KQUEUE
-// The know state of a kqueue read or write filter for a given file descriptor.
-// Unknown means that the kernel queue does not know about this filter yet.
-enum class KqueueFilterState {
- Unknown = 0,
- Disabled,
- Enabled,
-};
-#endif // USE_KQUEUE
-
-class AsyncHandle::State {
- public:
- enum class Kind {
- None = 0,
- Read,
- Write,
- Connect,
- Accept,
- };
-
- // Constructors.
- explicit State(AsyncLoop& async_loop) : async_loop_(async_loop) {}
-
- State(IpcHandle handle, AsyncLoop& async_loop,
- AsyncHandle::Callback&& callback)
- : fd_(std::move(handle)), callback_(std::move(callback)),
- async_loop_(async_loop) {
- if (fd_)
- async_loop_.AttachHandle(this);
- }
-
- // Destructor. Note that this type is unmoveable due to Win32
- // requirements.
- ~State() {
- if (fd_)
- async_loop_.DetachHandle(this);
- }
-
- // Disallow copy operations. Even though this is implied by
- // the use of moveable-only fields like |fd_|.
- State(const State&) = delete;
- State& operator=(const State&) = delete;
-
- // Disallow move operations. The address of these instances are recorded
- // in the |AsyncLoop::Impl::watches_| data structure.
- State(State&&) noexcept = delete;
- State& operator=(State&&) noexcept = delete;
-
- // Return AsyncLoop instance this instance belongs to.
- AsyncLoop& async_loop() const { return async_loop_; }
-
- // Return AsyncLoop implementation instance.
- AsyncLoop::Impl& loop() const { return *async_loop_.impl_; }
-
- // Return file descriptor.
- int fd() const { return fd_.native_handle(); }
- int native_handle() const { return fd(); }
-
- // Return true if the handle for this instnace is valid.
- bool is_valid() const { return !!fd_; }
-
- // Release the native handle for this instance, making it invalid.
- int ReleaseHandle() {
- Reset(Kind::None);
- if (fd_)
- async_loop_.DetachHandle(this);
- return fd_.ReleaseNativeHandle();
- }
-
- // Is an asynchronous operation running?
- bool IsRunning() const { return kind_ != Kind::None && !completed_; }
-
- // Cancel current asynchronous operation, if any.
- void Cancel() {
- if (kind_ != Kind::None) {
- async_loop_.CancelHandle(this);
- Reset(Kind::None);
- }
- }
-
- // Reset the callback.
- void ResetCallback(AsyncHandle::Callback&& cb) { callback_ = std::move(cb); }
-
- void ResetHandle(IpcHandle handle) {
- Reset(Kind::None);
- if (fd_)
- async_loop_.DetachHandle(this);
- fd_ = std::move(handle);
- if (fd_)
- async_loop_.AttachHandle(this);
- }
-
- bool NeedsEvent() const { return !completed_ && kind_ != Kind::None; }
-
- bool NeedsWriteEvent() const {
- return !completed_ && (kind_ == Kind::Write || kind_ == Kind::Connect);
- }
-
- bool NeedsReadEvent() const {
- return !completed_ && (kind_ == Kind::Read || kind_ == Kind::Accept);
- }
-
- short poll_events() const {
- if (completed_)
- return 0;
- if (kind_ == Kind::Write || kind_ == Kind::Connect)
- return POLLOUT;
- if (kind_ == Kind::Read || kind_ == Kind::Accept)
- return POLLIN | POLLPRI;
- return 0;
- }
-
- short poll_revents() const { return poll_events() | POLLERR | POLLHUP; }
-
- // Reset state to start a new asynchronous operation. Cancels current one
- // if any.
- void Reset(Kind kind = Kind::None) {
- kind_ = kind;
- completed_ = false;
- error_ = 0;
- buffer_ = nullptr;
- wanted_size_ = 0;
- actual_size_ = 0;
- accept_handle_.Close();
- }
-
- void StartRead(void* buffer, size_t size) {
- Reset(Kind::Read);
- buffer_ = buffer;
- wanted_size_ = size;
- if (!size)
- completed_ = true;
- else
- async_loop().UpdateHandle(this);
- }
-
- void StartWrite(const void* buffer, size_t size) {
- Reset(Kind::Write);
- buffer_ = const_cast<void*>(buffer);
- wanted_size_ = size;
- if (!size)
- completed_ = true;
- else
- async_loop().UpdateHandle(this);
- }
-
- void StartConnect() {
- Reset(Kind::Connect);
- async_loop().UpdateHandle(this);
- }
-
- void StartAccept() {
- Reset(Kind::Accept);
- async_loop().UpdateHandle(this);
- }
-
- bool OnEvent() {
- error_ = 0;
- actual_size_ = 0;
-
- assert(!completed_);
- completed_ = true;
-
- switch (kind_) {
- case Kind::Read: {
- int ret;
- do {
- ret = ::read(fd_.native_handle(), buffer_, wanted_size_);
- } while (ret < 0 && errno == EINTR);
- if (ret < 0) {
- error_ = errno;
- if (error_ == EAGAIN || error_ == EWOULDBLOCK) {
- // A spurious wakeup happened.
- return false;
- }
- } else {
- actual_size_ = static_cast<size_t>(ret);
- }
- break;
- }
-
- case Kind::Write: {
- int ret;
- do {
- ret = ::write(fd_.native_handle(), buffer_, wanted_size_);
- } while (ret < 0 && errno == EINTR);
- if (ret < 0) {
- error_ = errno;
- if (error_ == EAGAIN || error_ == EWOULDBLOCK) {
- // A spurious wakeup happened.
- return false;
- }
- } else {
- actual_size_ = static_cast<size_t>(ret);
- }
- break;
- }
-
- case Kind::Connect: {
- int so_error =
- IpcHandle::GetNativeAsyncConnectStatus(fd_.native_handle());
- if (so_error != 0) {
- if (so_error == EAGAIN || so_error == EINPROGRESS)
- return false;
-
- error_ = so_error;
- }
- break;
- }
-
- case Kind::Accept: {
- int ret;
- do {
- ret = ::accept(fd_.native_handle(), nullptr, 0);
- } while (ret < 0 && errno == EINTR);
- if (ret < 0) {
- error_ = errno;
- if (error_ == EAGAIN || error_ == EWOULDBLOCK)
- return false;
- } else {
- accept_handle_ = IpcHandle(ret);
- // NOTE: On Linux, client handle does not inherit O_NONBLOCK
- // but it will on BSDs, so portable programs should set the
- // flag explicitly. TODO(digit).
- }
- break;
- }
-
- default:
- assert(false && "Invalid runtime async op type!");
- return false;
- }
- async_loop_.UpdateHandle(this);
- callback_(error_, actual_size_);
- return true;
- }
-
- IpcHandle TakeAcceptedHandle() { return std::move(accept_handle_); }
-
- IpcHandle fd_;
- Kind kind_ = Kind::None;
- bool completed_ = false;
-#ifdef USE_KQUEUE
- // These values are used exclusively by AsyncLoop::Impl::Watches
- KqueueFilterState kqueue_read_filter_ = KqueueFilterState::Unknown;
- KqueueFilterState kqueue_write_filter_ = KqueueFilterState::Unknown;
-#endif // USE_KQUEUE
- int error_ = 0;
- void* buffer_ = nullptr;
- size_t wanted_size_ = 0;
- size_t actual_size_ = 0;
- AsyncHandle::Callback callback_;
- IpcHandle accept_handle_;
- AsyncLoop& async_loop_;
-};
-
-class AsyncLoop::Impl {
- /// The list of pending AsyncHandle::States that have completed but whose
- /// Invoke() method wasn't called yet. Identified by their address.
- struct PendingList : public std::vector<AsyncHandle::State*> {
- // Remove one async state from the list.
- bool Remove(AsyncHandle::State* astate) {
- for (auto it = begin(); it != end(); ++it) {
- if (*it == astate) {
- // Order does not matter for this list, so just move last item
- // to current location to avoid O(n) removal cost.
- auto it_last = end() - 1;
- if (it != it_last)
- *it = *it_last;
- pop_back();
- return true;
- }
- }
- return false;
- }
- };
-
- PendingList pending_ops_;
-
-#ifdef USE_KQUEUE
- class Watches {
- IpcHandle queue_ = -1;
- std::vector<AsyncHandle::State*> states_;
- std::vector<struct kevent> events_;
-
- static constexpr size_t npos = ~size_t(0);
-
- size_t FindState(AsyncHandle::State* state) const {
- size_t result = 0;
- for (auto it = states_.begin(); it != states_.end(); ++it, ++result) {
- if (*it == state)
- return result;
- }
- return npos;
- }
-
- void InsertIntoQueue(AsyncHandle::State* state) {
- // Do not create any filter yet, these will be added on demand
- // during WaitForEvents().
- state->kqueue_read_filter_ = KqueueFilterState::Unknown;
- state->kqueue_write_filter_ = KqueueFilterState::Unknown;
- }
-
- void RemoveFromQueue(AsyncHandle::State* state) {
- // Remove the filters directly, instead of delaying the operation
- // to WaitForEvents().
- struct kevent events[2];
- int count = 0;
- if (state->kqueue_read_filter_ != KqueueFilterState::Unknown) {
- events[count++] = {
- static_cast<uintptr_t>(state->fd()),
- EVFILT_READ,
- EV_DELETE,
- 0,
- 0,
- state,
- };
- }
- if (state->kqueue_write_filter_ != KqueueFilterState::Unknown) {
- events[count++] = {
- static_cast<uintptr_t>(state->fd()),
- EVFILT_WRITE,
- EV_DELETE,
- 0,
- 0,
- state,
- };
- }
- if (count > 0) {
- int ret = kevent(queue_.native_handle(), events, count, NULL, 0, NULL);
- if (ret < 0)
- ErrnoFatal("kevent.Remove");
- }
- }
-
- public:
- Watches() : queue_(kqueue()) {
- if (!queue_)
- ErrnoFatal("kqueue()");
- }
-
- ~Watches() {
- assert(states_.empty() &&
- "Destroying AsyncLoop before child AsyncHandle instances!");
- }
-
- bool HasWaiters() const {
- for (const auto* state : states_) {
- if (state->IsRunning())
- return true;
- }
- return false;
- }
-
- void Insert(AsyncHandle::State* state) {
- assert(FindState(state) == npos && "Async state already in the set!");
- states_.push_back(state);
- InsertIntoQueue(state);
- }
-
- void Remove(AsyncHandle::State* state) {
- size_t state_pos = FindState(state);
- assert(state_pos != npos && "Async state not in the set!");
- RemoveFromQueue(state);
- // Order is not important
- states_[state_pos] = states_.back();
- states_.pop_back();
- }
-
- void Update(AsyncHandle::State* state) {
- assert(FindState(state) != npos && "Updating unknown async state!");
- // Do not do anything, changes will be computed on
- // demand in the next WaitForEvents() call.
- }
-
- int WaitForEvents(const struct timespec* ts, PendingList& pending_ops) {
- events_.resize(2 * states_.size());
-
- // Compute the changes to send to the kernel.
- int num_changes = 0;
- int num_events = 0;
-
- // Helper function to compute the set of flags to apply to
- // a given Kqueue filter. Return 0 if there is no change to apply.
- // Also updates num_events.
- auto check_filter = [&num_events](KqueueFilterState& filter,
- bool event_needed) -> uint16_t {
- uint16_t flags = 0;
- if (event_needed) {
- num_events++;
- if (filter != KqueueFilterState::Enabled) {
- flags = EV_ENABLE | EV_CLEAR |
- ((filter == KqueueFilterState::Unknown) ? EV_ADD : 0);
- filter = KqueueFilterState::Enabled;
- }
- } else if (filter == KqueueFilterState::Enabled) {
- flags = EV_DELETE;
- filter = KqueueFilterState::Disabled;
- }
- return flags;
- };
-
- for (auto* state : states_) {
- uint16_t flags =
- check_filter(state->kqueue_read_filter_, state->NeedsReadEvent());
- if (flags) {
- events_[num_changes++] = {
- static_cast<uintptr_t>(state->fd()),
- EVFILT_READ,
- flags,
- 0,
- 0,
- state,
- };
- }
-
- flags =
- check_filter(state->kqueue_write_filter_, state->NeedsWriteEvent());
- if (flags) {
- events_[num_changes++] = {
- static_cast<uintptr_t>(state->fd()),
- EVFILT_WRITE,
- flags,
- 0,
- 0,
- state,
- };
- }
- }
-
- struct kevent* events = &events_.front();
- int ret = kevent(queue_.native_handle(), events, num_changes, events,
- events_.size(), ts);
- if (ret < 0) {
- if (errno != EINTR)
- ErrnoFatal("kevent.Wait");
- return -1;
- }
-
- // kevent() always returns immediately if the size of the events array
- // is 0, even if there is a timeout so use pselect() instead.
- if (ret == 0 && num_events == 0) {
- int ret = pselect(0, NULL, NULL, NULL, ts, NULL);
- if (ret < 0 && errno != EINTR)
- ErrnoFatal("pselect");
- return ret;
- }
-
- struct kevent* event = &events_.front();
- for (int n = 0; n < ret; ++n, ++event) {
- auto* state = static_cast<AsyncHandle::State*>(event->udata);
- assert(state);
- assert(static_cast<uintptr_t>(state->fd()) == event->ident);
-
- // Do not try to interpret EV_ERROR here, assume that the OnError()
- // method will retry the syscall and get the error from it.
-
- if (event->filter == EVFILT_READ) {
- assert(state->NeedsReadEvent());
- assert(state->kqueue_read_filter_ == KqueueFilterState::Enabled);
- state->kqueue_read_filter_ = KqueueFilterState::Disabled;
- pending_ops.push_back(state);
- } else if (event->filter == EVFILT_WRITE) {
- assert(state->NeedsWriteEvent());
- assert(state->kqueue_write_filter_ == KqueueFilterState::Enabled);
- state->kqueue_write_filter_ = KqueueFilterState::Disabled;
- pending_ops.push_back(state);
- }
- }
- return ret;
- }
- };
-#elif defined(USE_PPOLL)
- class Watches {
- // Use two parallel vectors, where polls_[n] is an entry matching
- // states_[n], since only one async operation can exist for each file
- // descriptor. They must be separate because the address of polls_.front()
- // will be passed to the kernel for ppoll(), which expects a contiguous
- // array of pollfd items in memory.
- std::vector<AsyncHandle::State*> states_;
- std::vector<pollfd> polls_;
-
- size_t FindState(AsyncHandle::State* state) {
- size_t result = 0;
- for (auto* s : states_) {
- if (s == state)
- return result;
- ++result;
- }
- return npos;
- }
-
- public:
- static constexpr size_t npos = ~size_t(0);
-
- ~Watches() {
- assert(states_.empty() &&
- "Destroying AsyncLoop before child AsyncHandle instances!");
- }
-
- bool HasWaiters() const {
- for (const auto& poll : polls_) {
- if (poll.events != 0)
- return true;
- }
- return false;
- }
-
- void Insert(AsyncHandle::State* state) {
- assert(FindState(state) == npos && "Async state already in the set!");
- states_.push_back(state);
- polls_.push_back({ state->fd(), state->poll_events(), 0 });
- }
-
- void Remove(AsyncHandle::State* state) {
- size_t state_pos = FindState(state);
- assert(state_pos != npos && "Async state not in the set!");
- // Order is not important
- size_t last_pos = states_.size() - 1;
- if (state_pos != last_pos) {
- states_[state_pos] = states_[last_pos];
- polls_[state_pos] = polls_[last_pos];
- }
- states_.pop_back();
- polls_.pop_back();
- }
-
- void Update(AsyncHandle::State* state) {
- size_t state_pos = FindState(state);
- assert(state_pos != npos && "Updating unknown async state!");
- polls_[state_pos].events = state->poll_events();
- }
-
- int WaitForEvents(const struct timespec* ts, PendingList& pending_ops) {
- int ret = ppoll(&polls_.front(), static_cast<nfds_t>(polls_.size()), ts,
- nullptr);
- if (ret < 0) {
- if (errno != EINTR)
- ErrnoFatal("ppoll");
- } else if (ret > 0) {
- auto cur_poll = polls_.begin();
- auto cur_state = states_.begin();
- for (; cur_poll != polls_.end(); ++cur_poll, ++cur_state) {
- AsyncHandle::State* state = *cur_state;
- assert(state->fd() == cur_poll->fd);
- if (cur_poll->revents & state->poll_revents())
- pending_ops.push_back(state);
- }
- }
- return ret;
- }
- };
-
-#else // !USE_KQUEUE && !USE_PPOLL
- class Watches {
- static constexpr int kInvalid = -2;
- mutable int max_fds_ = kInvalid;
-
- using ListType = std::vector<AsyncHandle::State*>;
- ListType states_;
- fd_set read_fds_;
- fd_set write_fds_;
- fd_set event_read_fds_;
- fd_set event_write_fds_;
-
- void Invalidate() { max_fds_ = kInvalid; }
-
- int num_fds() const {
- // Recompute max file descriptor if needed.
- if (max_fds_ == kInvalid) {
- max_fds_ = -1;
- for (AsyncHandle::State* state : states_) {
- if (state->NeedsReadEvent() || state->NeedsWriteEvent()) {
- if (state->fd() > max_fds_)
- max_fds_ = state->fd();
- }
- }
- }
- return max_fds_ + 1;
- }
-
- ListType::iterator FindState(AsyncHandle::State* state) {
- return std::find(states_.begin(), states_.end(), state);
- }
-
- public:
- Watches() {
- FD_ZERO(&read_fds_);
- FD_ZERO(&write_fds_);
- }
-
- void Insert(AsyncHandle::State* state) {
- if (state->fd() >= static_cast<int>(FD_SETSIZE))
- Fatal("File descriptor too large for pselect(): %d\n", state->fd());
-
- auto it = FindState(state);
- assert(it == states_.end() && "Async state already in the set!");
- if (it != states_.end())
- return;
- states_.push_back(state);
- if (state->NeedsReadEvent())
- FD_SET(state->fd(), &read_fds_);
- if (state->NeedsWriteEvent())
- FD_SET(state->fd(), &write_fds_);
- Invalidate();
- }
-
- void Remove(AsyncHandle::State* state) {
- auto it = FindState(state);
- assert(it != states_.end() && "Removing unknown Async state!");
- if (it == states_.end())
- return;
- FD_CLR(state->fd(), &read_fds_);
- FD_CLR(state->fd(), &write_fds_);
- Invalidate();
- }
-
- void Update(AsyncHandle::State* state) {
- int fd = state->fd();
- if (state->NeedsReadEvent()) {
- FD_SET(fd, &read_fds_);
- } else {
- FD_CLR(fd, &read_fds_);
- }
- if (state->NeedsWriteEvent()) {
- FD_SET(fd, &write_fds_);
- } else {
- FD_CLR(fd, &write_fds_);
- }
- }
-
- bool HasWaiters() const {
- for (const auto* state : states_) {
- if (state->IsRunning())
- return true;
- }
- return false;
- }
-
- int WaitForEvents(const struct timespec* ts, PendingList& pending_ops) {
- event_read_fds_ = read_fds_;
- event_write_fds_ = write_fds_;
-
- int count = num_fds();
- int ret = pselect(count, &event_read_fds_, &event_write_fds_, nullptr, ts,
- nullptr);
- if (ret < 0) {
- if (errno != EINTR)
- ErrnoFatal("pselect");
- } else if (ret > 0) {
- for (auto* state : states_) {
- int fd = state->fd();
- bool has_event = false;
- has_event |=
- state->NeedsWriteEvent() && FD_ISSET(fd, &event_write_fds_);
- has_event |=
- state->NeedsReadEvent() && FD_ISSET(fd, &event_read_fds_);
- if (has_event)
- pending_ops.emplace_back(state);
- }
- }
- return ret;
- }
- };
-#endif // !USE_KQUEUE && !USE_PPOLL
-
- public:
- ~Impl() {
- assert(!watches_.HasWaiters() &&
- "Destroying AsyncLoop before children AsyncHandle instances!");
- assert(timers_.empty() &&
- "Destroying AsyncLoop before children AsyncTimer instances!");
- }
-
- AsyncLoopTimers& timers() { return timers_; }
-
- void AttachHandle(AsyncHandle::State* state) {
- assert(state->fd_ && "Trying to attach invalid handle");
- watches_.Insert(state);
- }
-
- void DetachHandle(AsyncHandle::State* state) {
- assert(state->fd_ && "Trying to detach invalid handle");
- watches_.Remove(state);
- }
-
- void UpdateHandle(AsyncHandle::State* state) { watches_.Update(state); }
-
- void CancelHandle(AsyncHandle::State* state) {
- pending_ops_.Remove(state);
- watches_.Update(state);
- }
-
- AsyncLoop::ExitStatus RunOnce(int64_t timeout_ms, AsyncLoop& loop) {
- assert(pending_ops_.empty());
-
- /// An interrupt occured outside of this loop, return immediately.
- if (interrupt_catcher_.get() && interrupt_catcher_->interrupted()) {
- return ExitInterrupted;
- }
-
- /// Handle timeout.
- bool has_timers = false;
- int64_t timer_expiration_ms = timers_.ComputeNextExpiration();
- if (timer_expiration_ms >= 0) {
- has_timers = true;
- int64_t now_ms = NowMs();
- int64_t timer_timeout_ms = timer_expiration_ms - now_ms;
- if (timer_timeout_ms < 0) {
- timer_timeout_ms = 0;
- }
-
- if (timeout_ms < 0)
- timeout_ms = timer_timeout_ms;
- else if (timeout_ms > timer_timeout_ms)
- timeout_ms = timer_timeout_ms;
- }
-
- const struct timespec* ts = NULL;
- struct timespec timeout_ts = {};
- if (timeout_ms >= 0) {
- timeout_ts.tv_sec = static_cast<time_t>(timeout_ms / 1000);
- timeout_ts.tv_nsec = static_cast<long>((timeout_ms % 1000) * 1000000LL);
- ts = &timeout_ts;
- }
-
- /// Exit immediately if there is nothing to do and no timeout.
- if (!watches_.HasWaiters() && pending_ops_.empty() && timeout_ms < 0)
- return ExitIdle;
-
- int ret = watches_.WaitForEvents(ts, pending_ops_);
- if (ret == -1) {
- return ExitInterrupted;
- }
-
- if (interrupt_catcher_.get()) {
- interrupt_catcher_->HandlePendingInterrupt();
- if (interrupt_catcher_->interrupted())
- return ExitInterrupted;
- }
-
- ExitStatus result = ExitTimeout;
-
- // Handle all pending operations.
- // Note that OnEvent() may invoke a callback that changes the
- // state of pending_ops_.
- while (!pending_ops_.empty()) {
- AsyncHandle::State* state = pending_ops_.back();
- pending_ops_.pop_back();
- if (state->OnEvent())
- result = ExitSuccess;
- }
-
- if (has_timers && timers_.ProcessExpiration(NowMs()))
- result = ExitSuccess;
-
- return result;
- }
-
- void ClearInterrupt() {
- if (interrupt_catcher_)
- interrupt_catcher_->Clear();
- }
-
- int GetInterruptSignal() const {
- if (!interrupt_catcher_)
- return 0;
- return interrupt_catcher_->interrupted();
- }
-
- sigset_t GetOldSignalMask() const {
- if (!interrupt_catcher_) {
- sigset_t old_mask;
- sigset_t empty;
- sigemptyset(&empty);
- sigprocmask(SIG_BLOCK, &empty, &old_mask);
- return old_mask;
- }
- return interrupt_catcher_->old_mask();
- }
-
- void EnableInterruptCatcher() {
- interrupt_catcher_.reset(new InterruptCatcher());
- }
-
- void DisableInterruptCatcher() { interrupt_catcher_.reset(); }
-
- private:
- std::unique_ptr<InterruptCatcher> interrupt_catcher_;
- AsyncLoopTimers timers_;
- Watches watches_;
-};
-
-#endif // NINJA_ASYNC_LOOP_POSIX_H
diff --git a/src/async_loop-win32.h b/src/async_loop-win32.h
deleted file mode 100644
index fe6f702..0000000
--- a/src/async_loop-win32.h
+++ /dev/null
@@ -1,460 +0,0 @@
-// Copyright 2023 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_ASYNC_LOOP_WIN32_H
-#define NINJA_ASYNC_LOOP_WIN32_H
-
-/// Win32 implementation of the AsyncLoop class.
-
-#include <assert.h>
-
-#include <list>
-#include <unordered_map>
-
-#include "async_loop.h"
-#include "async_loop_timers.h"
-#include "interrupt_handling.h"
-#include "metrics.h" // GetTimeMillis()
-#include "util.h" // Win32Fatal()
-#include "win32port.h" // PRId64
-
-// Set to 1 to add special code paths to add extra debugging checks.
-#define DEBUG 0
-
-#if DEBUG
-#define DEBUG_OVERLAPPED_POINTERS 1
-#else
-#define DEBUG_OVERLAPPED_POINTERS 0
-#endif
-
-// Technical note on Win32 overlapped i/o:
-//
-// - Calling CreateIoCompletionPort() on a handle that
-// was already associated with the same port returns
-// ERROR_INVALID_PARAMETER.
-//
-// This means it is impossible to change the completion_key
-// value for a handle once it has been set.
-//
-// - If a handle is associated with a completion port,
-// calling DuplicateHandle() will create a new handle
-// that is _already_ associated with the same completion port.
-//
-// This means that calling CreateIoCompletionPort(new_handle, ...)
-// will fail with ERROR_INVALID_PARAMETER.
-//
-// But it also means the completion_key for both handles will
-// always be the same. Thus this value becomes useless to
-// distinguish handles in general. The only reliable way is to
-// use the OVERLAPPED* pointer.
-//
-// - Even if an overlapped ReadFile() call succeeds immediately,
-// a completion event will still be sent to the port for it.
-// Same with WriteFile(), ConnectNamedPipe() and other Win32 I/O functions.
-//
-
-// The Win32-specific state of an AsyncHandle instance, without any
-// AsyncLoop reference. Wraps an OVERLAPPED struct
-struct AsyncHandle::State {
- enum class Kind {
- None = 0,
- Read,
- Write,
- Connect,
- Accept,
- };
-
- // Constructors.
- explicit State(AsyncLoop& async_loop) : async_loop_(async_loop) {}
-
- State(IpcHandle handle, AsyncLoop& async_loop, AsyncHandle::Callback&& callback)
- : handle_(std::move(handle)), async_loop_(async_loop), callback_(std::move(callback)) {
- if (handle_)
- async_loop_.AttachHandle(this);
- }
-
- // Destructor.
- ~State() {
- if (!completed_)
- Cancel();
- if (handle_) {
- async_loop_.DetachHandle(this);
- }
- }
-
- // Disallow copy operations. Even though this is implied by
- // the use of moveable-only fields like |handle_|.
- State(const State&) = delete;
- State& operator=(const State&) = delete;
-
- // Disable move operations, since the address of this instance's
- // |overlapped_| field (which should equal the instance's address) is
- // passed to the Windows kernel, and thus cannot change.
- State(State&&) noexcept = delete;
- State& operator=(State&&) noexcept = delete;
-
- // Return AsyncLoop this instance belongs to.
- AsyncLoop& async_loop() const { return async_loop_; }
- AsyncLoop::Impl& loop() const { return *async_loop_.impl_; }
-
- bool is_valid() const { return !!handle_; }
-
- // Is an asynchronous operation running?
- bool IsRunning() const { return !completed_; }
-
- HANDLE native_handle() const { return handle_.native_handle(); }
-
- // Cancel current asynchronous operation, if any.
- void Cancel() {
- if (!completed_ && kind_ != Kind::None) {
- async_loop_.CancelHandle(this);
- ::CancelIoEx(handle_.native_handle(), &overlapped_);
- completed_ = true;
- }
- }
-
- // Reset the callback.
- void ResetCallback(AsyncHandle::Callback&& cb) { callback_ = std::move(cb); }
-
- void ResetHandle(IpcHandle handle) {
- if (handle_) {
- Cancel();
- async_loop_.DetachHandle(this);
- }
- handle_ = std::move(handle);
- if (handle_)
- async_loop_.AttachHandle(this);
- }
-
- HANDLE ReleaseHandle() {
- if (!handle_)
- return INVALID_HANDLE_VALUE;
-
- Cancel();
- async_loop_.DetachHandle(this);
- return handle_.ReleaseNativeHandle();
- }
-
- // Reset state to start a new asynchronous operation. Cancels current one
- // if any.
- void Reset(Kind kind = Kind::None) {
- kind_ = kind;
- if (!completed_) {
- ::CancelIoEx(handle_.native_handle(), &overlapped_);
- completed_ = true;
- }
- error_ = 0;
- actual_bytes_ = 0;
- accept_handle_.Close();
- }
-
- // Start async read operation.
- void StartRead(void* buffer, size_t size) {
- Reset(Kind::Read);
- completed_ = false;
- if (!ReadFile(handle_.native_handle(), buffer, size, &actual_bytes_,
- &overlapped_)) {
- error_ = GetLastError();
- completed_ = (error_ != ERROR_IO_PENDING);
- }
- async_loop_.UpdateHandle(this);
- }
-
- // Start async write operation.
- void StartWrite(const void* buffer, size_t size) {
- Reset(Kind::Write);
- completed_ = false;
- if (!::WriteFile(handle_.native_handle(), buffer, size, &actual_bytes_,
- &overlapped_)) {
- error_ = GetLastError();
- completed_ = (error_ != ERROR_IO_PENDING);
- }
- async_loop_.UpdateHandle(this);
- }
-
- // Start async connect operation.
- void StartConnect() {
- Reset(Kind::Connect);
- // Connection to named pipes is immediate on Win32.
- completed_ = true;
- async_loop_.UpdateHandle(this);
- }
-
- // Start async accept operation.
- void StartAccept() {
- // Note: duplicate the server handle here to be able to pass
- // the result to the final AsyncHandle::TakeAcceptedHandle()
- // call as a separate client connection handle.
- //
- // The duplicate is already associated with the i/o completion port
- // and does not need to be attached to the AsyncLoop instance.
- Reset(Kind::Accept);
- completed_ = false;
- accept_handle_ = handle_.Clone();
-
- if (!ConnectNamedPipe(accept_handle_.native_handle(), &overlapped_)) {
- error_ = GetLastError();
- completed_ = (error_ != ERROR_IO_PENDING);
- }
- async_loop_.UpdateHandle(this);
- }
-
- // Called when a completion event is received. Do not invoke the callback
- // yet though.
- void OnCompletion(AsyncError error, size_t transfer_size) {
- completed_ = true;
- error_ = error;
- actual_bytes_ = transfer_size;
- }
-
- // Invoke the callback.
- void InvokeCallback() { callback_(error_, actual_bytes_); }
-
- // Return accepted handle, if any.
- IpcHandle TakeAcceptedHandle() { return std::move(accept_handle_); }
-
- OVERLAPPED overlapped_ = {}; // Must be first!
- IpcHandle handle_;
- Kind kind_ = Kind::None;
- bool completed_ = false;
- DWORD error_ = 0;
- DWORD actual_bytes_ = 0;
- IpcHandle accept_handle_;
- AsyncLoop& async_loop_;
- AsyncHandle::Callback callback_;
-};
-
-// Win32-specific implementation of the AsyncLoop internal interface.
-class AsyncLoop::Impl {
- /// The list of pending AsyncHandle::States that have completed but whose
- /// Invoke() method wasn't called yet. Identified by their address.
- struct PendingList : public std::vector<AsyncHandle::State*> {
- // Remove one async_id from the list.
- bool Remove(AsyncHandle::State* async_op) {
- for (auto it = begin(); it != end(); ++it) {
- if (*it == async_op) {
- // Order does not matter for this list, so just move last item
- // to current location to avoid O(n) removal cost.
- auto it_last = end() - 1;
- if (it != it_last)
- *it = *it_last;
- pop_back();
- return true;
- }
- }
- return false;
- }
- };
-
- PendingList pending_ops_;
-
- // The number of AsyncHandle instances attached to this loop.
- size_t attached_count_ = 0;
-
- // The number of AsyncHandle instances waiting for an event.
- size_t waiting_count_ = 0;
-
-#if DEBUG_OVERLAPPED_POINTERS
- // A map used to convert OVERLAPPED* pointer values to the address
- // of a known AsyncHandle::State. Used for safety.
- using OverlappedMap = std::unordered_map<OVERLAPPED*, AsyncHandle::State*>;
- OverlappedMap overlapped_map_;
-#endif
-
- public:
- ~Impl() {
-#if DEBUG_OVERLAPPED_POINTERS
- assert(overlapped_map_.empty() &&
- "Destroying AsyncLop before AsyncHandle!");
-#endif
- assert(timers_.empty() &&
- "Destroying AsyncLoop before children AsyncTimer instances!");
- }
-
- AsyncLoopTimers& timers() { return timers_; }
-
- void AttachHandle(AsyncHandle::State* async_op) {
- assert(async_op && "Attaching null async op!");
- assert(async_op->handle_ && "Attaching invalid handle!");
-
- HANDLE handle = async_op->handle_.native_handle();
- if (handle == INVALID_HANDLE_VALUE)
- return;
-
- auto key = reinterpret_cast<ULONG_PTR>(handle);
- if (!CreateIoCompletionPort(handle, ioport_.get(), key, 0)) {
- DWORD error = GetLastError();
- // CreateIoCompletionPort() will return invalid parameter
- // when trying to call it with the duplicate of a handle that
- // was already associated with the port. Ignore it.
- if (error != ERROR_INVALID_PARAMETER)
- Win32Fatal("CreateIoCompletionPortRead");
- }
-
-#if DEBUG_OVERLAPPED_POINTERS
- auto ret = overlapped_map_.emplace(&async_op->overlapped_, async_op);
- assert(ret.second &&
- "Adding AsyncHandle::State for known OVERLAPPED pointer!");
-#endif // DEBUG_OVERLAPPED_POINTERS
-
- attached_count_ += 1;
- }
-
- void DetachHandle(AsyncHandle::State* async_op) {
- assert(async_op->handle_ && "Detaching invalid handle!");
- if (!async_op->handle_)
- return;
-
- assert(attached_count_ > 0 &&
- "Detaching AsyncHandle::State from empty AsyncLoop!");
- attached_count_--;
-
-#if DEBUG_OVERLAPPED_POINTERS
- auto overlapped_it = overlapped_map_.find(&async_op->overlapped_);
- assert(overlapped_it != overlapped_map_.end() &&
- "Releasing AsyncHandle::State for unknown OVERLAPPED pointer!");
- overlapped_map_.erase(overlapped_it);
-#endif // DEBUG_OVERLAPPED_POINTERS
- }
-
- void UpdateHandle(AsyncHandle::State* state) {
- if (state->completed_)
- pending_ops_.push_back(state);
- else
- waiting_count_++;
- }
-
- void CancelHandle(AsyncHandle::State* astate) {
- pending_ops_.Remove(astate);
- assert(waiting_count_ > 0);
- waiting_count_--;
- }
-
- ExitStatus RunOnce(int64_t timeout_ms, AsyncLoop& loop) {
- // Any pending operations (e.g. from StartAsyncConnect()) means
- // no timeout is needed.
- if (!pending_ops_.empty())
- timeout_ms = 0;
-
- // Handle timeout.
- bool has_timers = false;
- int64_t timer_expiration_ms = timers_.ComputeNextExpiration();
- if (timer_expiration_ms >= 0) {
- has_timers = true;
- int64_t timer_timeout_ms = timer_expiration_ms - NowMs();
- if (timer_timeout_ms < 0)
- timer_timeout_ms = 0;
-
- if (timeout_ms < 0)
- timeout_ms = timer_timeout_ms;
- else if (timeout_ms > timer_timeout_ms)
- timeout_ms = timer_timeout_ms;
- }
-
- // Do we have any async operation here?
- if (timeout_ms < 0 && !waiting_count_)
- return ExitIdle;
-
- DWORD error = 0;
- DWORD transfer_size = 0;
- ULONG_PTR completion_key = 0;
- bool received_key = true;
- OVERLAPPED* overlapped_ptr = nullptr;
- DWORD timeout = timeout_ms < 0 ? INFINITE : static_cast<DWORD>(timeout_ms);
- if (!GetQueuedCompletionStatus(ioport_.get(), &transfer_size,
- &completion_key, &overlapped_ptr, timeout)) {
- error = GetLastError();
- if (error == ERROR_BROKEN_PIPE) {
- // Pass the error to the callback.
- } else if (error == WAIT_TIMEOUT) {
- received_key = false;
- if (pending_ops_.empty() && !has_timers)
- return ExitTimeout;
- } else {
- Win32Fatal("GetQueuedCompletionStatus");
- }
- }
-
- if (received_key) {
- // NotifyInterrupted is the only thing that posts a NULL completion key.
- if (!completion_key)
- return ExitInterrupted;
-
-#if DEBUG_OVERLAPPED_POINTERS
- auto overlapped_it = overlapped_map_.find(overlapped_ptr);
- assert(overlapped_it != overlapped_map_.end() &&
- "Received completion for unknown OVERLAPPED pointer!");
- AsyncHandle::State* async_op = overlapped_it->second;
- assert(async_op ==
- reinterpret_cast<AsyncHandle::State*>(overlapped_ptr) &&
- "Inconsistent OVERLAPPED pointer value!");
-#else // !DEBUG_OVERLAPPED_POINTERS
- AsyncHandle::State* async_op =
- reinterpret_cast<AsyncHandle::State*>(overlapped_ptr);
-#endif
- async_op->OnCompletion(error, transfer_size);
- assert(waiting_count_ > 0);
- waiting_count_ -= 1;
- pending_ops_.push_back(async_op);
- }
-
- // Handle all pending operations.
- // Note that InvokeCallback() may invoke a callback that changes the
- // state of pending_ops_.
- while (!pending_ops_.empty()) {
- AsyncHandle::State* async_op = pending_ops_.back();
- pending_ops_.pop_back();
- async_op->InvokeCallback();
- }
-
- if (has_timers && !timers_.ProcessExpiration(NowMs()))
- return ExitTimeout;
-
- return ExitSuccess;
- }
-
- void ClearInterrupt() {
- // Nothing to do here.
- }
-
- void EnableInterruptCatcher() {
- interrupt_handler_.reset(new InterruptCompletionPortHandler(ioport_.get()));
- }
-
- void DisableInterruptCatcher() { interrupt_handler_.reset(); }
-
- private:
- /// Helper class to create and close an I/O completion port handle
- /// in the right order.
- struct ScopedIoPort {
- ScopedIoPort()
- : handle_(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1)) {
- if (!handle_)
- Win32Fatal("CreateIoCompletionPort");
- }
-
- ~ScopedIoPort() { ::CloseHandle(handle_); }
-
- HANDLE get() const { return handle_; }
-
- private:
- HANDLE handle_;
- };
-
- ScopedIoPort ioport_;
- std::unique_ptr<InterruptCompletionPortHandler> interrupt_handler_;
- AsyncLoopTimers timers_;
-};
-
-#endif // NINJA_ASYNC_LOOP_WIN32_H
diff --git a/src/async_loop.cc b/src/async_loop.cc
deleted file mode 100644
index 978a972..0000000
--- a/src/async_loop.cc
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright 2023 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 "async_loop.h"
-
-#include "util.h"
-
-#ifdef _WIN32
-#include "async_loop-win32.h"
-#else
-#include "async_loop-posix.h"
-#endif
-
-// static
-std::string AsyncErrorToString(AsyncError error) {
- return std::string(strerror(error));
-}
-
-// Global instance.
-static std::unique_ptr<AsyncLoop> s_loop;
-
-// static
-AsyncLoop& AsyncLoop::Get() {
- AsyncLoop* loop = s_loop.get();
- if (!loop) {
- loop = new AsyncLoop();
- s_loop.reset(loop);
- }
- return *loop;
-}
-
-// static
-std::unique_ptr<AsyncLoop> AsyncLoop::CreateLocal() {
- return std::unique_ptr<AsyncLoop>(new AsyncLoop());
-}
-
-void AsyncLoop::Reset() {
- impl_.reset(new AsyncLoop::Impl());
- if (interrupt_catcher_count_ > 0)
- impl_->EnableInterruptCatcher();
-}
-
-// static
-void AsyncLoop::ResetForTesting() {
- AsyncLoop* loop = s_loop.get();
- if (loop)
- loop->Reset();
-}
-
-AsyncLoop::AsyncLoop() : impl_(new AsyncLoop::Impl()) {}
-
-AsyncLoop::~AsyncLoop() = default;
-
-// static
-int64_t AsyncLoop::NowMs() {
- static bool init = false;
- static int64_t start_ms = 0;
- int64_t result = GetTimeMillis();
- if (!init) {
- start_ms = result;
- init = true;
- }
- return result - start_ms;
-}
-
-AsyncLoop::ExitStatus AsyncLoop::RunOnce(int64_t timeout_ms) {
- return impl_->RunOnce(timeout_ms, *this);
-}
-
-void AsyncLoop::ClearInterrupt() {
- impl_->ClearInterrupt();
-}
-
-#ifndef _WIN32
-int AsyncLoop::GetInterruptSignal() const {
- return impl_->GetInterruptSignal();
-}
-
-sigset_t AsyncLoop::GetOldSignalMask() const {
- return impl_->GetOldSignalMask();
-}
-#endif // !_WIN32
-
-void AsyncLoop::UpdateHandle(AsyncHandle::State* state) {
- impl_->UpdateHandle(state);
-}
-
-void AsyncLoop::CancelHandle(AsyncHandle::State* state) {
- impl_->CancelHandle(state);
-}
-
-void AsyncLoop::AttachTimer(AsyncTimer::State* state) {
- impl_->timers().AttachTimer(state);
-}
-
-void AsyncLoop::DetachTimer(AsyncTimer::State* state) {
- impl_->timers().DetachTimer(state);
-}
-
-void AsyncLoop::UpdateTimer(AsyncTimer::State* state) {
- impl_->timers().UpdateTimer(state);
-}
-
-AsyncLoop::RunUntilState::RunUntilState(int64_t timeout_ms)
- : timeout_ms_(timeout_ms) {}
-
-bool AsyncLoop::RunUntilState::LoopAgain(AsyncLoop& async_loop) {
- // Initialize expiration_ms_ the first time this method is called.
- int64_t expiration_ms = -1;
- if (timeout_ms_ >= 0)
- expiration_ms = async_loop.NowMs() + timeout_ms_;
-
- status_ = async_loop.RunOnce(timeout_ms_);
- if (status_ != ExitSuccess) {
- // Either an interrupt, timeout or idle exit.
- // This is the end of the loop.
- return false;
- }
-
- if (timeout_ms_ < 0) {
- // Since no timeout was specified, just loop again
- // after an async event.
- return true;
- }
-
- // Adjust the timeout for the next invocation.
- timeout_ms_ = expiration_ms - async_loop.NowMs();
- if (timeout_ms_ >= 0) {
- // There is still time left, so loop again.
- return true;
- }
-
- // There is no time left, stop the loop reporting
- // a real timeout.
- timeout_ms_ = 0;
- status_ = ExitTimeout;
- return false;
-}
-
-void AsyncLoop::ChangeInterruptCatcher(bool increment) {
- if (increment) {
- if (++interrupt_catcher_count_ == 1)
- impl_->EnableInterruptCatcher();
- } else {
- if (interrupt_catcher_count_ <= 0)
- Fatal("Unbalanced ChangeInterruptCatcher() calls");
- if (--interrupt_catcher_count_ == 0)
- impl_->DisableInterruptCatcher();
- }
-}
-
-void AsyncLoop::AttachHandle(AsyncHandle::State* state) {
- impl_->AttachHandle(state);
-}
-
-void AsyncLoop::DetachHandle(AsyncHandle::State* state) {
- impl_->DetachHandle(state);
-}
-
-///////////////////////////////////////////////////////////////////////////
-///
-/// AsyncHandle
-///
-
-AsyncHandle::AsyncHandle() = default;
-AsyncHandle::AsyncHandle(std::unique_ptr<AsyncHandle::State> state)
- : state_(std::move(state)) {}
-
-// static
-AsyncHandle AsyncHandle::Create(IpcHandle handle, AsyncLoop& async_loop,
- AsyncHandle::Callback&& callback) {
- auto state = std::unique_ptr<AsyncHandle::State>(new AsyncHandle::State(
- std::move(handle), async_loop, std::move(callback)));
- return AsyncHandle(std::move(state));
-}
-
-// static
-AsyncHandle AsyncHandle::Create(IpcHandle::HandleType native_handle,
- AsyncLoop& async_loop,
- AsyncHandle::Callback&& callback) {
- return Create(IpcHandle(native_handle), async_loop, std::move(callback));
-}
-
-// static
-AsyncHandle AsyncHandle::CreateClone(IpcHandle::HandleType native_handle,
- AsyncLoop& async_loop,
- AsyncHandle::Callback&& callback) {
- return Create(IpcHandle::CloneNativeHandle(native_handle), async_loop,
- std::move(callback));
-}
-
-AsyncHandle::~AsyncHandle() = default;
-
-AsyncHandle::AsyncHandle(AsyncHandle&&) noexcept = default;
-AsyncHandle& AsyncHandle::operator=(AsyncHandle&&) noexcept = default;
-
-void AsyncHandle::Close() {
- if (is_valid()) {
- state_->Cancel();
- state_.reset();
- }
-}
-
-void AsyncHandle::ResetHandle(IpcHandle handle) {
- assert(state_ && "Reset() on invalid AsyncHandle value");
-#ifndef _WIN32
- handle.SetNonBlocking(true);
-#endif
- state_->ResetHandle(std::move(handle));
-}
-
-IpcHandle::HandleType AsyncHandle::Release() {
- return state_->ReleaseHandle();
-}
-
-bool AsyncHandle::is_valid() const {
- return state_ && state_->is_valid();
-}
-
-IpcHandle::HandleType AsyncHandle::native_handle() const {
- return state_ ? state_->native_handle() : IpcHandle::kInvalid;
-}
-
-bool AsyncHandle::IsRunning() const {
- return state_ && state_->IsRunning();
-}
-
-void AsyncHandle::Cancel() {
- if (state_)
- state_->Cancel();
-}
-
-AsyncHandle& AsyncHandle::ResetCallback(AsyncHandle::Callback&& callback) {
- assert(state_ && "ResetCallback() on invalid AsyncHandle value");
- state_->ResetCallback(std::move(callback));
- return *this;
-}
-
-void AsyncHandle::StartRead(void* buffer, size_t size) {
- assert(state_ && "StartRead() on invalid AsyncHandle value");
- state_->StartRead(buffer, size);
-}
-
-void AsyncHandle::StartWrite(const void* buffer, size_t size) {
- assert(state_ && "StartWrite() on invalid AsyncHandle value");
- state_->StartWrite(buffer, size);
-}
-
-void AsyncHandle::StartConnect() {
- assert(state_ && "StartConnect() on invalid AsyncHandle value");
- state_->StartConnect();
-}
-
-void AsyncHandle::StartAccept() {
- assert(state_ && "StartAccept() on invalid AsyncHandle value");
- state_->StartAccept();
-}
-
-IpcHandle AsyncHandle::TakeAcceptedHandle() {
- assert(state_ && "TakeAcceptedHandle() on invalid AsyncHandle value");
- return state_->TakeAcceptedHandle();
-}
-
-AsyncLoop& AsyncHandle::async_loop() const {
- assert(state_ && "async_loop() on invalid AsyncHandle value");
- return state_->async_loop();
-}
-
-///////////////////////////////////////////////////////////////////////////
-///
-/// AsyncTimer
-///
-
-AsyncTimer::AsyncTimer() = default;
-
-AsyncTimer::AsyncTimer(AsyncLoop& async_loop, AsyncTimer::Callback&& callback)
- : state_(new State(async_loop, std::move(callback))) {}
-
-AsyncTimer::~AsyncTimer() = default;
-
-AsyncTimer::AsyncTimer(AsyncTimer&&) noexcept = default;
-AsyncTimer& AsyncTimer::operator=(AsyncTimer&&) noexcept = default;
-
-void AsyncTimer::ResetCallback(AsyncTimer::Callback&& callback) {
- assert(state_ && "Calling AsyncTimer::ResetCallback() on invalid instance!");
- state_->ResetCallback(std::move(callback));
-}
-void AsyncTimer::SetExpirationMs(int64_t expiration_ms) {
- assert(state_ &&
- "Calling AsyncTimer::SetExpirationMs() on invalid instance!");
- state_->SetExpirationMs(expiration_ms);
-}
-
-void AsyncTimer::SetDurationMs(int64_t duration_ms) {
- assert(state_ && "Calling AsyncTimer::SetDurationMs() on invalid instance!");
- state_->SetDurationMs(duration_ms);
-}
-
-void AsyncTimer::Cancel() {
- assert(state_ && "Calling AsyncTimer::Cancel() on invalid instance!");
- state_->Cancel();
-}
-
-void AsyncTimer::Close() {
- state_.reset();
-}
-
-AsyncLoop& AsyncTimer::async_loop() const {
- assert(state_ && "Calling AsyncTimer::async_loop() on invalid instance!");
- return state_->async_loop();
-}
-
-// static
-AsyncTimer AsyncTimer::CreateWithExpiration(int64_t expiration_ms,
- AsyncLoop& async_loop,
- AsyncTimer::Callback&& callback) {
- AsyncTimer timer(async_loop, std::move(callback));
- timer.SetExpirationMs(expiration_ms);
- return timer;
-}
-
-// static
-AsyncTimer AsyncTimer::CreateWithDuration(int64_t duration_ms,
- AsyncLoop& async_loop,
- AsyncTimer::Callback&& callback) {
- AsyncTimer timer(async_loop, std::move(callback));
- timer.SetDurationMs(duration_ms);
- return timer;
-}
diff --git a/src/async_loop.h b/src/async_loop.h
deleted file mode 100644
index fd83729..0000000
--- a/src/async_loop.h
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2023 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_ASYNC_LOOP_H
-#define NINJA_ASYNC_LOOP_H
-
-#include "ipc_handle.h"
-
-#ifndef _WIN32
-#include <signal.h>
-#endif
-
-#include <functional>
-#include <memory>
-
-/// AsyncLoop provides an abstraction to perform asynchronous i/o
-/// operations on Posix and Win32. Its API is inspired by Win32
-/// overlapped operations, since this model can be implemented
-/// efficiently on Posix.
-///
-/// AsyncHandle wraps an i/o handle, and a user-provided callback.
-/// It provides methods like StartRead() to initiate an asynchronous operation.
-///
-/// AsyncTimer wraps an expiration date and a user-provided callback.
-/// It provides SetExpirationMs() and SetDurationMs() methods to indicate
-/// when the timer's next expiration should occur.
-///
-/// Each AsyncHandle or AsyncTimer instance is scoped to a parent AsyncLoop
-/// instance.
-///
-/// Example usage:
-///
-/// 1) Obtain a reference to an AsyncLoop instance, e.g. by
-/// calling AsyncLoop::Get() or AsyncLoop::CreateLocal().
-///
-/// 2) Create as many AsyncHandle and AsyncTimer instances from
-/// the parent loop as needed. passing a user-provided callback
-/// when constructing each one of them.
-///
-/// 4) Use AsyncHandle::StartXXX() methods to start an i/o asynchronous
-/// operation, or AsyncTimer::Set{Expiration,Duration}Ms() to set a
-/// timer's expiration time.
-///
-/// 5) Call AsyncLoop::RunOnce() or AsyncLoop::RunUntil() to run the
-/// the loop. This waits for the completion of async i/o events or
-/// expiration of timers, and invokes their callbacks automatically.
-///
-/// Using AsyncLoop::RunOnce(-1) will wait until one of the following
-/// happens:
-///
-/// - There are no more active handles or timers, in which case
-/// the function returns immediately with AsyncLoop::ExitIdle.
-///
-/// - At least one AsyncHandle's i/o operation completes, in which
-/// case their callbacks are invoked before the function returns
-/// AsyncLoop::ExitSuccess.
-///
-/// - At least one AsyncTimer expires, in which case their callbacks
-/// are invoked before the function returns AsyncLoop::ExitSuccess.
-///
-/// - If the AsyncLoop's interrupt catching feature is enabled (see
-/// below), return immediately with AsyncLoop::ExitInterrupted.
-///
-/// It is also possible to pass a timeout in milliseconds, e.g.
-/// calling AsyncLoop::RunOnce(1000) to wait for one second. If the
-/// timeout expires without an i/o completion or timer expiration,
-/// the function returns with AsyncLoop::ExitTimeout.
-///
-/// Calling AsyncLoop::RunUntil() is more convenient as it allows to
-/// wait for specific conditions instead.
-///
-/// Some important points:
-///
-/// - There is a global AsyncLoop instance which can be
-/// retrieved with AsyncLoop::Get(), this is meant to be
-/// used from the main thread only.
-///
-/// - Use AsyncLoop::CreateLocal() to create additionnal
-/// AsyncLoop instances. This can be useful to run them in
-/// background threads.
-///
-/// - It is a runtime error to destroy an AsyncLoop before any of its
-/// children AsyncHandle or AsyncTimer instances (assertion failure on
-/// debug build).
-///
-/// - An AsyncLoop instance can catch interrupts (i.e. Ctrl-C
-/// on Windows, and SIGINT/SIGUP/SIGTERM on Posix) automatically
-/// and report them in AsyncLoop::ExitStatus.
-///
-/// IMPORTANT: At the moment, the global AsyncLoop always catches
-/// interrupts by default. This may change in the future.
-/// TODO(digit): Change this and make all uses explicit.
-///
-/// Only one AsyncLoop instance can catch interrupts at any
-/// given time. Use AsyncLoop::ScopedInterruptCatcher if you
-/// want to force a given AsyncLoop instance to catch
-/// interrupts (see its documentation below for details).
-///
-/// - Once a timer expires, it must be re-armed explicitly, which can
-/// be done by calling its Set{Expiration,Duration}Ms() method directly
-/// from its callback.
-///
-/// - Similarly, it is possible (and actually the most efficient path)
-/// to start another i/o operation right from the completion callback
-/// of an AsyncHandle instance.
-///
-/// - AsyncLoop::RunOnce() and AsyncLoop::RunUntil() are not re-entrant,
-/// meaning one should not call them from completion / expiration callbacks.
-///
-/// On the other hand, creating / deleting AsyncHandle / AsyncTimer instances
-/// within callbacks is supported.
-///
-/// - There is no method to cancel all asynchronous operations
-/// associated with a handle, or to detach all handles. This
-/// is intentional.
-
-/// AsyncError is an error code returned by a failed asynchronous operation.
-/// On Win32, this is a GetLastError() value, while on Posix this is an errno
-/// value. Always 0 to indicate that there is no error.
-#ifdef _WIN32
-using AsyncError = DWORD;
-#else
-using AsyncError = int;
-#endif
-
-/// Forward declaration.
-class AsyncLoop;
-
-/// Convert AsyncError to string.
-std::string AsyncErrorToString(AsyncError error);
-
-/// Scoped AsyncHandle type that only supports one async operation
-/// at a time.
-class AsyncHandle {
- public:
- /// The type of a callable object that will be invoked when an
- /// asynchronous operation completes. The first argument is an error
- /// code and will be 0 in case of success. The second argument is
- /// the transfer size, and will be 0 to connect and accept operations.
- using Callback = std::function<void(AsyncError, size_t)>;
-
- /// Default constructor creates an empty instance.
- AsyncHandle();
-
- /// Destructor, closes the handle automatically.
- ~AsyncHandle();
-
- /// Move operations.
- AsyncHandle(AsyncHandle&&) noexcept;
- AsyncHandle& operator=(AsyncHandle&&) noexcept;
-
- /// Create new instance that takes ownership of |native_handle|.
- static AsyncHandle Create(IpcHandle::HandleType native_handle,
- AsyncLoop& async_loop, Callback&& callback);
-
- /// Create a new instance that duplicates |native_handle| and takes
- /// ownership of the resulting handle. Note that the duplicate is
- /// not inheritable and will be in non-blocking mode on Posix.
- static AsyncHandle CreateClone(IpcHandle::HandleType native_handle,
- AsyncLoop& async_loop, Callback&& callback);
-
- /// Create new instance that takes ownership of the native handle
- /// from |handle|. Note that when |handle| is an IpcServiceHandle instance,
- /// then it should only be destroyed _after_ this instance, in order to
- /// perform proper cleanups on MacOS (i.e. remove a Unix domain socket and
- /// pid file), even though ownership of the native handle itself was passed
- /// to this AsyncHandle instance.
- static AsyncHandle Create(IpcHandle handle, AsyncLoop& async_loop,
- Callback&& callback);
-
- /// Close handle
- void Close();
-
- /// Release native handle ownership to caller.
- /// Note that this cancels any async operation for the handle.
- IpcHandle::HandleType Release();
-
- /// Return true if the native handle is valid, and was not released.
- bool is_valid() const;
-
- /// Return value of native handle, but does not transfer ownership.
- IpcHandle::HandleType native_handle() const;
-
- /// Conversion to bool, equivalent to is_valid().
- operator bool() const { return is_valid(); }
-
- /// Return true if an asynchronous operation was started and
- /// Did not complete yet.
- bool IsRunning() const;
-
- /// Cancel current asynchronous operation, if any.
- void Cancel();
-
- /// Change the callback for this instance.
- AsyncHandle& ResetCallback(Callback&& cb);
-
- /// Reset instance with a new native handle, keeping the same AsyncLoop
- /// reference, and same callback. Calls Cancel() implicitly.
- void ResetHandle(IpcHandle handle);
-
- /// Start an asynchronous read operation. Cancels any previous operation.
- void StartRead(void* buffer, size_t size);
-
- /// Start an asynchronous write operation. Cancels any previous operation.
- void StartWrite(const void* buffer, size_t size);
-
- /// Start an asynchronous connect operation. Cancels any previous operation.
- /// Note that this instance's handle should be the result of an
- /// IpcServiceHandle::AsyncConnectTo() operation.
- void StartConnect();
-
- /// Start an asynchronous accept operation. Note that in case of success,
- /// the client handle should be retrieved with TakeAcceptedHandle();
- /// Cancels any previous operation.
- /// Note that this instance's handle must be the result of an
- /// IpcServiceHandle::BindTo() call.
- void StartAccept();
-
- /// Return the client handle corresponding to the last successful
- /// asynchronous accept operation.
- IpcHandle TakeAcceptedHandle();
-
- /// Return AsyncLoop pointer from this handle. Will be nullptr if
- /// handle is invalid (i.e. after an explicit Close()).
- AsyncLoop& async_loop() const;
-
- /// Opaque type for platform-dependent asynchronous operation.
- class State;
-
- protected:
- /// Private constructor, use AsyncLoop::CreateHandle() to
- /// create a new non-default instance.
- explicit AsyncHandle(std::unique_ptr<State> state);
-
- std::unique_ptr<State> state_;
-};
-
-class AsyncTimer {
- public:
- using Callback = std::function<void(void)>;
-
- // Default constructor.
- AsyncTimer();
-
- // Constructor.
- AsyncTimer(AsyncLoop& async_loop, Callback&& callback);
-
- /// Destructor. Cancels the timer automatically.
- ~AsyncTimer();
-
- /// Move operations are allowed.
- AsyncTimer(AsyncTimer&&) noexcept;
- AsyncTimer& operator=(AsyncTimer&&) noexcept;
-
- /// Return true if this instance is valid (i.e. not default constructed).
- bool is_valid() const { return !!state_; }
- operator bool() const { return is_valid(); }
-
- /// Set the expiration time for this timer. After this the callback
- /// will be invoked once.
- void SetExpirationMs(int64_t expiration_ms);
-
- /// Set a duration after which the timer will expire.
- void SetDurationMs(int64_t duration_ms);
-
- /// Cancel this timer (remove any expiration).
- void Cancel();
-
- /// Close the timer before destruction (makes it invalid).
- void Close();
-
- /// Reset the callback for this instance.
- void ResetCallback(Callback&& callback);
-
- /// Construct a new timer and set its expiration time directly.
- static AsyncTimer CreateWithExpiration(int64_t expiration_ms,
- AsyncLoop& async_loop,
- Callback&& callback);
-
- /// Construct a new timer and set its duration period directly.
- static AsyncTimer CreateWithDuration(int64_t duration_ms,
- AsyncLoop& async_loop,
- Callback&& callback);
-
- /// Retrieve reference to AsyncLoop this timer belongs to.
- /// NOTE: It is a runtime error to call this on an invalid instance.
- AsyncLoop& async_loop() const;
-
- class State;
-
- private:
- std::unique_ptr<State> state_;
-};
-
-/// A class used to create asynchronous operations and wait for their
-/// completion with a possible timeout. This also detect user interruptions
-/// through Ctrl-C / SIGINT / SIGTERM / SIGHUP.
-///
-class AsyncLoop {
- public:
- /// Destructor
- ~AsyncLoop();
-
- /// Retrieve reference to global instance. Created on demand.
- /// This instance always catches interrupts by default.
- static AsyncLoop& Get();
-
- /// Create a new instance independent from the global one. It does _not_
- /// catches interrupts by default. This instance will have its own set of
- /// timer and async IDs.
- static std::unique_ptr<AsyncLoop> CreateLocal();
-
- /// Destroy current global instance. Only use this for testing.
- static void ResetForTesting();
-
-#ifdef _WIN32
- using NativeHandle = HANDLE;
-#else
- using NativeHandle = int;
-#endif
-
- /// Each asynchonous operation is identified by a unique, non-zero, ID.
- using AsyncId = uint32_t;
-
- /// Return current time in milliseconds. Epoch is undetermined
- /// but all values are guaranteed to be non-negative.
- static int64_t NowMs();
-
- /// Possible return values for RunOnce() method.
- enum ExitStatus {
- ExitIdle = 0, // no more async ops or timers to wait for.
- ExitSuccess, // at least one async op completed or timer expired.
- ExitTimeout, // timeout expired.
- ExitInterrupted, // user interruption detected.
- };
-
- /// Run the loop once with a timeout. This function only returns after
- /// a least one async operation / timer has completed, or on expiration,
- /// or a user interrupt. A negative |timeout_ms| value corresponds to no
- /// timeout at all, but the function may return with ExitIdle if there
- /// are no more async operations registered. Note that ExitIdle
- /// _cannot_ be returned if the value of |timeout_ms| is not negative.
- /// Also the function cannot return ExitInterrupted if interrupt catching
- /// is not enabled for this AsyncLoop instance.
- ExitStatus RunOnce(int64_t timeout_ms);
-
- /// Run the loop until a given condition is met, or an interrupt is
- /// detected, or a timeout expires. This will return ExitSuccess,
- /// ExitInterrupted and ExitTimeout respectively.
- ///
- /// Also, if no timeout is specified (i.e. the value of |timeout_ms|
- /// is negative), this will return ExitIdle if there are no more
- /// registered async operations or active timers.
- ///
- /// |condition| is a predicate to check for the condition, whose
- /// value only depends on variables modified by async handlers or
- /// timers.
- ///
- template <typename PREDICATE>
- ExitStatus RunUntil(PREDICATE condition, int64_t timeout_ms = -1) {
- RunUntilState state(timeout_ms);
- do {
- if (condition())
- return ExitSuccess;
- } while (state.LoopAgain(*this));
- return state.status();
- }
-
- /// Clear the interrupt flag after it has been acknowledged by the caller.
- void ClearInterrupt();
-
-#ifndef _WIN32
- /// Return the signal number after RunOnce() returns with ExitInterrupted.
- int GetInterruptSignal() const;
-
- /// Return the process signal mask that was set before this instance was
- /// created (since the constructor blocks all signals).
- sigset_t GetOldSignalMask() const;
-#endif
-
- /// Completely reset the instance, forgetting about all registered
- /// async operations and timers, only the interrupt catcher count is
- /// preserved.
- void Reset();
-
- /// Convenience struct that allows to temporarily redirect interrupts
- /// (i.e. Ctrl-C on Windows, and SIGINT/SIGHUP/SIGTERM on Posix) to
- /// a given AsyncLoop instance. Note that only one AsyncLoop instance can
- /// catch them at any given point in the process.
- ///
- /// Usage is simply:
- ///
- /// {
- /// AsyncLoop::ScopedInterruptCatcher catcher(async_loop);
- ///
- /// ... all interrupts redirect to |async_loop| until the
- /// ... destructor is called.
- /// }
- ///
- /// It is safe to nest multiple catchers, which can even refer to the
- /// same async_loop. Only the most-nested instance will get the interrupts.
- ///
- struct ScopedInterruptCatcher {
- explicit ScopedInterruptCatcher(AsyncLoop& async_loop)
- : async_loop_(async_loop) {
- async_loop_.ChangeInterruptCatcher(true);
- }
-
- ~ScopedInterruptCatcher() { async_loop_.ChangeInterruptCatcher(false); }
-
- AsyncLoop& async_loop_;
- };
-
- private:
- friend class AsyncHandle::State;
- friend class AsyncTimer::State;
- friend class AsyncLoopTimers;
-
- AsyncLoop();
-
- // Used internally to implement AsyncHandles.
- void AttachHandle(AsyncHandle::State* state);
- void DetachHandle(AsyncHandle::State* state);
- void UpdateHandle(AsyncHandle::State* state);
- void CancelHandle(AsyncHandle::State* state);
-
- void AttachTimer(AsyncTimer::State* state);
- void DetachTimer(AsyncTimer::State* state);
- void UpdateTimer(AsyncTimer::State* state);
-
- /// Internal struct used by RunUntil() template instantiations.
- struct RunUntilState {
- /// Constructor sets up the state.
- RunUntilState(int64_t timeout_ms);
-
- /// Call this method to call RunOnce() to drain events or
- /// timers. Returns true to indicate that the function
- /// must be called again, and false in case of exit
- /// condition (reported by status()).
- bool LoopAgain(AsyncLoop& async_loop);
-
- /// Return final status, only valid after LoopAgain()
- /// returns false. Cannot be ExitSuccess.
- ExitStatus status() const { return status_; }
-
- int64_t timeout_ms_;
- int64_t expiration_ms_ = -1;
- ExitStatus status_ = ExitIdle;
- };
-
- /// Used internally by ScopedInterruptCatcher class.
- void ChangeInterruptCatcher(bool increment);
-
- /// Implementation details hidden from this header intentionally.
- class Impl;
- std::unique_ptr<Impl> impl_;
- int interrupt_catcher_count_ = 0;
-};
-
-#endif // NINJA_ASYNC_LOOP_H
diff --git a/src/async_loop_test.cc b/src/async_loop_test.cc
deleted file mode 100644
index c14c08d..0000000
--- a/src/async_loop_test.cc
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2023 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 "async_loop.h"
-
-// This file also includes tests for the AsyncLoopTimers class.
-#include "async_loop_timers.h"
-#include "ipc_handle.h"
-#include "test.h"
-
-static AsyncLoop& GetNewLoop() {
- AsyncLoop::ResetForTesting();
- return AsyncLoop::Get();
-}
-
-TEST(AsyncLoop, NowMs) {
- AsyncLoop& loop = GetNewLoop();
- // Ensure the result of NowMs() is always positive, otherwise many
- // things will not work correctly since a negative expiration date is
- // interpreted as infinite.
- EXPECT_GE(loop.NowMs(), 0LL);
-}
-
-// Helper type to store async operation results.
-struct AsyncResult {
- bool completed = false;
- AsyncError error = 0;
- size_t size = 0;
-
- AsyncHandle::Callback callback() {
- return [this](AsyncError error, size_t size) {
- this->completed = true;
- this->error = error;
- this->size = size;
- };
- }
-};
-
-TEST(AsyncLoop, AsyncHandleStartRead) {
- std::string err_msg;
- IpcHandle read_handle, write_handle;
- bool ret = IpcHandle::CreateAsyncPipe(&read_handle, &write_handle, &err_msg);
- if (!ret)
- fprintf(stderr, "CreatePipe() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(ret);
-
- AsyncLoop& loop = GetNewLoop();
- char read_buffer[10] = {};
-
- AsyncResult result;
- auto async_handle =
- AsyncHandle::Create(std::move(read_handle), loop, result.callback());
- async_handle.StartRead(read_buffer, sizeof(read_buffer));
-
- // Nothing should be accepted in the next 50ms
- auto status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitTimeout, status);
- EXPECT_FALSE(result.completed);
-
- // Write to the pipe.
- char buf[1] = { 'x' };
- EXPECT_EQ(1, write_handle.Write(buf, sizeof(buf), &err_msg));
-
- status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
- EXPECT_TRUE(result.completed);
- EXPECT_EQ(0u, result.error);
- EXPECT_EQ(1u, result.size);
- EXPECT_EQ('x', read_buffer[0]);
-}
-
-TEST(AsyncLoop, AsyncHandleStartWrite) {
- std::string err_msg;
- IpcHandle read_handle, write_handle;
- bool ret = IpcHandle::CreateAsyncPipe(&read_handle, &write_handle, &err_msg);
- if (!ret)
- fprintf(stderr, "CreatePipe() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(ret);
-
- AsyncLoop& loop = GetNewLoop();
- const char buffer[] = "foo bar";
-
- AsyncResult result;
- auto async_handle =
- AsyncHandle::Create(std::move(write_handle), loop, result.callback());
- async_handle.StartWrite(buffer, sizeof(buffer));
-
- // Writing should work directly.
- auto status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
-
- EXPECT_TRUE(result.completed);
- EXPECT_EQ(0u, result.error);
- EXPECT_EQ(sizeof(buffer), result.size);
-
- // Read from pipe synchronously.
- char read_buffer[sizeof(buffer)];
- ssize_t actual_bytes =
- read_handle.Read(read_buffer, sizeof(buffer), &err_msg);
- EXPECT_EQ(sizeof(buffer), static_cast<size_t>(actual_bytes));
-}
-
-TEST(AsyncLoop, AsyncHandleStartAccept) {
- std::string err_msg;
- auto server_handle = IpcServiceHandle::BindTo("test_service", &err_msg);
- if (!server_handle)
- fprintf(stderr, "BindTo() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(server_handle);
-
- AsyncLoop& loop = GetNewLoop();
-
- AsyncResult result;
- auto async_handle =
- AsyncHandle::Create(std::move(server_handle), loop, result.callback());
- async_handle.StartAccept();
-
- // Nothing should be accepted in the next 50ms
- auto status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitTimeout, status);
- EXPECT_FALSE(result.completed);
-
- // Synchronous connect.
- IpcHandle client = IpcServiceHandle::ConnectTo("test_service", &err_msg);
- if (!client)
- fprintf(stderr, "ConnectTo() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(client);
-
- status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
- EXPECT_TRUE(result.completed);
- EXPECT_EQ(0, result.error);
-
- IpcHandle accept_handle = async_handle.TakeAcceptedHandle();
- EXPECT_TRUE(accept_handle);
- EXPECT_NE(accept_handle.native_handle(), client.native_handle());
-}
-
-TEST(AsyncLoop, AsyncHandleStartConnect) {
- std::string err_msg;
- auto server_handle = IpcServiceHandle::BindTo("test_service", &err_msg);
- if (!server_handle)
- fprintf(stderr, "BindTo() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(server_handle);
-
- bool did_connect = false;
- IpcHandle client_handle =
- IpcServiceHandle::AsyncConnectTo("test_service", &did_connect, &err_msg);
- if (!client_handle)
- fprintf(stderr, "AsyncClientTo() error: %s\n", err_msg.c_str());
- ASSERT_TRUE(client_handle);
-
- (void)did_connect; // ignored intentionally.
-
- AsyncLoop& loop = GetNewLoop();
-
- AsyncResult result;
- auto async_handle =
- AsyncHandle::Create(std::move(client_handle), loop, result.callback());
- async_handle.StartConnect();
-
- auto status = loop.RunOnce(50);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
- EXPECT_TRUE(result.completed);
- EXPECT_EQ(0, result.error);
- EXPECT_EQ(0, result.size);
-}
-
-TEST(AsyncLoop, RunUntil) {
- AsyncLoop& loop = GetNewLoop();
-
- auto always_false = []() { return false; };
-
- auto status = loop.RunUntil(always_false, -1);
- EXPECT_EQ(AsyncLoop::ExitIdle, status);
-
- status = loop.RunUntil(always_false, 10);
- EXPECT_EQ(AsyncLoop::ExitTimeout, status);
-
- bool flag = false;
- auto flag_is_set = [&flag]() { return flag; };
- AsyncTimer timer(loop, [&flag]() { flag = true; });
-
- timer.SetDurationMs(100LL);
- status = loop.RunUntil(flag_is_set, -1);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
-
- flag = false;
- timer.SetDurationMs(1000LL);
- status = loop.RunUntil(flag_is_set, 10LL);
- EXPECT_EQ(AsyncLoop::ExitTimeout, status);
-
- status = loop.RunUntil(flag_is_set, -1);
- EXPECT_EQ(AsyncLoop::ExitSuccess, status);
-}
-
-TEST(AsyncLoop, TimerTest) {
- AsyncLoop& loop = GetNewLoop();
- int counter = 0;
- AsyncTimer timer_1(loop, [&counter]() { counter += 1; });
- timer_1.SetDurationMs(100LL);
- EXPECT_EQ(AsyncLoop::ExitTimeout, loop.RunOnce(0));
- EXPECT_EQ(0, counter);
-
- EXPECT_EQ(AsyncLoop::ExitSuccess, loop.RunOnce(200));
- EXPECT_EQ(1, counter);
-}
-
-TEST(AsyncLoopTimers, Test) {
- AsyncLoopTimers timers;
-
- EXPECT_EQ(-1LL, timers.ComputeNextExpiration());
-
- int counter = 0;
-
- // Create local loop, required by AsyncTimer::State construtor
- // but not used by the test though because its RunOnce() method is never
- // called.
- auto loop = AsyncLoop::CreateLocal();
-
- auto timer_1 = std::unique_ptr<AsyncTimer::State>(
- new AsyncTimer::State(*loop, [&counter]() { counter += 1; }));
-
- auto timer_2 = std::unique_ptr<AsyncTimer::State>(
- new AsyncTimer::State(*loop, [&counter]() { counter += 100; }));
-
- timers.AttachTimer(timer_1.get());
- timers.AttachTimer(timer_2.get());
-
- EXPECT_EQ(-1LL, timers.ComputeNextExpiration());
-
- timer_1->SetExpirationMs(100LL);
-
- EXPECT_EQ(100LL, timers.ComputeNextExpiration());
- EXPECT_EQ(0, counter);
-
- timer_2->SetExpirationMs(200LL);
- EXPECT_EQ(100LL, timers.ComputeNextExpiration());
- EXPECT_EQ(0, counter);
-
- EXPECT_FALSE(timers.ProcessExpiration(99LL));
- EXPECT_EQ(100LL, timers.ComputeNextExpiration());
- EXPECT_EQ(0, counter);
-
- EXPECT_TRUE(timers.ProcessExpiration(100LL));
- EXPECT_EQ(1, counter);
- EXPECT_EQ(200LL, timers.ComputeNextExpiration());
-
- EXPECT_TRUE(timers.ProcessExpiration(200LL));
- EXPECT_EQ(101, counter);
- EXPECT_EQ(-1LL, timers.ComputeNextExpiration());
-
- timer_1->SetExpirationMs(200LL);
- timers.DetachTimer(timer_1.get());
- timers.DetachTimer(timer_2.get());
-
- EXPECT_EQ(-1LL, timers.ComputeNextExpiration());
-
- EXPECT_FALSE(timers.ProcessExpiration(300LL));
- EXPECT_EQ(101, counter);
-}
diff --git a/src/async_loop_timers.h b/src/async_loop_timers.h
deleted file mode 100644
index 0b93020..0000000
--- a/src/async_loop_timers.h
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2023 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_ASYNC_LOOP_TIMERS_H
-#define NINJA_ASYNC_LOOP_TIMERS_H
-
-#include <algorithm>
-#include <vector>
-
-#include "async_loop.h"
-
-class AsyncTimer::State {
- public:
- State(AsyncLoop& async_loop, AsyncTimer::Callback&& callback)
- : async_loop_(async_loop), callback_(std::move(callback)) {
- async_loop_.AttachTimer(this);
- }
-
- ~State() { async_loop_.DetachTimer(this); }
-
- AsyncLoop& async_loop() const { return async_loop_; }
-
- // Return true if this instance is active.
- bool active() const { return expiration_ms_ >= 0; }
-
- int64_t expiration_ms() const { return expiration_ms_; }
-
- void ResetCallback(AsyncTimer::Callback&& callback) {
- callback_ = std::move(callback);
- }
-
- void SetExpirationMs(int64_t expiration_ms) {
- expiration_ms_ = expiration_ms;
- async_loop_.UpdateTimer(this);
- }
-
- void SetDurationMs(int64_t duration_ms) {
- SetExpirationMs(async_loop_.NowMs() + duration_ms);
- }
-
- void Cancel() { SetExpirationMs(-1); }
-
- void Invoke() {
- expiration_ms_ = -1;
- callback_();
- }
-
- private:
- AsyncLoop& async_loop_;
- int64_t expiration_ms_ = -1;
- AsyncTimer::Callback callback_;
-};
-
-/// Common class used by all AsyncLoop implementation to manage timers.
-class AsyncLoopTimers {
- public:
- void AttachTimer(AsyncTimer::State* timer) { timers_.push_back(timer); }
-
- void DetachTimer(AsyncTimer::State* timer) {
- pending_.erase(timer);
- timers_.erase(timer);
- }
-
- void UpdateTimer(AsyncTimer::State* timer) { pending_.erase(timer); }
-
- /// Compute the next expiration time in milliseconds, or return -1 if
- /// not timer is active.
- int64_t ComputeNextExpiration() const {
- int64_t result = -1;
- for (const auto* timer : timers_) {
- if (!timer->active())
- continue;
- if (result < 0)
- result = timer->expiration_ms();
- else if (result > timer->expiration_ms())
- result = timer->expiration_ms();
- }
- return result;
- }
-
- /// Compute the set of pending timers at a given time.
- /// Return true if any timer expired, false otherwise.
- bool ProcessExpiration(int64_t now_ms) {
- // First compute the list of pending timers.
- pending_.clear();
- for (auto* timer : timers_) {
- if (timer->active() && timer->expiration_ms() <= now_ms)
- pending_.push_back(timer);
- }
-
- if (pending_.empty())
- return false;
-
- // Then process it. Each callback can end up calling ResetExpiration()
- // or Destroy() which will remove or deactivate pending timers so do
- // not assume that each id in the list is valid on each iteration.
- do {
- AsyncTimer::State* state = pending_.back();
- pending_.pop_back();
- state->Invoke();
- } while (!pending_.empty());
-
- return true;
- }
-
- // Return true if there are no timers in this set.
- bool empty() const { return timers_.empty(); }
-
- private:
- struct TimerList : public std::vector<AsyncTimer::State*> {
- void erase(AsyncTimer::State* state) {
- auto it = std::find(begin(), end(), state);
- if (it != end()) {
- *it = back();
- pop_back();
- }
- }
- };
-
- TimerList timers_;
- TimerList pending_;
-};
-
-#endif // NINJA_ASYNC_LOOP_TIMERS_H
diff --git a/src/browse.cc b/src/browse.cc
deleted file mode 100644
index 76bee07..0000000
--- a/src/browse.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.
-
-#include "browse.h"
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <vector>
-
-#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.
- // (Actually the Python process becomes the parent.)
- int pipefd[2];
- if (pipe(pipefd) < 0) {
- perror("ninja: pipe");
- return;
- }
-
- pid_t pid = fork();
- if (pid < 0) {
- perror("ninja: fork");
- return;
- }
-
- if (pid > 0) { // Parent.
- close(pipefd[1]);
- do {
- if (dup2(pipefd[0], 0) < 0) {
- perror("ninja: dup2");
- break;
- }
-
- std::vector<const char *> command;
- command.push_back(NINJA_PYTHON);
- command.push_back("-");
- command.push_back("--ninja-command");
- command.push_back(ninja_command);
- command.push_back("-f");
- command.push_back(input_file);
- for (int i = 0; i < argc; i++) {
- command.push_back(argv[i]);
- }
- command.push_back(NULL);
- execvp(command[0], (char**)&command[0]);
- if (errno == ENOENT) {
- printf("ninja: %s is required for the browse tool\n", NINJA_PYTHON);
- } else {
- perror("ninja: execvp");
- }
- } while (false);
- _exit(1);
- } else { // Child.
- close(pipefd[0]);
-
- // Write the script file into the stdin of the Python process.
- ssize_t len = write(pipefd[1], kBrowsePy, sizeof(kBrowsePy));
- if (len < (ssize_t)sizeof(kBrowsePy))
- perror("ninja: write");
- close(pipefd[1]);
- exit(0);
- }
-}
diff --git a/src/browse.h b/src/browse.h
deleted file mode 100644
index 8d6d285..0000000
--- a/src/browse.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.
-
-#ifndef NINJA_BROWSE_H_
-#define NINJA_BROWSE_H_
-
-struct State;
-
-/// Run in "browse" mode, which execs a Python webserver.
-/// \a ninja_command is the command used to invoke ninja.
-/// \a args are the number of arguments to be passed to the Python script.
-/// \a argv are arguments to be passed to the Python script.
-/// This function does not return if it runs successfully.
-void RunBrowsePython(State* state, const char* ninja_command,
- const char* input_file, int argc, char* argv[]);
-
-#endif // NINJA_BROWSE_H_
diff --git a/src/browse.py b/src/browse.py
deleted file mode 100755
index b125e80..0000000
--- a/src/browse.py
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2001 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.
-
-"""Simple web server for browsing dependency graph data.
-
-This script is inlined into the final executable and spawned by
-it when needed.
-"""
-
-try:
- import http.server as httpserver
- import socketserver
-except ImportError:
- import BaseHTTPServer as httpserver
- import SocketServer as socketserver
-import argparse
-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:
- from urllib2 import unquote
-from collections import namedtuple
-
-Node = namedtuple('Node', ['inputs', 'rule', 'target', 'outputs'])
-
-# Ideally we'd allow you to navigate to a build edge or a build node,
-# with appropriate views for each. But there's no way to *name* a build
-# edge so we can only display nodes.
-#
-# For a given node, it has at most one input edge, which has n
-# different inputs. This becomes node.inputs. (We leave out the
-# outputs of the input edge due to what follows.) The node can have
-# multiple dependent output edges. Rather than attempting to display
-# those, they are summarized by taking the union of all their outputs.
-#
-# This means there's no single view that shows you all inputs and outputs
-# of an edge. But I think it's less confusing than alternatives.
-
-def match_strip(line, prefix):
- if not line.startswith(prefix):
- return (False, line)
- return (True, line[len(prefix):])
-
-def html_escape(text):
- return escape(text, quote=True)
-
-def parse(text):
- lines = iter(text.split('\n'))
-
- target = None
- rule = None
- inputs = []
- outputs = []
-
- try:
- target = next(lines)[:-1] # strip trailing colon
-
- line = next(lines)
- (match, rule) = match_strip(line, ' input: ')
- if match:
- (match, line) = match_strip(next(lines), ' ')
- while match:
- type = None
- (match, line) = match_strip(line, '| ')
- if match:
- type = 'implicit'
- (match, line) = match_strip(line, '|| ')
- if match:
- type = 'order-only'
- inputs.append((line, type))
- (match, line) = match_strip(next(lines), ' ')
-
- match, _ = match_strip(line, ' outputs:')
- if match:
- (match, line) = match_strip(next(lines), ' ')
- while match:
- outputs.append(line)
- (match, line) = match_strip(next(lines), ' ')
- except StopIteration:
- pass
-
- return Node(inputs, rule, target, outputs)
-
-def create_page(body):
- return '''<!DOCTYPE html>
-<style>
-body {
- font-family: sans;
- font-size: 0.8em;
- margin: 4ex;
-}
-h1 {
- font-weight: normal;
- font-size: 140%;
- text-align: center;
- margin: 0;
-}
-h2 {
- font-weight: normal;
- font-size: 120%;
-}
-tt {
- font-family: WebKitHack, monospace;
- white-space: nowrap;
-}
-.filelist {
- -webkit-columns: auto 2;
-}
-</style>
-''' + body
-
-def generate_html(node):
- document = ['<h1><tt>%s</tt></h1>' % html_escape(node.target)]
-
- if node.inputs:
- document.append('<h2>target is built using rule <tt>%s</tt> of</h2>' %
- html_escape(node.rule))
- if len(node.inputs) > 0:
- document.append('<div class=filelist>')
- for input, type in sorted(node.inputs):
- extra = ''
- if type:
- extra = ' (%s)' % html_escape(type)
- document.append('<tt><a href="?%s">%s</a>%s</tt><br>' %
- (html_escape(input), html_escape(input), extra))
- document.append('</div>')
-
- if node.outputs:
- document.append('<h2>dependent edges build:</h2>')
- document.append('<div class=filelist>')
- for output in sorted(node.outputs):
- document.append('<tt><a href="?%s">%s</a></tt><br>' %
- (html_escape(output), html_escape(output)))
- document.append('</div>')
-
- return '\n'.join(document)
-
-def ninja_dump(target):
- cmd = [args.ninja_command, '-f', args.f, '-t', 'query', target]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- universal_newlines=True)
- return proc.communicate() + (proc.returncode,)
-
-class RequestHandler(httpserver.BaseHTTPRequestHandler):
- def do_GET(self):
- assert self.path[0] == '/'
- target = unquote(self.path[1:])
-
- if target == '':
- self.send_response(302)
- self.send_header('Location', '?' + args.initial_target)
- self.end_headers()
- return
-
- if not target.startswith('?'):
- self.send_response(404)
- self.end_headers()
- return
- target = target[1:]
-
- ninja_output, ninja_error, exit_code = ninja_dump(target)
- if exit_code == 0:
- page_body = generate_html(parse(ninja_output.strip()))
- else:
- # Relay ninja's error message.
- page_body = '<h1><tt>%s</tt></h1>' % html_escape(ninja_error)
-
- self.send_response(200)
- self.end_headers()
- self.wfile.write(create_page(page_body).encode('utf-8'))
-
- def log_message(self, format, *args):
- pass # Swallow console spam.
-
-parser = argparse.ArgumentParser(prog='ninja -t browse')
-parser.add_argument('--port', '-p', default=8000, type=int,
- help='Port number to use (default %(default)d)')
-parser.add_argument('--hostname', '-a', default='localhost', type=str,
- help='Hostname to bind to (default %(default)s)')
-parser.add_argument('--no-browser', action='store_true',
- help='Do not open a webbrowser on startup.')
-
-parser.add_argument('--ninja-command', default='ninja',
- help='Path to ninja binary (default %(default)s)')
-parser.add_argument('-f', default='build.ninja',
- help='Path to build.ninja file (default %(default)s)')
-parser.add_argument('initial_target', default='all', nargs='?',
- help='Initial target to show (default %(default)s)')
-
-class HTTPServer(socketserver.ThreadingMixIn, httpserver.HTTPServer):
- # terminate server immediately when Python exits.
- daemon_threads = True
-
-args = parser.parse_args()
-port = args.port
-hostname = args.hostname
-httpd = HTTPServer((hostname,port), RequestHandler)
-try:
- if hostname == "":
- hostname = socket.gethostname()
- print('Web server running on %s:%d, ctl-C to abort...' % (hostname,port) )
- print('Web server pid %d' % os.getpid(), file=sys.stderr )
- if not args.no_browser:
- webbrowser.open_new('http://%s:%s' % (hostname, port) )
- httpd.serve_forever()
-except KeyboardInterrupt:
- print()
- pass # Swallow console spam.
-
-
diff --git a/src/build.cc b/src/build.cc
deleted file mode 100644
index bca3e8d..0000000
--- a/src/build.cc
+++ /dev/null
@@ -1,964 +0,0 @@
-// 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.
-
-#include "build.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <functional>
-
-#if defined(__SVR4) && defined(__sun)
-#include <sys/termios.h>
-#endif
-
-#include "async_loop.h"
-#include "build_config.h"
-#include "build_log.h"
-#include "clparser.h"
-#include "debug_flags.h"
-#include "depfile_parser.h"
-#include "deps_log.h"
-#include "disk_interface.h"
-#include "graph.h"
-#include "metrics.h"
-#include "state.h"
-#include "status.h"
-#include "subprocess.h"
-#include "util.h"
-
-using namespace std;
-
-namespace {
-
-/// A CommandRunner that doesn't actually run the commands.
-struct DryRunCommandRunner : public CommandRunner {
- virtual ~DryRunCommandRunner() {}
-
- // Overridden from CommandRunner:
- bool CanRunMore() const override;
- bool StartCommand(Edge* edge) override;
- bool WaitForCommand(Result* result) override;
-
- private:
- queue<Edge*> finished_;
-};
-
-bool DryRunCommandRunner::CanRunMore() const {
- return true;
-}
-
-bool DryRunCommandRunner::StartCommand(Edge* edge) {
- finished_.push(edge);
- return true;
-}
-
-bool DryRunCommandRunner::WaitForCommand(Result* result) {
- if (finished_.empty())
- return false;
-
- result->status = ExitSuccess;
- result->edge = finished_.front();
- finished_.pop();
- return true;
-}
-
-} // namespace
-
-Plan::Plan(Builder* builder)
- : builder_(builder)
- , command_edges_(0)
- , wanted_edges_(0)
-{}
-
-void Plan::Reset() {
- command_edges_ = 0;
- wanted_edges_ = 0;
- ready_.clear();
- want_.clear();
-}
-
-bool Plan::AddTarget(const Node* target, string* err) {
- return AddSubTarget(target, NULL, err, NULL);
-}
-
-bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err,
- set<Edge*>* dyndep_walk) {
- Edge* edge = node->in_edge();
- if (!edge) {
- // Leaf node, this can be either a regular input from the manifest
- // (e.g. a source file), or an implicit input from a depfile or dyndep
- // file. In the first case, a dirty flag means the file is missing,
- // and the build should stop. In the second, do not do anything here
- // since there is no producing edge to add to the plan.
- if (node->dirty() && !node->generated_by_dep_loader()) {
- string referenced;
- if (dependent)
- referenced = ", needed by '" + dependent->path() + "',";
- *err = "'" + node->path() + "'" + referenced + " missing "
- "and no known rule to make it";
- }
- return false;
- }
-
- if (edge->outputs_ready())
- return false; // Don't need to do anything.
-
- // If an entry in want_ does not already exist for edge, create an entry which
- // maps to kWantNothing, indicating that we do not want to build this entry itself.
- pair<map<Edge*, Want>::iterator, bool> want_ins =
- want_.insert(make_pair(edge, kWantNothing));
- Want& want = want_ins.first->second;
-
- if (dyndep_walk && want == kWantToFinish)
- return false; // Don't need to do anything with already-scheduled edge.
-
- // If we do need to build edge and we haven't already marked it as wanted,
- // mark it now.
- if (node->dirty() && want == kWantNothing) {
- want = kWantToStart;
- EdgeWanted(edge);
- if (!dyndep_walk && edge->AllInputsReady())
- ScheduleWork(want_ins.first);
- }
-
- if (dyndep_walk)
- dyndep_walk->insert(edge);
-
- if (!want_ins.second)
- return true; // We've already processed the inputs.
-
- for (vector<Node*>::iterator i = edge->inputs_.begin();
- i != edge->inputs_.end(); ++i) {
- if (!AddSubTarget(*i, node, err, dyndep_walk) && !err->empty())
- return false;
- }
-
- return true;
-}
-
-void Plan::EdgeWanted(const Edge* edge) {
- ++wanted_edges_;
- if (!edge->is_phony())
- ++command_edges_;
-}
-
-Edge* Plan::FindWork() {
- if (ready_.empty())
- return NULL;
- EdgeSet::iterator e = ready_.begin();
- Edge* edge = *e;
- ready_.erase(e);
- return edge;
-}
-
-void Plan::ScheduleWork(map<Edge*, Want>::iterator want_e) {
- if (want_e->second == kWantToFinish) {
- // This edge has already been scheduled. We can get here again if an edge
- // and one of its dependencies share an order-only input, or if a node
- // duplicates an out edge (see https://github.com/ninja-build/ninja/pull/519).
- // Avoid scheduling the work again.
- return;
- }
- assert(want_e->second == kWantToStart);
- want_e->second = kWantToFinish;
-
- Edge* edge = want_e->first;
- Pool* pool = edge->pool();
- if (pool->ShouldDelayEdge()) {
- pool->DelayEdge(edge);
- pool->RetrieveReadyEdges(&ready_);
- } else {
- pool->EdgeScheduled(*edge);
- ready_.insert(edge);
- }
-}
-
-bool Plan::EdgeFinished(Edge* edge, EdgeResult result, string* err) {
- map<Edge*, Want>::iterator e = want_.find(edge);
- assert(e != want_.end());
- bool directly_wanted = e->second != kWantNothing;
-
- // See if this job frees up any delayed jobs.
- if (directly_wanted)
- edge->pool()->EdgeFinished(*edge);
- edge->pool()->RetrieveReadyEdges(&ready_);
-
- // The rest of this function only applies to successful commands.
- if (result != kEdgeSucceeded)
- return true;
-
- if (directly_wanted)
- --wanted_edges_;
- want_.erase(e);
- edge->outputs_ready_ = true;
-
- // Check off any nodes we were waiting for with this edge.
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (!NodeFinished(*o, err))
- return false;
- }
- return true;
-}
-
-bool Plan::NodeFinished(Node* node, string* err) {
- // If this node provides dyndep info, load it now.
- if (node->dyndep_pending()) {
- assert(builder_ && "dyndep requires Plan to have a Builder");
- // Load the now-clean dyndep file. This will also update the
- // build plan and schedule any new work that is ready.
- return builder_->LoadDyndeps(node, err);
- }
-
- // See if we we want any edges from this node.
- for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
- oe != node->out_edges().end(); ++oe) {
- map<Edge*, Want>::iterator want_e = want_.find(*oe);
- if (want_e == want_.end())
- continue;
-
- // See if the edge is now ready.
- if (!EdgeMaybeReady(want_e, err))
- return false;
- }
- return true;
-}
-
-bool Plan::EdgeMaybeReady(map<Edge*, Want>::iterator want_e, string* err) {
- Edge* edge = want_e->first;
- if (edge->AllInputsReady()) {
- if (want_e->second != kWantNothing) {
- ScheduleWork(want_e);
- } else {
- // We do not need to build this edge, but we might need to build one of
- // its dependents.
- if (!EdgeFinished(edge, kEdgeSucceeded, err))
- return false;
- }
- }
- return true;
-}
-
-bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) {
- node->set_dirty(false);
-
- for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
- oe != node->out_edges().end(); ++oe) {
- // Don't process edges that we don't actually want.
- map<Edge*, Want>::iterator want_e = want_.find(*oe);
- if (want_e == want_.end() || want_e->second == kWantNothing)
- continue;
-
- // Don't attempt to clean an edge if it failed to load deps.
- if ((*oe)->deps_missing_)
- continue;
-
- // If all non-order-only inputs for this edge are now clean,
- // we might have changed the dirty state of the outputs.
- vector<Node*>::iterator
- begin = (*oe)->inputs_.begin(),
- end = (*oe)->inputs_.end() - (*oe)->order_only_deps_;
-#if __cplusplus < 201703L
-#define MEM_FN mem_fun
-#else
-#define MEM_FN mem_fn // mem_fun was removed in C++17.
-#endif
- if (find_if(begin, end, MEM_FN(&Node::dirty)) == end) {
- // Recompute most_recent_input.
- Node* most_recent_input = NULL;
- for (vector<Node*>::iterator i = begin; i != end; ++i) {
- if (!most_recent_input || (*i)->mtime() > most_recent_input->mtime())
- most_recent_input = *i;
- }
-
- // Now, this edge is dirty if any of the outputs are dirty.
- // If the edge isn't dirty, clean the outputs and mark the edge as not
- // wanted.
- bool outputs_dirty = false;
- if (!scan->RecomputeOutputsDirty(*oe, most_recent_input,
- &outputs_dirty, err)) {
- return false;
- }
- if (!outputs_dirty) {
- for (vector<Node*>::iterator o = (*oe)->outputs_.begin();
- o != (*oe)->outputs_.end(); ++o) {
- if (!CleanNode(scan, *o, err))
- return false;
- }
-
- want_e->second = kWantNothing;
- --wanted_edges_;
- if (!(*oe)->is_phony())
- --command_edges_;
- }
- }
- }
- return true;
-}
-
-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.
- if (!RefreshDyndepDependents(scan, node, err))
- return false;
-
- // We loaded dyndep information for those out_edges of the dyndep node that
- // specify the node in a dyndep binding, but they may not be in the plan.
- // Starting with those already in the plan, walk newly-reachable portion
- // of the graph through the dyndep-discovered dependencies.
-
- // Find edges in the the build plan for which we have new dyndep info.
- std::vector<DyndepFile::const_iterator> dyndep_roots;
- for (DyndepFile::const_iterator oe = ddf.begin(); oe != ddf.end(); ++oe) {
- Edge* edge = oe->first;
-
- // If the edge outputs are ready we do not need to consider it here.
- if (edge->outputs_ready())
- continue;
-
- map<Edge*, Want>::iterator want_e = want_.find(edge);
-
- // If the edge has not been encountered before then nothing already in the
- // plan depends on it so we do not need to consider the edge yet either.
- if (want_e == want_.end())
- continue;
-
- // This edge is already in the plan so queue it for the walk.
- dyndep_roots.push_back(oe);
- }
-
- // Walk dyndep-discovered portion of the graph to add it to the build plan.
- std::set<Edge*> dyndep_walk;
- for (std::vector<DyndepFile::const_iterator>::iterator
- oei = dyndep_roots.begin(); oei != dyndep_roots.end(); ++oei) {
- DyndepFile::const_iterator oe = *oei;
- for (vector<Node*>::const_iterator i = oe->second.implicit_inputs_.begin();
- i != oe->second.implicit_inputs_.end(); ++i) {
- if (!AddSubTarget(*i, oe->first->outputs_[0], err, &dyndep_walk) &&
- !err->empty())
- return false;
- }
- }
-
- // Add out edges from this node that are in the plan (just as
- // Plan::NodeFinished would have without taking the dyndep code path).
- for (vector<Edge*>::const_iterator oe = node->out_edges().begin();
- oe != node->out_edges().end(); ++oe) {
- map<Edge*, Want>::iterator want_e = want_.find(*oe);
- if (want_e == want_.end())
- continue;
- dyndep_walk.insert(want_e->first);
- }
-
- // See if any encountered edges are now ready.
- for (set<Edge*>::iterator wi = dyndep_walk.begin();
- wi != dyndep_walk.end(); ++wi) {
- map<Edge*, Want>::iterator want_e = want_.find(*wi);
- if (want_e == want_.end())
- continue;
- if (!EdgeMaybeReady(want_e, err))
- return false;
- }
-
- return true;
-}
-
-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.
- set<Node*> dependents;
- UnmarkDependents(node, &dependents);
-
- // Update the dirty state of all dependents and check if their edges
- // have become wanted.
- for (set<Node*>::iterator i = dependents.begin();
- i != dependents.end(); ++i) {
- Node* n = *i;
-
- // Check if this dependent node is now dirty. Also checks for new cycles.
- std::vector<Node*> validation_nodes;
- if (!scan->RecomputeDirty(n, &validation_nodes, err))
- return false;
-
- // Add any validation nodes found during RecomputeDirty as new top level
- // targets.
- for (std::vector<Node*>::iterator v = validation_nodes.begin();
- v != validation_nodes.end(); ++v) {
- if (Edge* in_edge = (*v)->in_edge()) {
- if (!in_edge->outputs_ready() &&
- !AddTarget(*v, err)) {
- return false;
- }
- }
- }
- if (!n->dirty())
- continue;
-
- // This edge was encountered before. However, we may not have wanted to
- // build it if the outputs were not known to be dirty. With dyndep
- // information an output is now known to be dirty, so we want the edge.
- Edge* edge = n->in_edge();
- assert(edge && !edge->outputs_ready());
- map<Edge*, Want>::iterator want_e = want_.find(edge);
- assert(want_e != want_.end());
- if (want_e->second == kWantNothing) {
- want_e->second = kWantToStart;
- EdgeWanted(edge);
- }
- }
- return true;
-}
-
-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;
-
- map<Edge*, Want>::iterator want_e = want_.find(edge);
- if (want_e == want_.end())
- continue;
-
- if (edge->mark_ != Edge::VisitNone) {
- edge->mark_ = Edge::VisitNone;
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (dependents->insert(*o).second)
- UnmarkDependents(*o, dependents);
- }
- }
- }
-}
-
-void Plan::Dump() const {
- printf("pending: %d\n", (int)want_.size());
- for (map<Edge*, Want>::const_iterator e = want_.begin(); e != want_.end(); ++e) {
- if (e->second != kWantNothing)
- printf("want ");
- e->first->Dump();
- }
- printf("ready: %d\n", (int)ready_.size());
-}
-
-struct RealCommandRunner : public CommandRunner {
- RealCommandRunner(const BuildConfig& config, DiskInterface& disk_interface)
- : config_(config), subprocs_(config_.environment),
- disk_interface_(disk_interface) {}
- virtual ~RealCommandRunner() {}
- bool CanRunMore() const override;
- bool StartCommand(Edge* edge) override;
- bool WaitForCommand(Result* result) override;
- vector<Edge*> GetActiveEdges() override;
- void Abort() override;
-
- const BuildConfig& config_;
- DiskInterface& disk_interface_;
- SubprocessSet subprocs_;
- map<const Subprocess*, Edge*> subproc_to_edge_;
-};
-
-vector<Edge*> RealCommandRunner::GetActiveEdges() {
- std::vector<Edge*> edges;
- edges.reserve(subproc_to_edge_.size());
- for (const auto& pair : subproc_to_edge_)
- edges.push_back(pair.second);
- return edges;
-}
-
-void RealCommandRunner::Abort() {
- subprocs_.Clear();
-}
-
-bool RealCommandRunner::CanRunMore() const {
- size_t subproc_number =
- subprocs_.running_.size() + subprocs_.finished_.size();
- return (int)subproc_number < config_.parallelism
- && ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
- || GetLoadAverage() < config_.max_load_average);
-}
-
-bool RealCommandRunner::StartCommand(Edge* edge) {
- string command = edge->EvaluateCommand();
- const Subprocess* subproc = subprocs_.Add(command, edge->use_console());
- if (!subproc)
- return false;
-
- subproc_to_edge_.insert(std::make_pair(subproc, edge));
- return true;
-}
-
-bool RealCommandRunner::WaitForCommand(Result* result) {
- std::unique_ptr<Subprocess> subproc;
- while (!(subproc = subprocs_.NextFinished())) {
- bool interrupted = subprocs_.DoWork();
-
- // Launched sub-commands may have modified file timestamps.
- disk_interface_.Sync();
-
- if (interrupted) {
- result->status = ExitInterrupted;
- return false;
- }
- }
-
- result->status = subproc->Finish();
- result->output = subproc->GetOutput();
-
- auto e = subproc_to_edge_.find(subproc.get());
- result->edge = e->second;
- subproc_to_edge_.erase(e);
-
- return true;
-}
-
-Builder::Builder(State* state, const BuildConfig& config,
- BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface, Status *status,
- int64_t start_time_millis)
- : state_(state), config_(config), plan_(this), status_(status),
- start_time_millis_(start_time_millis), disk_interface_(disk_interface),
- scan_(state, build_log, deps_log, disk_interface,
- &config_.depfile_parser_options) {
- lock_file_path_ = ".ninja_lock";
- string build_dir = state_->bindings().LookupVariable("builddir");
- if (!build_dir.empty())
- lock_file_path_ = build_dir + "/" + lock_file_path_;
-}
-
-Builder::~Builder() {
- Cleanup();
-
- // Remove lock file now.
- string err;
- if (disk_interface_->Stat(lock_file_path_, &err) > 0) {
- disk_interface_->RemoveFile(lock_file_path_);
- }
-}
-
-void Builder::Cleanup() {
- if (command_runner_.get()) {
- vector<Edge*> active_edges = command_runner_->GetActiveEdges();
- command_runner_->Abort();
-
- for (vector<Edge*>::iterator e = active_edges.begin();
- e != active_edges.end(); ++e) {
- string depfile = (*e)->GetUnescapedDepfile();
- for (vector<Node*>::iterator o = (*e)->outputs_.begin();
- o != (*e)->outputs_.end(); ++o) {
- // Only delete this output if it was actually modified. This is
- // important for things like the generator where we don't want to
- // delete the manifest file if we can avoid it. But if the rule
- // uses a depfile, always delete. (Consider the case where we
- // need to rebuild an output because of a modified header file
- // mentioned in a depfile, and the command touches its depfile
- // but is interrupted before it touches its output file.)
- string err;
- TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), &err);
- if (new_mtime == -1) // Log and ignore Stat() errors.
- status_->Error("%s", err.c_str());
- if (!depfile.empty() || (*o)->mtime() != new_mtime)
- disk_interface_->RemoveFile((*o)->path());
- }
- if (!depfile.empty())
- disk_interface_->RemoveFile(depfile);
- }
- }
-}
-
-Node* Builder::AddTarget(const string& name, string* err) {
- Node* node = state_->LookupNode(name);
- if (!node) {
- *err = "unknown target: '" + name + "'";
- return NULL;
- }
- if (!AddTarget(node, err))
- return NULL;
- return node;
-}
-
-bool Builder::AddTarget(Node* target, string* err) {
- std::vector<Node*> validation_nodes;
- if (!scan_.RecomputeDirty(target, &validation_nodes, err))
- return false;
-
- Edge* in_edge = target->in_edge();
- if (!in_edge || !in_edge->outputs_ready()) {
- if (!plan_.AddTarget(target, err)) {
- return false;
- }
- }
-
- // Also add any validation nodes found during RecomputeDirty as top level
- // targets.
- for (std::vector<Node*>::iterator n = validation_nodes.begin();
- n != validation_nodes.end(); ++n) {
- if (Edge* validation_in_edge = (*n)->in_edge()) {
- if (!validation_in_edge->outputs_ready() &&
- !plan_.AddTarget(*n, err)) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-bool Builder::AlreadyUpToDate() const {
- return !plan_.more_to_do();
-}
-
-bool Builder::Build(string* err) {
- assert(!AlreadyUpToDate());
-
- status_->PlanHasTotalEdges(plan_.command_edge_count());
- int pending_commands = 0;
- int failures_allowed = config_.failures_allowed;
-
- // Set up the command runner if we haven't done so already.
- if (!command_runner_.get()) {
- if (config_.dry_run)
- command_runner_.reset(new DryRunCommandRunner);
- else
- command_runner_.reset(new RealCommandRunner(config_, *disk_interface_));
- }
-
- // We are about to start the build process.
- status_->BuildStarted();
-
- // This main loop runs the entire build process.
- // It is structured like this:
- // First, we attempt to start as many commands as allowed by the
- // command runner.
- // Second, we attempt to wait for / reap the next finished command.
- while (plan_.more_to_do()) {
- // See if we can start any more commands.
- if (failures_allowed && command_runner_->CanRunMore()) {
- if (Edge* edge = plan_.FindWork()) {
- if (edge->is_generator()) {
- scan_.build_log()->Close();
- }
-
- if (!StartEdge(edge, err)) {
- Cleanup();
- status_->BuildFinished();
- return false;
- }
-
- if (edge->is_phony()) {
- if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) {
- Cleanup();
- status_->BuildFinished();
- return false;
- }
- } else {
- ++pending_commands;
- }
-
- // We made some progress; go back to the main loop.
- continue;
- }
- }
-
- // See if we can reap any finished commands.
- if (pending_commands) {
- CommandRunner::Result result;
- if (!command_runner_->WaitForCommand(&result) ||
- result.status == ExitInterrupted) {
- Cleanup();
- status_->BuildFinished();
- *err = "interrupted by user";
- return false;
- }
-
- --pending_commands;
- if (!FinishCommand(&result, err)) {
- Cleanup();
- status_->BuildFinished();
- return false;
- }
-
- if (!result.success()) {
- if (failures_allowed)
- failures_allowed--;
- }
-
- // We made some progress; start the main loop over.
- continue;
- }
-
- // If we get here, we cannot make any more progress.
- status_->BuildFinished();
- if (failures_allowed == 0) {
- if (config_.failures_allowed > 1)
- *err = "subcommands failed";
- else
- *err = "subcommand failed";
- } else if (failures_allowed < config_.failures_allowed)
- *err = "cannot make progress due to previous errors";
- else
- *err = "stuck [this is a bug]";
-
- return false;
- }
-
- status_->BuildFinished();
- return true;
-}
-
-bool Builder::StartEdge(Edge* edge, string* err) {
- METRIC_RECORD("StartEdge");
- if (edge->is_phony())
- return true;
-
- int64_t start_time_millis = GetTimeMillis() - start_time_millis_;
- running_edges_.insert(make_pair(edge, start_time_millis));
- status_->BuildEdgeStarted(edge);
-
- TimeStamp build_start = -1;
-
- // Create directories necessary for outputs and remember the current
- // filesystem mtime to record later
- // XXX: this will block; do we care?
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (!disk_interface_->MakeDirs((*o)->path()))
- return false;
- if (build_start == -1) {
- disk_interface_->WriteFile(lock_file_path_, "");
- build_start = disk_interface_->Stat(lock_file_path_, err);
- if (build_start == -1)
- build_start = 0;
- }
- }
-
- edge->command_start_time_ = build_start;
-
- // Create response file, if needed
- // XXX: this may also block; do we care?
- string rspfile = edge->GetUnescapedRspfile();
- if (!rspfile.empty()) {
- string content = edge->GetBinding("rspfile_content");
- if (!disk_interface_->WriteFile(rspfile, content))
- return false;
- }
-
- // start command computing and run it
- if (!command_runner_->StartCommand(edge)) {
- err->assign("command '" + edge->EvaluateCommand() + "' failed.");
- return false;
- }
-
- return true;
-}
-
-bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
- METRIC_RECORD("FinishCommand");
-
- Edge* edge = result->edge;
-
- // First try to extract dependencies from the result, if any.
- // This must happen first as it filters the command output (we want
- // to filter /showIncludes output, even on compile failure) and
- // extraction itself can fail, which makes the command fail from a
- // build perspective.
- vector<Node*> deps_nodes;
- string deps_type = edge->GetBinding("deps");
- const string deps_prefix = edge->GetBinding("msvc_deps_prefix");
- if (!deps_type.empty()) {
- string extract_err;
- if (!ExtractDeps(result, deps_type, deps_prefix, &deps_nodes,
- &extract_err) &&
- result->success()) {
- if (!result->output.empty())
- result->output.append("\n");
- result->output.append(extract_err);
- result->status = ExitFailure;
- }
- }
-
- int64_t start_time_millis, end_time_millis;
- RunningEdgeMap::iterator it = running_edges_.find(edge);
- start_time_millis = it->second;
- end_time_millis = GetTimeMillis() - start_time_millis_;
- running_edges_.erase(it);
-
- status_->BuildEdgeFinished(edge, result->success(), result->output);
-
- // The rest of this function only applies to successful commands.
- if (!result->success()) {
- return plan_.EdgeFinished(edge, Plan::kEdgeFailed, err);
- }
-
- // Restat the edge outputs
- TimeStamp record_mtime = 0;
- if (!config_.dry_run) {
- const bool restat = edge->has_restat();
- const bool generator = edge->is_generator();
- bool node_cleaned = false;
- record_mtime = edge->command_start_time_;
-
- // restat and generator rules must restat the outputs after the build
- // has finished. if record_mtime == 0, then there was an error while
- // attempting to touch/stat the temp file when the edge started and
- // we should fall back to recording the outputs' current mtime in the
- // log.
- if (record_mtime == 0 || restat || generator) {
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), err);
- if (new_mtime == -1)
- return false;
- if (new_mtime > record_mtime)
- record_mtime = new_mtime;
- if ((*o)->mtime() == new_mtime && restat) {
- // The rule command did not change the output. Propagate the clean
- // state through the build graph.
- // Note that this also applies to nonexistent outputs (mtime == 0).
- if (!plan_.CleanNode(&scan_, *o, err))
- return false;
- node_cleaned = true;
- }
- }
- }
- if (node_cleaned) {
- record_mtime = edge->command_start_time_;
-
- // The total number of edges in the plan may have changed as a result
- // of a restat.
- status_->PlanHasTotalEdges(plan_.command_edge_count());
- }
- }
-
- if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err))
- return false;
-
- // Delete any left over response file.
- string rspfile = edge->GetUnescapedRspfile();
- if (!rspfile.empty() && !g_keep_rsp)
- disk_interface_->RemoveFile(rspfile);
-
- if (scan_.build_log()) {
- if (!scan_.build_log()->RecordCommand(edge, start_time_millis,
- end_time_millis, record_mtime)) {
- *err = string("Error writing to build log: ") + strerror(errno);
- return false;
- }
- }
-
- if (!deps_type.empty() && !config_.dry_run) {
- 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;
- }
- }
- }
-
- // Ensure that the next dependency scan that touches this edge removes
- // any recorded deps in it since they are now stale.
- edge->deps_loaded_ = false;
- return true;
-}
-
-bool Builder::ExtractDeps(CommandRunner::Result* result,
- const string& deps_type,
- const string& deps_prefix,
- vector<Node*>* deps_nodes,
- string* err) {
- if (deps_type == "msvc") {
- CLParser parser;
- string output;
- if (!parser.Parse(result->output, deps_prefix, &output, err))
- return false;
- result->output = output;
- for (set<string>::iterator i = parser.includes_.begin();
- i != parser.includes_.end(); ++i) {
- // ~0 is assuming that with MSVC-parsed headers, it's ok to always make
- // all backslashes (as some of the slashes will certainly be backslashes
- // anyway). This could be fixed if necessary with some additional
- // complexity in IncludesNormalize::Relativize.
- deps_nodes->push_back(state_->GetNode(*i, ~0u));
- }
- } 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");
- return false;
- }
-
- // Read depfile content. Treat a missing depfile as empty.
- string content;
- switch (disk_interface_->ReadFile(depfile, &content, err)) {
- case DiskInterface::Okay:
- break;
- case DiskInterface::NotFound:
- err->clear();
- break;
- case DiskInterface::OtherError:
- return false;
- }
- if (content.empty())
- return true;
-
- DepfileParser deps(config_.depfile_parser_options);
- if (!deps.Parse(&content, err))
- return false;
-
- // XXX check depfile matches expected output.
- deps_nodes->reserve(deps.ins_.size());
- for (vector<StringPiece>::iterator i = deps.ins_.begin();
- i != deps.ins_.end(); ++i) {
- uint64_t slash_bits;
- CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits);
- deps_nodes->push_back(state_->GetNode(*i, slash_bits));
- }
-
- if (!g_keep_depfile) {
- if (disk_interface_->RemoveFile(depfile) < 0) {
- *err = string("deleting depfile: ") + strerror(errno) + string("\n");
- return false;
- }
- }
- } else {
- Fatal("unknown deps type '%s'", deps_type.c_str());
- }
-
- return true;
-}
-
-bool Builder::LoadDyndeps(Node* node, string* err) {
- status_->BuildLoadDyndeps();
-
- // Load the dyndep information provided by this node.
- DyndepFile ddf;
- if (!scan_.LoadDyndeps(node, &ddf, err))
- return false;
-
- // Update the build plan to account for dyndep modifications to the graph.
- if (!plan_.DyndepsLoaded(&scan_, node, ddf, err))
- return false;
-
- // New command edges may have been added to the plan.
- status_->PlanHasTotalEdges(plan_.command_edge_count());
-
- return true;
-}
diff --git a/src/build.h b/src/build.h
deleted file mode 100644
index e2b7eeb..0000000
--- a/src/build.h
+++ /dev/null
@@ -1,223 +0,0 @@
-// 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.
-
-#ifndef NINJA_BUILD_H_
-#define NINJA_BUILD_H_
-
-#include <cstdio>
-#include <map>
-#include <memory>
-#include <queue>
-#include <string>
-#include <vector>
-
-#include "exit_status.h"
-#include "graph.h" // XXX needed for DependencyScan; should rearrange.
-#include "util.h" // int64_t
-
-class AsyncLoop;
-
-struct BuildConfig;
-struct BuildLog;
-struct Builder;
-struct DiskInterface;
-struct Edge;
-struct Node;
-struct State;
-struct Status;
-
-/// Plan stores the state of a build plan: what we intend to build,
-/// which steps we're ready to execute.
-struct Plan {
- Plan(Builder* builder = NULL);
-
- /// 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(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.
- Edge* FindWork();
-
- /// Returns true if there's more work to be done.
- bool more_to_do() const { return wanted_edges_ > 0 && command_edges_ > 0; }
-
- /// Dumps the current state of the plan.
- void Dump() const;
-
- enum EdgeResult {
- kEdgeFailed,
- kEdgeSucceeded
- };
-
- /// Mark an edge as done building (whether it succeeded or failed).
- /// 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, std::string* err);
-
- /// Clean the given node during the build.
- /// Return false on error.
- bool CleanNode(DependencyScan* scan, Node* node, std::string* err);
-
- /// Number of edges with commands to run.
- int command_edge_count() const { return command_edges_; }
-
- /// Reset state. Clears want and ready sets.
- void Reset();
-
- /// Update the build plan to account for modifications made to the graph
- /// by information loaded from a dyndep file.
- bool DyndepsLoaded(DependencyScan* scan, const Node* node,
- const DyndepFile& ddf, std::string* err);
-private:
- 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, std::string* err);
-
- /// Enumerate possible steps we want for an edge.
- enum Want
- {
- /// We do not want to build the edge, but we might want to build one of
- /// its dependents.
- kWantNothing,
- /// We want to build the edge, but have not yet scheduled it.
- kWantToStart,
- /// We want to build the edge, have scheduled it, and are waiting
- /// for it to complete.
- kWantToFinish
- };
-
- void EdgeWanted(const Edge* edge);
- 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(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.
- std::map<Edge*, Want> want_;
-
- EdgeSet ready_;
-
- Builder* builder_;
-
- /// Total number of edges that have commands (not phony).
- int command_edges_;
-
- /// Total remaining number of wanted edges.
- int wanted_edges_;
-};
-
-/// CommandRunner is an interface that wraps running the build
-/// subcommands. This allows tests to abstract out running commands.
-/// RealCommandRunner is an implementation that actually runs commands.
-struct CommandRunner {
- virtual ~CommandRunner() {}
- virtual bool CanRunMore() const = 0;
- virtual bool StartCommand(Edge* edge) = 0;
-
- /// The result of waiting for a command.
- struct Result {
- Result() : edge(NULL) {}
- Edge* edge;
- ExitStatus status;
- 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 std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
- virtual void Abort() {}
-};
-
-/// Builder wraps the build process: starting commands, updating status.
-struct Builder {
- Builder(State* state, const BuildConfig& config,
- BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface, Status* status,
- int64_t start_time_millis);
- ~Builder();
-
- /// Clean up after interrupted commands by deleting output files.
- void Cleanup();
-
- 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, 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(std::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, std::string* err);
-
- /// Used for tests.
- void SetBuildLog(BuildLog* log) {
- scan_.set_build_log(log);
- }
-
- /// Load the dyndep information provided by the given node.
- bool LoadDyndeps(Node* node, std::string* err);
-
- State* state_;
- const BuildConfig& config_;
- Plan plan_;
- std::unique_ptr<CommandRunner> command_runner_;
- Status* status_;
-
- private:
- bool ExtractDeps(CommandRunner::Result* result, const std::string& deps_type,
- const std::string& deps_prefix,
- std::vector<Node*>* deps_nodes, std::string* err);
-
- /// Map of running edge to time the edge started running.
- typedef std::map<const Edge*, int> RunningEdgeMap;
- RunningEdgeMap running_edges_;
-
- /// Time the build started.
- int64_t start_time_millis_;
-
- std::string lock_file_path_;
- DiskInterface* disk_interface_;
- DependencyScan scan_;
-
- // Unimplemented copy ctor and operator= ensure we don't copy the auto_ptr.
- Builder(const Builder &other); // DO NOT IMPLEMENT
- void operator=(const Builder &other); // DO NOT IMPLEMENT
-};
-
-#endif // NINJA_BUILD_H_
diff --git a/src/build_config.cc b/src/build_config.cc
deleted file mode 100644
index 121a9db..0000000
--- a/src/build_config.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2023 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 "build_config.h"
-
-#include <inttypes.h>
-
-#include "ipc_utils.h"
-#include "process_utils.h"
-#include "util.h"
-
-std::string BuildConfig::status_format() const {
- std::string result = "[%f/%t] ";
- const char* env = environment.Get("NINJA_STATUS");
- if (env)
- result = env;
- return result;
-}
-
-int BuildConfig::status_max_commands() const {
- int result = 0;
- const char* env = environment.Get("NINJA_STATUS_MAX_COMMANDS");
- if (env) {
- int count = atoi(env);
- if (count < 0)
- count = 0;
- result = count;
- }
- return result;
-}
-
-int64_t BuildConfig::status_refresh_millis() const {
- int64_t result = 100;
- const char* env = environment.Get("NINJA_STATUS_REFRESH_MILLIS");
- if (env) {
- long long val = strtoll(env, NULL, 10);
- result = static_cast<int64_t>(val);
- }
- return result;
-}
-
-bool BuildConfig::operator==(const BuildConfig& o) const {
- return verbosity == o.verbosity && dry_run == o.dry_run &&
- parallelism == o.parallelism &&
- failures_allowed == o.failures_allowed &&
- max_load_average == o.max_load_average &&
- depfile_parser_options == o.depfile_parser_options &&
- input_file == o.input_file && environment == o.environment;
-}
-
-std::string BuildConfig::ToEncodedString() const {
- WireEncoder encoder;
- encoder.Write(verbosity);
- encoder.Write(dry_run);
- encoder.Write(parallelism);
- encoder.Write(failures_allowed);
- encoder.Write(max_load_average);
- // depfile_parser_options is empty!
- encoder.Write(input_file);
- encoder.Write(environment.ToEncodedString());
- return encoder.TakeResult();
-}
-
-// static
-BuildConfig BuildConfig::FromEncodedString(const std::string& str,
- std::string* error) {
- BuildConfig result;
-
- WireDecoder decoder(str);
- error->clear();
- decoder.Read(result.verbosity);
- decoder.Read(result.dry_run);
- decoder.Read(result.parallelism);
- decoder.Read(result.failures_allowed);
- decoder.Read(result.max_load_average);
- // depfile_parser_options is empty!
- decoder.Read(result.input_file);
-
- std::string encoded;
- decoder.Read(encoded);
- if (decoder.has_error()) {
- *error = "Truncated BuildCOnfig encoded string";
- return {};
- }
-
- result.environment = EnvironmentBlock::FromEncodedString(encoded, error);
- if (!error->empty())
- return {};
-
- return result;
-}
-
-std::string BuildConfig::ToString() const {
- std::string result = "verbosity=";
- switch (verbosity) {
- case QUIET:
- result += "quiet";
- break;
- case NO_STATUS_UPDATE:
- result += "no-status-upadte";
- break;
- case NORMAL:
- result += "normal";
- break;
- case VERBOSE:
- result += "verbose";
- break;
- default:
- StringAppendFormat(result, "unknown(%d)", static_cast<int>(verbosity));
- }
-
- StringAppendFormat(
- result,
- " dry_run=%s parallelism=%d failures_allowed=%d max_load_average=%.2f",
- dry_run ? "true" : "false", parallelism, failures_allowed,
- max_load_average);
-
- StringAppendFormat(result, "input_file=%s", input_file.c_str());
-
- StringAppendFormat(
- result,
- " environment=%s", environment.AsString().c_str());
-
- StringAppendFormat(
- result,
- " status_format=%s status_max_commands=%d status_refresh_millis=" PRId64,
- status_format().c_str(), status_max_commands(), status_refresh_millis());
-
- return result;
-}
diff --git a/src/build_config.h b/src/build_config.h
deleted file mode 100644
index b0f0709..0000000
--- a/src/build_config.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2023 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_BUILD_CONFIG_H_
-#define NINJA_BUILD_CONFIG_H_
-
-#include <string>
-
-#include "depfile_parser.h"
-#include "process_utils.h"
-
-/// Options (e.g. verbosity, parallelism) passed to a build.
-/// NOTE: This does not include debug flags, stored in global variables :-/
-struct BuildConfig {
- enum Verbosity {
- QUIET, // No output -- used when testing.
- NO_STATUS_UPDATE, // just regular output but suppress status update
- NORMAL, // regular output and status update
- VERBOSE
- };
- Verbosity verbosity = NORMAL;
- bool dry_run = false;
- int parallelism = 1;
- int failures_allowed = 1;
- /// The maximum load average we must not exceed. A negative value
- /// means that we do not have any limit.
- double max_load_average = -0.0f;
- /// Name of the main manifest input file.
- std::string input_file = "build.ninja";
-
- DepfileParserOptions depfile_parser_options;
-
- /// The set of environment variables to use when spawning commands.
- EnvironmentBlock environment;
-
- /// Retrieve some values from environment variables.
- std::string status_format() const;
- int status_max_commands() const;
- int64_t status_refresh_millis() const;
-
- /// Compare two instances.
- bool operator==(const BuildConfig& o) const;
- bool operator!=(const BuildConfig& o) const { return !(*this == o); }
-
- /// Encoding/decoding support for IPC.
- static BuildConfig FromEncodedString(const std::string& str,
- std::string* error);
-
- std::string ToEncodedString() const;
-
- /// Convert to string for debugging.
- std::string ToString() const;
-};
-
-#endif // NINJA_BUILD_CONFIG_H_
diff --git a/src/build_log.cc b/src/build_log.cc
deleted file mode 100644
index b415025..0000000
--- a/src/build_log.cc
+++ /dev/null
@@ -1,502 +0,0 @@
-// 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.
-
-// On AIX, inttypes.h gets indirectly included by build_log.h.
-// It's easiest just to ask for the printf format macros right away.
-#ifndef _WIN32
-#ifndef __STDC_FORMAT_MACROS
-#define __STDC_FORMAT_MACROS
-#endif
-#endif
-
-#include "build_log.h"
-#include "disk_interface.h"
-
-#include <cassert>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifndef _WIN32
-#include <inttypes.h>
-#include <unistd.h>
-#endif
-
-#include "build.h"
-#include "graph.h"
-#include "metrics.h"
-#include "util.h"
-#if defined(_MSC_VER) && (_MSC_VER < 1800)
-#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
-// older runs.
-// Once the number of redundant entries exceeds a threshold, we write
-// out a new file and replace the existing one with it.
-
-namespace {
-
-const char kFileSignature[] = "# ninja log v%d\n";
-const int kOldestSupportedVersion = 4;
-const int kCurrentVersion = 5;
-
-// 64bit MurmurHash2, by Austin Appleby
-#if defined(_MSC_VER)
-#define BIG_CONSTANT(x) (x)
-#else // defined(_MSC_VER)
-#define BIG_CONSTANT(x) (x##LLU)
-#endif // !defined(_MSC_VER)
-inline
-uint64_t MurmurHash64A(const void* key, size_t len) {
- static const uint64_t seed = 0xDECAFBADDECAFBADull;
- const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995);
- const int r = 47;
- uint64_t h = seed ^ (len * m);
- const unsigned char* data = (const unsigned char*)key;
- while (len >= 8) {
- uint64_t k;
- memcpy(&k, data, sizeof k);
- k *= m;
- k ^= k >> r;
- k *= m;
- h ^= k;
- h *= m;
- data += 8;
- len -= 8;
- }
- switch (len & 7)
- {
- case 7: h ^= uint64_t(data[6]) << 48;
- NINJA_FALLTHROUGH;
- case 6: h ^= uint64_t(data[5]) << 40;
- NINJA_FALLTHROUGH;
- case 5: h ^= uint64_t(data[4]) << 32;
- NINJA_FALLTHROUGH;
- case 4: h ^= uint64_t(data[3]) << 24;
- NINJA_FALLTHROUGH;
- case 3: h ^= uint64_t(data[2]) << 16;
- NINJA_FALLTHROUGH;
- case 2: h ^= uint64_t(data[1]) << 8;
- NINJA_FALLTHROUGH;
- case 1: h ^= uint64_t(data[0]);
- h *= m;
- };
- h ^= h >> r;
- h *= m;
- h ^= h >> r;
- return h;
-}
-#undef BIG_CONSTANT
-
-
-} // namespace
-
-// static
-uint64_t BuildLog::LogEntry::HashCommand(StringPiece command) {
- return MurmurHash64A(command.str_, command.len_);
-}
-
-BuildLog::LogEntry::LogEntry(const string& output)
- : output(output) {}
-
-BuildLog::LogEntry::LogEntry(const string& output, uint64_t command_hash,
- int start_time, int end_time, TimeStamp mtime)
- : output(output), command_hash(command_hash),
- start_time(start_time), end_time(end_time), mtime(mtime)
-{}
-
-BuildLog::BuildLog()
- : log_file_(NULL), needs_recompaction_(false) {}
-
-BuildLog::~BuildLog() {
- Close();
-
- for (auto& pair : entries_) {
- delete pair.second;
- }
- entries_.clear();
-}
-
-bool BuildLog::OpenForWrite(const string& path, const BuildLogUser& user,
- string* err) {
- if (needs_recompaction_) {
- if (!Recompact(path, user, err))
- 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;
-}
-
-bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
- TimeStamp mtime) {
- string command = edge->EvaluateCommand(true);
- uint64_t command_hash = LogEntry::HashCommand(command);
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- const string& path = (*out)->path();
- Entries::iterator i = entries_.find(path);
- LogEntry* log_entry;
- if (i != entries_.end()) {
- log_entry = i->second;
- } else {
- log_entry = new LogEntry(path);
- entries_.insert(Entries::value_type(log_entry->output, log_entry));
- }
- log_entry->command_hash = command_hash;
- log_entry->start_time = start_time;
- 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;
- if (fflush(log_file_) != 0) {
- return false;
- }
- }
- }
- return true;
-}
-
-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) {
- memset(buf_, 0, sizeof(buf_));
- }
-
- // Reads a \n-terminated line from the file passed to the constructor.
- // On return, *line_start points to the beginning of the next line, and
- // *line_end points to the \n at the end of the line. If no newline is seen
- // in a fixed buffer size, *line_end is set to NULL. Returns false on EOF.
- bool ReadLine(char** line_start, char** line_end) {
- if (line_start_ >= buf_end_ || !line_end_) {
- // Buffer empty, refill.
- size_t size_read = fread(buf_, 1, sizeof(buf_), file_);
- if (!size_read)
- return false;
- line_start_ = buf_;
- buf_end_ = buf_ + size_read;
- } else {
- // Advance to next line in buffer.
- line_start_ = line_end_ + 1;
- }
-
- line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
- if (!line_end_) {
- // No newline. Move rest of data to start of buffer, fill rest.
- size_t already_consumed = line_start_ - buf_;
- size_t size_rest = (buf_end_ - buf_) - already_consumed;
- memmove(buf_, line_start_, size_rest);
-
- size_t read = fread(buf_ + size_rest, 1, sizeof(buf_) - size_rest, file_);
- buf_end_ = buf_ + size_rest + read;
- line_start_ = buf_;
- line_end_ = (char*)memchr(line_start_, '\n', buf_end_ - line_start_);
- }
-
- *line_start = line_start_;
- *line_end = line_end_;
- return true;
- }
-
- private:
- FILE* file_;
- char buf_[256 << 10];
- char* buf_end_; // Points one past the last valid byte in |buf_|.
-
- char* line_start_;
- // Points at the next \n in buf_ after line_start, or NULL.
- char* line_end_;
-};
-
-LoadStatus BuildLog::Load(const string& path, string* err) {
- METRIC_RECORD_LOAD(".ninja_log load");
- FILE* file = fopen(path.c_str(), "r");
- if (!file) {
- if (errno == ENOENT)
- return LOAD_NOT_FOUND;
- *err = strerror(errno);
- return LOAD_ERROR;
- }
-
- int log_version = 0;
- int unique_entry_count = 0;
- int total_entry_count = 0;
-
- LineReader reader(file);
- char* line_start = 0;
- char* line_end = 0;
- while (reader.ReadLine(&line_start, &line_end)) {
- if (!log_version) {
- sscanf(line_start, kFileSignature, &log_version);
-
- if (log_version < kOldestSupportedVersion) {
- *err = ("build log version invalid, perhaps due to being too old; "
- "starting over");
- fclose(file);
- unlink(path.c_str());
- // Don't report this as a failure. An empty build log will cause
- // us to rebuild the outputs anyway.
- return LOAD_SUCCESS;
- }
- }
-
- // If no newline was found in this chunk, read the next.
- if (!line_end)
- continue;
-
- const char kFieldSeparator = '\t';
-
- char* start = line_start;
- char* end = (char*)memchr(start, kFieldSeparator, line_end - start);
- if (!end)
- continue;
- *end = 0;
-
- int start_time = 0, end_time = 0;
- TimeStamp mtime = 0;
-
- start_time = atoi(start);
- start = end + 1;
-
- end = (char*)memchr(start, kFieldSeparator, line_end - start);
- if (!end)
- continue;
- *end = 0;
- end_time = atoi(start);
- start = end + 1;
-
- end = (char*)memchr(start, kFieldSeparator, line_end - start);
- if (!end)
- continue;
- *end = 0;
- mtime = strtoll(start, NULL, 10);
- start = end + 1;
-
- end = (char*)memchr(start, kFieldSeparator, line_end - start);
- if (!end)
- continue;
- string output = string(start, end - start);
-
- start = end + 1;
- end = line_end;
-
- LogEntry* entry;
- Entries::iterator i = entries_.find(output);
- if (i != entries_.end()) {
- entry = i->second;
- } else {
- entry = new LogEntry(output);
- entries_.insert(Entries::value_type(entry->output, entry));
- ++unique_entry_count;
- }
- ++total_entry_count;
-
- entry->start_time = start_time;
- entry->end_time = end_time;
- entry->mtime = mtime;
- if (log_version >= 5) {
- char c = *end; *end = '\0';
- entry->command_hash = (uint64_t)strtoull(start, NULL, 16);
- *end = c;
- } else {
- entry->command_hash = LogEntry::HashCommand(StringPiece(start,
- end - start));
- }
- }
- fclose(file);
-
- if (!line_start) {
- return LOAD_SUCCESS; // file was empty
- }
-
- // Decide whether it's time to rebuild the log:
- // - if we're upgrading versions
- // - if it's getting large
- int kMinCompactionEntryCount = 100;
- int kCompactionRatio = 3;
- if (log_version < kCurrentVersion) {
- needs_recompaction_ = true;
- } else if (total_entry_count > kMinCompactionEntryCount &&
- total_entry_count > unique_entry_count * kCompactionRatio) {
- needs_recompaction_ = true;
- }
-
- return LOAD_SUCCESS;
-}
-
-BuildLog::LogEntry* BuildLog::LookupByOutput(const string& path) {
- Entries::iterator i = entries_.find(path);
- if (i != entries_.end())
- return i->second;
- return NULL;
-}
-
-bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) {
- return fprintf(f, "%d\t%d\t%" PRId64 "\t%s\t%" PRIx64 "\n",
- entry.start_time, entry.end_time, entry.mtime,
- entry.output.c_str(), entry.command_hash) > 0;
-}
-
-bool BuildLog::Recompact(const string& path, const BuildLogUser& user,
- string* err) {
- METRIC_RECORD(".ninja_log recompact");
-
- Close();
- string temp_path = path + ".recompact";
- 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;
- }
-
- vector<StringPiece> dead_outputs;
- for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
- if (user.IsPathDead(i->first)) {
- dead_outputs.push_back(i->first);
- continue;
- }
-
- if (!WriteEntry(f, *i->second)) {
- *err = strerror(errno);
- fclose(f);
- return false;
- }
- }
-
- for (size_t i = 0; i < dead_outputs.size(); ++i) {
- auto it = entries_.find(dead_outputs[i]);
- delete it->second;
- entries_.erase(it);
- }
-
- fclose(f);
- if (unlink(path.c_str()) < 0) {
- *err = strerror(errno);
- return false;
- }
-
- if (rename(temp_path.c_str(), path.c_str()) < 0) {
- *err = strerror(errno);
- return false;
- }
-
- 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
deleted file mode 100644
index c51303c..0000000
--- a/src/build_log.h
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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.
-
-#ifndef NINJA_BUILD_LOG_H_
-#define NINJA_BUILD_LOG_H_
-
-#include <string>
-#include <stdio.h>
-
-#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.
-struct BuildLogUser {
- /// Return if a given output is no longer part of the build manifest.
- /// This is only called during recompaction and doesn't have to be fast.
- virtual bool IsPathDead(StringPiece s) const = 0;
-};
-
-/// Store a log of every command ran for every build.
-/// It has a few uses:
-///
-/// 1) (hashes of) command lines for existing output files, so we know
-/// when we need to rebuild due to the command changing
-/// 2) timing information, perhaps for generating reports
-/// 3) restat information
-struct BuildLog {
- BuildLog();
- ~BuildLog();
-
- /// 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.
- LoadStatus Load(const std::string& path, std::string* err);
-
- struct LogEntry {
- std::string output;
- uint64_t command_hash;
- int start_time;
- int end_time;
- TimeStamp mtime;
-
- static uint64_t HashCommand(StringPiece command);
-
- // Used by tests.
- bool operator==(const LogEntry& o) {
- return output == o.output && command_hash == o.command_hash &&
- start_time == o.start_time && end_time == o.end_time &&
- mtime == o.mtime;
- }
-
- explicit LogEntry(const std::string& output);
- LogEntry(const std::string& output, uint64_t command_hash,
- int start_time, int end_time, TimeStamp mtime);
- };
-
- /// Return the number of entries in the log.
- size_t size() const { return entries_.size(); }
-
- /// Lookup a previously-run command by its output 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 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_;
-};
-
-#endif // NINJA_BUILD_LOG_H_
diff --git a/src/build_log_perftest.cc b/src/build_log_perftest.cc
deleted file mode 100644
index 5a93619..0000000
--- a/src/build_log_perftest.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2012 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 <stdio.h>
-#include <stdlib.h>
-
-#include "build_log.h"
-#include "graph.h"
-#include "manifest_parser.h"
-#include "state.h"
-#include "util.h"
-#include "metrics.h"
-
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
-using namespace std;
-
-const char kTestFilename[] = "BuildLogPerfTest-tempfile";
-
-struct NoDeadPaths : public BuildLogUser {
- virtual bool IsPathDead(StringPiece) const { return false; }
-};
-
-bool WriteTestData(string* err) {
- BuildLog log;
-
- NoDeadPaths no_dead_paths;
- if (!log.OpenForWrite(kTestFilename, no_dead_paths, err))
- return false;
-
- /*
- A histogram of command lengths in chromium. For example, 407 builds,
- 1.4% of all builds, had commands longer than 32 bytes but shorter than 64.
- 32 407 1.4%
- 64 183 0.6%
- 128 1461 5.1%
- 256 791 2.8%
- 512 1314 4.6%
- 1024 6114 21.3%
- 2048 11759 41.0%
- 4096 2056 7.2%
- 8192 4567 15.9%
- 16384 13 0.0%
- 32768 4 0.0%
- 65536 5 0.0%
- The average command length is 4.1 kB and there were 28674 commands in total,
- which makes for a total log size of ~120 MB (also counting output filenames).
-
- Based on this, write 30000 many 4 kB long command lines.
- */
-
- // ManifestParser is the only object allowed to create Rules.
- const size_t kRuleSize = 4000;
- string long_rule_command = "gcc ";
- for (int i = 0; long_rule_command.size() < kRuleSize; ++i) {
- char buf[80];
- sprintf(buf, "-I../../and/arbitrary/but/fairly/long/path/suffixed/%d ", i);
- long_rule_command += buf;
- }
- long_rule_command += "$in -o $out\n";
-
- State state;
- ManifestParser parser(&state, NULL);
- if (!parser.ParseTest("rule cxx\n command = " + long_rule_command, err))
- return false;
-
- // Create build edges. Using ManifestParser is as fast as using the State api
- // for edge creation, so just use that.
- const int kNumCommands = 30000;
- string build_rules;
- for (int i = 0; i < kNumCommands; ++i) {
- char buf[80];
- sprintf(buf, "build input%d.o: cxx input%d.cc\n", i, i);
- build_rules += buf;
- }
-
- if (!parser.ParseTest(build_rules, err))
- return false;
-
- for (int i = 0; i < kNumCommands; ++i) {
- log.RecordCommand(state.edges_[i],
- /*start_time=*/100 * i,
- /*end_time=*/100 * i + 1,
- /*mtime=*/0);
- }
-
- return true;
-}
-
-int main() {
- vector<int> times;
- string err;
-
- if (!WriteTestData(&err)) {
- fprintf(stderr, "Failed to write test data: %s\n", err.c_str());
- return 1;
- }
-
- {
- // Read once to warm up disk cache.
- BuildLog log;
- if (log.Load(kTestFilename, &err) == LOAD_ERROR) {
- fprintf(stderr, "Failed to read test data: %s\n", err.c_str());
- return 1;
- }
- }
- const int kNumRepetitions = 5;
- for (int i = 0; i < kNumRepetitions; ++i) {
- int64_t start = GetTimeMillis();
- BuildLog log;
- if (log.Load(kTestFilename, &err) == LOAD_ERROR) {
- fprintf(stderr, "Failed to read test data: %s\n", err.c_str());
- return 1;
- }
- int delta = (int)(GetTimeMillis() - start);
- printf("%dms\n", delta);
- times.push_back(delta);
- }
-
- int min = times[0];
- int max = times[0];
- float total = 0;
- for (size_t i = 0; i < times.size(); ++i) {
- total += times[i];
- if (times[i] < min)
- min = times[i];
- else if (times[i] > max)
- max = times[i];
- }
-
- printf("min %dms max %dms avg %.1fms\n",
- min, max, total / times.size());
-
- unlink(kTestFilename);
-
- return 0;
-}
diff --git a/src/build_log_test.cc b/src/build_log_test.cc
deleted file mode 100644
index 4725377..0000000
--- a/src/build_log_test.cc
+++ /dev/null
@@ -1,362 +0,0 @@
-// 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.
-
-#include "build_log.h"
-
-#include "util.h"
-#include "test.h"
-
-#include <sys/stat.h>
-#ifdef _WIN32
-#include <fcntl.h>
-#include <share.h>
-#else
-#include <sys/types.h>
-#include <unistd.h>
-#endif
-#include <cassert>
-
-using namespace std;
-
-namespace {
-
-const char kTestFilename[] = "BuildLogTest-tempfile";
-
-struct BuildLogTest : public StateTestWithBuiltinRules, public BuildLogUser {
- virtual void SetUp() {
- // In case a crashing test left a stale file behind.
- unlink(kTestFilename);
- }
- virtual void TearDown() {
- unlink(kTestFilename);
- }
- virtual bool IsPathDead(StringPiece s) const { return false; }
-};
-
-TEST_F(BuildLogTest, WriteRead) {
- AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n");
-
- 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, log1.entries().size());
- ASSERT_EQ(2u, log2.entries().size());
- BuildLog::LogEntry* e1 = log1.LookupByOutput("out");
- ASSERT_TRUE(e1);
- BuildLog::LogEntry* e2 = log2.LookupByOutput("out");
- ASSERT_TRUE(e2);
- ASSERT_TRUE(*e1 == *e2);
- ASSERT_EQ(15, e1->start_time);
- ASSERT_EQ("out", e1->output);
-}
-
-TEST_F(BuildLogTest, FirstWriteAddsSignature) {
- const char kExpectedVersion[] = "# ninja log vX\n";
- const size_t kVersionPos = strlen(kExpectedVersion) - 2; // Points at 'X'.
-
- BuildLog log;
- string contents, err;
-
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
- ASSERT_EQ("", err);
- log.Close();
-
- ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
- ASSERT_EQ("", err);
- if (contents.size() >= kVersionPos)
- contents[kVersionPos] = 'X';
- EXPECT_EQ(kExpectedVersion, contents);
-
- // Opening the file anew shouldn't add a second version string.
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
- ASSERT_EQ("", err);
- log.Close();
-
- contents.clear();
- ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
- ASSERT_EQ("", err);
- if (contents.size() >= kVersionPos)
- contents[kVersionPos] = 'X';
- EXPECT_EQ(kExpectedVersion, contents);
-}
-
-TEST_F(BuildLogTest, DoubleEntry) {
- FILE* f = fopen(kTestFilename, "wb");
- fprintf(f, "# ninja log v4\n");
- fprintf(f, "0\t1\t2\tout\tcommand abc\n");
- fprintf(f, "3\t4\t5\tout\tcommand def\n");
- fclose(f);
-
- string err;
- BuildLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- BuildLog::LogEntry* e = log.LookupByOutput("out");
- ASSERT_TRUE(e);
- ASSERT_NO_FATAL_FAILURE(AssertHash("command def", e->command_hash));
-}
-
-TEST_F(BuildLogTest, Truncate) {
- AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n");
-
- {
- 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();
- }
-#ifdef __USE_LARGEFILE64
- struct stat64 statbuf;
- ASSERT_EQ(0, stat64(kTestFilename, &statbuf));
-#else
- struct stat statbuf;
- ASSERT_EQ(0, stat(kTestFilename, &statbuf));
-#endif
- ASSERT_GT(statbuf.st_size, 0);
-
- // For all possible truncations of the input file, assert that we don't
- // crash when parsing.
- for (off_t size = statbuf.st_size; size > 0; --size) {
- BuildLog log2;
- string err;
- EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
- ASSERT_EQ("", err);
- log2.RecordCommand(state_.edges_[0], 15, 18);
- log2.RecordCommand(state_.edges_[1], 20, 25);
- log2.Close();
-
- ASSERT_TRUE(Truncate(kTestFilename, size, &err));
-
- BuildLog log3;
- err.clear();
- ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
- }
-}
-
-TEST_F(BuildLogTest, ObsoleteOldVersion) {
- FILE* f = fopen(kTestFilename, "wb");
- fprintf(f, "# ninja log v3\n");
- fprintf(f, "123 456 0 out command\n");
- fclose(f);
-
- string err;
- BuildLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &err));
- ASSERT_NE(err.find("version"), string::npos);
-}
-
-TEST_F(BuildLogTest, SpacesInOutputV4) {
- FILE* f = fopen(kTestFilename, "wb");
- fprintf(f, "# ninja log v4\n");
- fprintf(f, "123\t456\t456\tout with space\tcommand\n");
- fclose(f);
-
- string err;
- BuildLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- BuildLog::LogEntry* e = log.LookupByOutput("out with space");
- ASSERT_TRUE(e);
- ASSERT_EQ(123, e->start_time);
- ASSERT_EQ(456, e->end_time);
- ASSERT_EQ(456, e->mtime);
- ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
-}
-
-TEST_F(BuildLogTest, DuplicateVersionHeader) {
- // Old versions of ninja accidentally wrote multiple version headers to the
- // build log on Windows. This shouldn't crash, and the second version header
- // should be ignored.
- FILE* f = fopen(kTestFilename, "wb");
- fprintf(f, "# ninja log v4\n");
- fprintf(f, "123\t456\t456\tout\tcommand\n");
- fprintf(f, "# ninja log v4\n");
- fprintf(f, "456\t789\t789\tout2\tcommand2\n");
- fclose(f);
-
- string err;
- BuildLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- BuildLog::LogEntry* e = log.LookupByOutput("out");
- ASSERT_TRUE(e);
- ASSERT_EQ(123, e->start_time);
- ASSERT_EQ(456, e->end_time);
- ASSERT_EQ(456, e->mtime);
- ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
-
- e = log.LookupByOutput("out2");
- ASSERT_TRUE(e);
- ASSERT_EQ(456, e->start_time);
- ASSERT_EQ(789, e->end_time);
- ASSERT_EQ(789, e->mtime);
- 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.
- FILE* f = fopen(kTestFilename, "wb");
- fprintf(f, "# ninja log v4\n");
- fprintf(f, "123\t456\t456\tout\tcommand start");
- for (size_t i = 0; i < (512 << 10) / strlen(" more_command"); ++i)
- fputs(" more_command", f);
- fprintf(f, "\n");
- fprintf(f, "456\t789\t789\tout2\tcommand2\n");
- fclose(f);
-
- string err;
- BuildLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- BuildLog::LogEntry* e = log.LookupByOutput("out");
- ASSERT_NULL(e);
-
- e = log.LookupByOutput("out2");
- ASSERT_TRUE(e);
- ASSERT_EQ(456, e->start_time);
- ASSERT_EQ(789, e->end_time);
- ASSERT_EQ(789, e->mtime);
- ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
-}
-
-TEST_F(BuildLogTest, MultiTargetEdge) {
- AssertParse(&state_,
-"build out out.d: cat\n");
-
- BuildLog log;
- log.RecordCommand(state_.edges_[0], 21, 22);
-
- ASSERT_EQ(2u, log.entries().size());
- BuildLog::LogEntry* e1 = log.LookupByOutput("out");
- ASSERT_TRUE(e1);
- BuildLog::LogEntry* e2 = log.LookupByOutput("out.d");
- ASSERT_TRUE(e2);
- ASSERT_EQ("out", e1->output);
- ASSERT_EQ("out.d", e2->output);
- ASSERT_EQ(21, e1->start_time);
- ASSERT_EQ(21, e2->start_time);
- ASSERT_EQ(22, e2->end_time);
- ASSERT_EQ(22, e2->end_time);
-}
-
-struct BuildLogRecompactTest : public BuildLogTest {
- virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
-};
-
-TEST_F(BuildLogRecompactTest, Recompact) {
- AssertParse(&state_,
-"build out: cat in\n"
-"build out2: cat in\n");
-
- BuildLog log1;
- string err;
- EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
- ASSERT_EQ("", err);
- // Record the same edge several times, to trigger recompaction
- // the next time the log is opened.
- for (int i = 0; i < 200; ++i)
- log1.RecordCommand(state_.edges_[0], 15, 18 + i);
- log1.RecordCommand(state_.edges_[1], 21, 22);
- log1.Close();
-
- // Load...
- BuildLog log2;
- EXPECT_TRUE(log2.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(2u, log2.entries().size());
- ASSERT_TRUE(log2.LookupByOutput("out"));
- ASSERT_TRUE(log2.LookupByOutput("out2"));
- // ...and force a recompaction.
- EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
- log2.Close();
-
- // "out2" is dead, it should've been removed.
- BuildLog log3;
- EXPECT_TRUE(log2.Load(kTestFilename, &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, log2.entries().size());
- ASSERT_TRUE(log2.LookupByOutput("out"));
- ASSERT_FALSE(log2.LookupByOutput("out2"));
-}
-
-} // anonymous namespace
diff --git a/src/build_test.cc b/src/build_test.cc
deleted file mode 100644
index 47daa09..0000000
--- a/src/build_test.cc
+++ /dev/null
@@ -1,4303 +0,0 @@
-// 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.
-
-#include "build.h"
-
-#include <assert.h>
-
-#include "build_config.h"
-#include "build_log.h"
-#include "deps_log.h"
-#include "graph.h"
-#include "status.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();
- }
-};
-
-/// Fixture for tests involving Plan.
-// Though Plan doesn't use State, it's useful to have one around
-// to create Nodes and Edges.
-struct PlanTest : public StateTestWithBuiltinRules {
- Plan plan_;
-
- /// Because FindWork does not return Edges in any sort of predictable order,
- // provide a means to get available Edges in order and in a format which is
- // easy to write tests around.
- void FindWorkSorted(deque<Edge*>* ret, int count) {
- for (int i = 0; i < count; ++i) {
- ASSERT_TRUE(plan_.more_to_do());
- Edge* edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ret->push_back(edge);
- }
- ASSERT_FALSE(plan_.FindWork());
- sort(ret->begin(), ret->end(), CompareEdgesByOutput::cmp);
- }
-
- void TestPoolWithDepthOne(const char *test_case);
-};
-
-TEST_F(PlanTest, Basic) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n"));
- GetNode("mid")->MarkDirty();
- GetNode("out")->MarkDirty();
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("mid", edge->outputs_[0]->path());
-
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("mid", edge->inputs_[0]->path());
- ASSERT_EQ("out", edge->outputs_[0]->path());
-
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- ASSERT_FALSE(plan_.more_to_do());
- edge = plan_.FindWork();
- ASSERT_NULL(edge);
-}
-
-// Test that two outputs from one rule can be handled as inputs to the next.
-TEST_F(PlanTest, DoubleOutputDirect) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid1 mid2\n"
-"build mid1 mid2: cat in\n"));
- GetNode("mid1")->MarkDirty();
- GetNode("mid2")->MarkDirty();
- GetNode("out")->MarkDirty();
-
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge;
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat in
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat mid1 mid2
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_FALSE(edge); // done
-}
-
-// Test that two outputs from one rule can eventually be routed to another.
-TEST_F(PlanTest, DoubleOutputIndirect) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat b1 b2\n"
-"build b1: cat a1\n"
-"build b2: cat a2\n"
-"build a1 a2: cat in\n"));
- GetNode("a1")->MarkDirty();
- GetNode("a2")->MarkDirty();
- GetNode("b1")->MarkDirty();
- GetNode("b2")->MarkDirty();
- GetNode("out")->MarkDirty();
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge;
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat in
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat a1
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat a2
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat b1 b2
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_FALSE(edge); // done
-}
-
-// Test that two edges from one output can both execute.
-TEST_F(PlanTest, DoubleDependent) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat a1 a2\n"
-"build a1: cat mid\n"
-"build a2: cat mid\n"
-"build mid: cat in\n"));
- GetNode("mid")->MarkDirty();
- GetNode("a1")->MarkDirty();
- GetNode("a2")->MarkDirty();
- GetNode("out")->MarkDirty();
-
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge;
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat in
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat mid
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat mid
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge); // cat a1 a2
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_FALSE(edge); // done
-}
-
-void PlanTest::TestPoolWithDepthOne(const char* test_case) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, test_case));
- GetNode("out1")->MarkDirty();
- GetNode("out2")->MarkDirty();
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out1"), &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(plan_.AddTarget(GetNode("out2"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("out1", edge->outputs_[0]->path());
-
- // This will be false since poolcat is serialized
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("out2", edge->outputs_[0]->path());
-
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- ASSERT_FALSE(plan_.more_to_do());
- edge = plan_.FindWork();
- ASSERT_NULL(edge);
-}
-
-TEST_F(PlanTest, PoolWithDepthOne) {
- TestPoolWithDepthOne(
-"pool foobar\n"
-" depth = 1\n"
-"rule poolcat\n"
-" command = cat $in > $out\n"
-" pool = foobar\n"
-"build out1: poolcat in\n"
-"build out2: poolcat in\n");
-}
-
-TEST_F(PlanTest, ConsolePool) {
- TestPoolWithDepthOne(
-"rule poolcat\n"
-" command = cat $in > $out\n"
-" pool = console\n"
-"build out1: poolcat in\n"
-"build out2: poolcat in\n");
-}
-
-TEST_F(PlanTest, PoolsWithDepthTwo) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"pool foobar\n"
-" depth = 2\n"
-"pool bazbin\n"
-" depth = 2\n"
-"rule foocat\n"
-" command = cat $in > $out\n"
-" pool = foobar\n"
-"rule bazcat\n"
-" command = cat $in > $out\n"
-" pool = bazbin\n"
-"build out1: foocat in\n"
-"build out2: foocat in\n"
-"build out3: foocat in\n"
-"build outb1: bazcat in\n"
-"build outb2: bazcat in\n"
-"build outb3: bazcat in\n"
-" pool =\n"
-"build allTheThings: cat out1 out2 out3 outb1 outb2 outb3\n"
-));
- // Mark all the out* nodes dirty
- for (int i = 0; i < 3; ++i) {
- GetNode("out" + string(1, '1' + static_cast<char>(i)))->MarkDirty();
- GetNode("outb" + string(1, '1' + static_cast<char>(i)))->MarkDirty();
- }
- GetNode("allTheThings")->MarkDirty();
-
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("allTheThings"), &err));
- ASSERT_EQ("", err);
-
- deque<Edge*> edges;
- FindWorkSorted(&edges, 5);
-
- for (int i = 0; i < 4; ++i) {
- Edge *edge = edges[i];
- ASSERT_EQ("in", edge->inputs_[0]->path());
- string base_name(i < 2 ? "out" : "outb");
- ASSERT_EQ(base_name + string(1, '1' + (i % 2)), edge->outputs_[0]->path());
- }
-
- // outb3 is exempt because it has an empty pool
- Edge* edge = edges[4];
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("outb3", edge->outputs_[0]->path());
-
- // finish out1
- plan_.EdgeFinished(edges.front(), Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
- edges.pop_front();
-
- // out3 should be available
- Edge* out3 = plan_.FindWork();
- ASSERT_TRUE(out3);
- ASSERT_EQ("in", out3->inputs_[0]->path());
- ASSERT_EQ("out3", out3->outputs_[0]->path());
-
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(out3, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- ASSERT_FALSE(plan_.FindWork());
-
- for (deque<Edge*>::iterator it = edges.begin(); it != edges.end(); ++it) {
- plan_.EdgeFinished(*it, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
- }
-
- Edge* last = plan_.FindWork();
- ASSERT_TRUE(last);
- ASSERT_EQ("allTheThings", last->outputs_[0]->path());
-
- plan_.EdgeFinished(last, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- ASSERT_FALSE(plan_.more_to_do());
- ASSERT_FALSE(plan_.FindWork());
-}
-
-TEST_F(PlanTest, PoolWithRedundantEdges) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "pool compile\n"
- " depth = 1\n"
- "rule gen_foo\n"
- " command = touch foo.cpp\n"
- "rule gen_bar\n"
- " command = touch bar.cpp\n"
- "rule echo\n"
- " command = echo $out > $out\n"
- "build foo.cpp.obj: echo foo.cpp || foo.cpp\n"
- " pool = compile\n"
- "build bar.cpp.obj: echo bar.cpp || bar.cpp\n"
- " pool = compile\n"
- "build libfoo.a: echo foo.cpp.obj bar.cpp.obj\n"
- "build foo.cpp: gen_foo\n"
- "build bar.cpp: gen_bar\n"
- "build all: phony libfoo.a\n"));
- GetNode("foo.cpp")->MarkDirty();
- GetNode("foo.cpp.obj")->MarkDirty();
- GetNode("bar.cpp")->MarkDirty();
- GetNode("bar.cpp.obj")->MarkDirty();
- GetNode("libfoo.a")->MarkDirty();
- GetNode("all")->MarkDirty();
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("all"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge = NULL;
-
- deque<Edge*> initial_edges;
- FindWorkSorted(&initial_edges, 2);
-
- edge = initial_edges[1]; // Foo first
- ASSERT_EQ("foo.cpp", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_FALSE(plan_.FindWork());
- ASSERT_EQ("foo.cpp", edge->inputs_[0]->path());
- ASSERT_EQ("foo.cpp", edge->inputs_[1]->path());
- ASSERT_EQ("foo.cpp.obj", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = initial_edges[0]; // Now for bar
- ASSERT_EQ("bar.cpp", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_FALSE(plan_.FindWork());
- ASSERT_EQ("bar.cpp", edge->inputs_[0]->path());
- ASSERT_EQ("bar.cpp", edge->inputs_[1]->path());
- ASSERT_EQ("bar.cpp.obj", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_FALSE(plan_.FindWork());
- ASSERT_EQ("foo.cpp.obj", edge->inputs_[0]->path());
- ASSERT_EQ("bar.cpp.obj", edge->inputs_[1]->path());
- ASSERT_EQ("libfoo.a", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_FALSE(plan_.FindWork());
- ASSERT_EQ("libfoo.a", edge->inputs_[0]->path());
- ASSERT_EQ("all", edge->outputs_[0]->path());
- plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_FALSE(edge);
- ASSERT_FALSE(plan_.more_to_do());
-}
-
-TEST_F(PlanTest, PoolWithFailingEdge) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "pool foobar\n"
- " depth = 1\n"
- "rule poolcat\n"
- " command = cat $in > $out\n"
- " pool = foobar\n"
- "build out1: poolcat in\n"
- "build out2: poolcat in\n"));
- GetNode("out1")->MarkDirty();
- GetNode("out2")->MarkDirty();
- string err;
- EXPECT_TRUE(plan_.AddTarget(GetNode("out1"), &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(plan_.AddTarget(GetNode("out2"), &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(plan_.more_to_do());
-
- Edge* edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("out1", edge->outputs_[0]->path());
-
- // This will be false since poolcat is serialized
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(edge, Plan::kEdgeFailed, &err);
- ASSERT_EQ("", err);
-
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
- ASSERT_EQ("in", edge->inputs_[0]->path());
- ASSERT_EQ("out2", edge->outputs_[0]->path());
-
- ASSERT_FALSE(plan_.FindWork());
-
- plan_.EdgeFinished(edge, Plan::kEdgeFailed, &err);
- ASSERT_EQ("", err);
-
- ASSERT_TRUE(plan_.more_to_do()); // Jobs have failed
- edge = plan_.FindWork();
- ASSERT_NULL(edge);
-}
-
-/// Fake implementation of CommandRunner, useful for tests.
-struct FakeCommandRunner : public CommandRunner {
- explicit FakeCommandRunner(VirtualFileSystem* fs) :
- max_active_edges_(1), fs_(fs) {}
-
- // CommandRunner impl
- bool CanRunMore() const override;
- bool StartCommand(Edge* edge) override;
- bool WaitForCommand(Result* result) override;
- vector<Edge*> GetActiveEdges() override;
- void Abort() override;
-
- vector<string> commands_ran_;
- vector<Edge*> active_edges_;
- size_t max_active_edges_;
- VirtualFileSystem* fs_;
-};
-
-struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
- BuildTest() : config_(MakeConfig()), command_runner_(&fs_), status_(config_),
- builder_(&state_, config_, NULL, NULL, &fs_, &status_, 0) {
- }
-
- explicit BuildTest(DepsLog* log)
- : config_(MakeConfig()), command_runner_(&fs_), status_(config_),
- builder_(&state_, config_, NULL, log, &fs_, &status_, 0) {}
-
- virtual void SetUp() {
- StateTestWithBuiltinRules::SetUp();
-
- builder_.command_runner_.reset(&command_runner_);
- AssertParse(&state_,
-"build cat1: cat in1\n"
-"build cat2: cat in1 in2\n"
-"build cat12: cat cat1 cat2\n");
-
- fs_.Create("in1", "");
- fs_.Create("in2", "");
- }
-
- ~BuildTest() {
- builder_.command_runner_.release();
- }
-
- virtual bool IsPathDead(StringPiece s) const { return false; }
-
- /// Rebuild target in the 'working tree' (fs_).
- /// State of command_runner_ and logs contents (if specified) ARE MODIFIED.
- /// Handy to check for NOOP builds, and higher-level rebuild tests.
- void RebuildTarget(const string& target, const char* manifest,
- const char* log_path = NULL, const char* deps_path = NULL,
- State* state = NULL);
-
- // Mark a path dirty.
- void Dirty(const string& path);
-
- BuildConfig MakeConfig() {
- BuildConfig config;
- config.verbosity = BuildConfig::QUIET;
- return config;
- }
-
- BuildConfig config_;
- FakeCommandRunner command_runner_;
- VirtualFileSystem fs_;
- StatusPrinter status_;
- Builder builder_;
-};
-
-void BuildTest::RebuildTarget(const string& target, const char* manifest,
- const char* log_path, const char* deps_path,
- State* state) {
- State local_state, *pstate = &local_state;
- if (state)
- pstate = state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(pstate));
- AssertParse(pstate, manifest);
-
- string err;
- BuildLog build_log, *pbuild_log = NULL;
- if (log_path) {
- ASSERT_TRUE(build_log.Load(log_path, &err));
- ASSERT_TRUE(build_log.OpenForWrite(log_path, *this, &err));
- ASSERT_EQ("", err);
- pbuild_log = &build_log;
- }
-
- DepsLog deps_log, *pdeps_log = NULL;
- if (deps_path) {
- ASSERT_TRUE(deps_log.Load(deps_path, pstate, &err));
- ASSERT_TRUE(deps_log.OpenForWrite(deps_path, &err));
- ASSERT_EQ("", err);
- pdeps_log = &deps_log;
- }
-
- Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_, &status_, 0);
- EXPECT_TRUE(builder.AddTarget(target, &err));
-
- command_runner_.commands_ran_.clear();
- builder.command_runner_.reset(&command_runner_);
- if (!builder.AlreadyUpToDate()) {
- bool build_res = builder.Build(&err);
- EXPECT_TRUE(build_res);
- }
- builder.command_runner_.release();
-}
-
-bool FakeCommandRunner::CanRunMore() const {
- return active_edges_.size() < max_active_edges_;
-}
-
-bool FakeCommandRunner::StartCommand(Edge* edge) {
- assert(active_edges_.size() < max_active_edges_);
- assert(find(active_edges_.begin(), active_edges_.end(), edge)
- == active_edges_.end());
- commands_ran_.push_back(edge->EvaluateCommand());
- if (edge->rule().name() == "cat" ||
- 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") {
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- fs_->Create((*out)->path(), "");
- }
- } else if (edge->rule().name() == "true" ||
- edge->rule().name() == "fail" ||
- edge->rule().name() == "interrupt" ||
- edge->rule().name() == "console") {
- // Don't do anything.
- } else if (edge->rule().name() == "cp") {
- assert(!edge->inputs_.empty());
- assert(edge->outputs_.size() == 1);
- string content;
- string err;
- if (fs_->ReadFile(edge->inputs_[0]->path(), &content, &err) ==
- DiskInterface::Okay)
- fs_->WriteFile(edge->outputs_[0]->path(), content);
- } else if (edge->rule().name() == "touch-implicit-dep-out") {
- string dep = edge->GetBinding("test_dependency");
- fs_->Tick();
- fs_->Create(dep, "");
- fs_->Tick();
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- fs_->Create((*out)->path(), "");
- }
- } else if (edge->rule().name() == "touch-out-implicit-dep") {
- string dep = edge->GetBinding("test_dependency");
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- fs_->Create((*out)->path(), "");
- }
- fs_->Tick();
- fs_->Create(dep, "");
- } else if (edge->rule().name() == "generate-depfile") {
- string dep = edge->GetBinding("test_dependency");
- bool touch_dep = edge->GetBindingBool("touch_dependency");
- string depfile = edge->GetUnescapedDepfile();
- if (touch_dep) {
- fs_->Tick();
- fs_->Create(dep, "");
- }
- string contents;
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- contents += (*out)->path() + ": " + dep + "\n";
- fs_->Create((*out)->path(), "");
- }
- fs_->Create(depfile, contents);
- } else if (edge->rule().name() == "long-cc") {
- string dep = edge->GetBinding("test_dependency");
- string depfile = edge->GetUnescapedDepfile();
- string contents;
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- fs_->Tick();
- fs_->Tick();
- fs_->Tick();
- fs_->Create((*out)->path(), "");
- contents += (*out)->path() + ": " + dep + "\n";
- }
- if (!dep.empty() && !depfile.empty())
- fs_->Create(depfile, contents);
- } else {
- printf("unknown command\n");
- return false;
- }
-
- active_edges_.push_back(edge);
-
- // Allow tests to control the order by the name of the first output.
- sort(active_edges_.begin(), active_edges_.end(),
- CompareEdgesByOutput::cmp);
-
- return true;
-}
-
-bool FakeCommandRunner::WaitForCommand(Result* result) {
- if (active_edges_.empty())
- return false;
-
- // All active edges were already completed immediately when started,
- // so we can pick any edge here. Pick the last edge. Tests can
- // control the order of edges by the name of the first output.
- vector<Edge*>::iterator edge_iter = active_edges_.end() - 1;
-
- Edge* edge = *edge_iter;
- result->edge = edge;
-
- if (edge->rule().name() == "interrupt" ||
- edge->rule().name() == "touch-interrupt") {
- result->status = ExitInterrupted;
- return true;
- }
-
- if (edge->rule().name() == "console") {
- if (edge->use_console())
- result->status = ExitSuccess;
- else
- result->status = ExitFailure;
- active_edges_.erase(edge_iter);
- 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;
- else
- result->status = ExitSuccess;
-
- // This rule simulates an external process modifying files while the build command runs.
- // See TestInputMtimeRaceCondition and TestInputMtimeRaceConditionWithDepFile.
- // Note: only the first and third time the rule is run per test is the file modified, so
- // the test can verify that subsequent runs without the race have no work to do.
- if (edge->rule().name() == "long-cc") {
- string dep = edge->GetBinding("test_dependency");
- if (fs_->now_ == 4)
- fs_->files_[dep].mtime = 3;
- if (fs_->now_ == 10)
- fs_->files_[dep].mtime = 9;
- }
-
- // Provide a way for test cases to verify when an edge finishes that
- // some other edge is still active. This is useful for test cases
- // covering behavior involving multiple active edges.
- const string& verify_active_edge = edge->GetBinding("verify_active_edge");
- if (!verify_active_edge.empty()) {
- bool verify_active_edge_found = false;
- for (vector<Edge*>::iterator i = active_edges_.begin();
- i != active_edges_.end(); ++i) {
- if (!(*i)->outputs_.empty() &&
- (*i)->outputs_[0]->path() == verify_active_edge) {
- verify_active_edge_found = true;
- }
- }
- EXPECT_TRUE(verify_active_edge_found);
- }
-
- active_edges_.erase(edge_iter);
- return true;
-}
-
-vector<Edge*> FakeCommandRunner::GetActiveEdges() {
- return active_edges_;
-}
-
-void FakeCommandRunner::Abort() {
- active_edges_.clear();
-}
-
-void BuildTest::Dirty(const string& path) {
- Node* node = GetNode(path);
- node->MarkDirty();
-
- // If it's an input file, mark that we've already stat()ed it and
- // it's missing.
- if (!node->in_edge())
- node->MarkMissing();
-}
-
-TEST_F(BuildTest, NoWork) {
- string err;
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-TEST_F(BuildTest, OneStep) {
- // Given a dirty target with one ready input,
- // we should rebuild the target.
- Dirty("cat1");
- string err;
- EXPECT_TRUE(builder_.AddTarget("cat1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, OneStep2) {
- // Given a target with one dirty input,
- // we should rebuild the target.
- Dirty("cat1");
- string err;
- EXPECT_TRUE(builder_.AddTarget("cat1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, TwoStep) {
- string err;
- EXPECT_TRUE(builder_.AddTarget("cat12", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- // Depending on how the pointers work out, we could've ran
- // the first two commands in either order.
- EXPECT_TRUE((command_runner_.commands_ran_[0] == "cat in1 > cat1" &&
- command_runner_.commands_ran_[1] == "cat in1 in2 > cat2") ||
- (command_runner_.commands_ran_[1] == "cat in1 > cat1" &&
- command_runner_.commands_ran_[0] == "cat in1 in2 > cat2"));
-
- EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[2]);
-
- fs_.Tick();
-
- // Modifying in2 requires rebuilding one intermediate file
- // and the final file.
- fs_.Create("in2", "");
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("cat12", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(5u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in1 in2 > cat2", command_runner_.commands_ran_[3]);
- EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[4]);
-}
-
-TEST_F(BuildTest, TwoOutputs) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build out1 out2: touch in.txt\n"));
-
- fs_.Create("in.txt", "");
-
- 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("touch out1 out2", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, ImplicitOutput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"build out | out.imp: touch in.txt\n"));
- fs_.Create("in.txt", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[0]);
-}
-
-// Test case from
-// https://github.com/ninja-build/ninja/issues/148
-TEST_F(BuildTest, MultiOutIn) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build in1 otherfile: touch in\n"
-"build out: touch in | in1\n"));
-
- fs_.Create("in", "");
- fs_.Tick();
- fs_.Create("in1", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-}
-
-TEST_F(BuildTest, Chain) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build c2: cat c1\n"
-"build c3: cat c2\n"
-"build c4: cat c3\n"
-"build c5: cat c4\n"));
-
- fs_.Create("c1", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("c5", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(4u, command_runner_.commands_ran_.size());
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("c5", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- fs_.Tick();
-
- fs_.Create("c3", "");
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("c5", &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // 3->4, 4->5
-}
-
-TEST_F(BuildTest, MissingInput) {
- // Input is referenced by build file, but no rule for it.
- string err;
- Dirty("in1");
- EXPECT_FALSE(builder_.AddTarget("cat1", &err));
- EXPECT_EQ("'in1', needed by 'cat1', missing and no known rule to make it",
- err);
-}
-
-TEST_F(BuildTest, MissingTarget) {
- // Target is not referenced by build file.
- string err;
- EXPECT_FALSE(builder_.AddTarget("meow", &err));
- EXPECT_EQ("unknown target: 'meow'", err);
-}
-
-TEST_F(BuildTest, MissingInputTarget) {
- // Target is a missing input file
- string err;
- Dirty("in1");
- EXPECT_FALSE(builder_.AddTarget("in1", &err));
- EXPECT_EQ("'in1' missing and no known rule to make it", err);
-}
-
-TEST_F(BuildTest, MakeDirs) {
- string err;
-
-#ifdef _WIN32
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build subdir\\dir2\\file: cat in1\n"));
-#else
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build subdir/dir2/file: cat in1\n"));
-#endif
- EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err));
-
- EXPECT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(2u, fs_.directories_made_.size());
- EXPECT_EQ("subdir", fs_.directories_made_[0]);
- EXPECT_EQ("subdir/dir2", fs_.directories_made_[1]);
-}
-
-TEST_F(BuildTest, DepFileMissing) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n depfile = $out.d\n"
-"build fo$ o.o: cc foo.c\n"));
- fs_.Create("foo.c", "");
-
- EXPECT_TRUE(builder_.AddTarget("fo o.o", &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, fs_.files_read_.size());
- EXPECT_EQ("fo o.o.d", fs_.files_read_[0]);
-}
-
-TEST_F(BuildTest, DepFileOK) {
- string err;
- int orig_edges = state_.edges_.size();
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n depfile = $out.d\n"
-"build foo.o: cc foo.c\n"));
- Edge* edge = state_.edges_.back();
-
- fs_.Create("foo.c", "");
- GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing.
- fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, fs_.files_read_.size());
- EXPECT_EQ("foo.o.d", fs_.files_read_[0]);
-
- // Expect one new edge generating foo.o. Loading the depfile should have
- // added nodes, but not phony edges to the graph.
- ASSERT_EQ(orig_edges + 1, (int)state_.edges_.size());
-
- // Verify that nodes for blah.h and bar.h were added and that they
- // are marked as generated by a dep loader.
- ASSERT_FALSE(state_.LookupNode("foo.o")->generated_by_dep_loader());
- ASSERT_FALSE(state_.LookupNode("foo.c")->generated_by_dep_loader());
- ASSERT_TRUE(state_.LookupNode("blah.h"));
- ASSERT_TRUE(state_.LookupNode("blah.h")->generated_by_dep_loader());
- ASSERT_TRUE(state_.LookupNode("bar.h"));
- ASSERT_TRUE(state_.LookupNode("bar.h")->generated_by_dep_loader());
-
- // Expect our edge to now have three inputs: foo.c and two headers.
- ASSERT_EQ(3u, edge->inputs_.size());
-
- // Expect the command line we generate to only use the original input.
- ASSERT_EQ("cc foo.c", edge->EvaluateCommand());
-}
-
-TEST_F(BuildTest, DepFileParseError) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n depfile = $out.d\n"
-"build foo.o: cc foo.c\n"));
- fs_.Create("foo.c", "");
- fs_.Create("foo.o.d", "randomtext\n");
- EXPECT_FALSE(builder_.AddTarget("foo.o", &err));
- EXPECT_EQ("foo.o.d: expected ':' in depfile", err);
-}
-
-TEST_F(BuildTest, EncounterReadyTwice) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build c: touch\n"
-"build b: touch || c\n"
-"build a: touch | b || c\n"));
-
- vector<Edge*> c_out = GetNode("c")->out_edges();
- ASSERT_EQ(2u, c_out.size());
- EXPECT_EQ("b", c_out[0]->outputs_[0]->path());
- EXPECT_EQ("a", c_out[1]->outputs_[0]->path());
-
- fs_.Create("b", "");
- EXPECT_TRUE(builder_.AddTarget("a", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, OrderOnlyDeps) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n depfile = $out.d\n"
-"build foo.o: cc foo.c || otherfile\n"));
- Edge* edge = state_.edges_.back();
-
- fs_.Create("foo.c", "");
- fs_.Create("otherfile", "");
- fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- ASSERT_EQ("", err);
-
- // One explicit, two implicit, one order only.
- ASSERT_EQ(4u, edge->inputs_.size());
- EXPECT_EQ(2, edge->implicit_deps_);
- EXPECT_EQ(1, edge->order_only_deps_);
- // Verify the inputs are in the order we expect
- // (explicit then implicit then orderonly).
- EXPECT_EQ("foo.c", edge->inputs_[0]->path());
- EXPECT_EQ("blah.h", edge->inputs_[1]->path());
- EXPECT_EQ("bar.h", edge->inputs_[2]->path());
- EXPECT_EQ("otherfile", edge->inputs_[3]->path());
-
- // Expect the command line we generate to only use the original input.
- ASSERT_EQ("cc foo.c", edge->EvaluateCommand());
-
- // explicit dep dirty, expect a rebuild.
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- fs_.Tick();
-
- // Recreate the depfile, as it should have been deleted by the build.
- fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
-
- // implicit dep dirty, expect a rebuild.
- fs_.Create("blah.h", "");
- fs_.Create("bar.h", "");
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- fs_.Tick();
-
- // Recreate the depfile, as it should have been deleted by the build.
- fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
-
- // order only dep dirty, no rebuild.
- fs_.Create("otherfile", "");
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- // implicit dep missing, expect rebuild.
- fs_.RemoveFile("bar.h");
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, RebuildOrderOnlyDeps) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n"
-"rule true\n command = true\n"
-"build oo.h: cc oo.h.in\n"
-"build foo.o: cc foo.c || oo.h\n"));
-
- fs_.Create("foo.c", "");
- fs_.Create("oo.h.in", "");
-
- // foo.o and order-only dep dirty, build both.
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // all clean, no rebuild.
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- // order-only dep missing, build it only.
- fs_.RemoveFile("oo.h");
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]);
-
- fs_.Tick();
-
- // order-only dep dirty, build it only.
- fs_.Create("oo.h.in", "");
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]);
-}
-
-#ifdef _WIN32
-TEST_F(BuildTest, DepFileCanonicalize) {
- string err;
- int orig_edges = state_.edges_.size();
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n command = cc $in\n depfile = $out.d\n"
-"build gen/stuff\\things/foo.o: cc x\\y/z\\foo.c\n"));
- Edge* edge = state_.edges_.back();
-
- fs_.Create("x/y/z/foo.c", "");
- GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing.
- // Note, different slashes from manifest.
- fs_.Create("gen/stuff\\things/foo.o.d",
- "gen\\stuff\\things\\foo.o: blah.h bar.h\n");
- EXPECT_TRUE(builder_.AddTarget("gen/stuff/things/foo.o", &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, fs_.files_read_.size());
- // The depfile path does not get Canonicalize as it seems unnecessary.
- EXPECT_EQ("gen/stuff\\things/foo.o.d", fs_.files_read_[0]);
-
- // Expect one new edge generating foo.o
- ASSERT_EQ(orig_edges + 1, (int)state_.edges_.size());
- // Expect our edge to now have three inputs: foo.c and two headers.
- ASSERT_EQ(3u, edge->inputs_.size());
-
- // Expect the command line we generate to only use the original input, and
- // using the slashes from the manifest.
- ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand());
-}
-#endif
-
-TEST_F(BuildTest, Phony) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat bar.cc\n"
-"build all: phony out\n"));
- fs_.Create("bar.cc", "");
-
- EXPECT_TRUE(builder_.AddTarget("all", &err));
- ASSERT_EQ("", err);
-
- // Only one command to run, because phony runs no command.
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, PhonyNoWork) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat bar.cc\n"
-"build all: phony out\n"));
- fs_.Create("bar.cc", "");
- fs_.Create("out", "");
-
- EXPECT_TRUE(builder_.AddTarget("all", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-// Test a self-referencing phony. Ideally this should not work, but
-// ninja 1.7 and below tolerated and CMake 2.8.12.x and 3.0.x both
-// incorrectly produce it. We tolerate it for compatibility.
-TEST_F(BuildTest, PhonySelfReference) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build a: phony a\n"));
-
- EXPECT_TRUE(builder_.AddTarget("a", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-// There are 6 different cases for phony rules:
-//
-// 1. output edge does not exist, inputs are not real
-// 2. output edge does not exist, no inputs
-// 3. output edge does not exist, inputs are real, newest mtime is M
-// 4. output edge is real, inputs are not real
-// 5. output edge is real, no inputs
-// 6. output edge is real, inputs are real, newest mtime is M
-//
-// Expected results :
-// 1. Edge is marked as clean, mtime is newest mtime of dependents.
-// Touching inputs will cause dependents to rebuild.
-// 2. Edge is marked as dirty, causing dependent edges to always rebuild
-// 3. Edge is marked as clean, mtime is newest mtime of dependents.
-// Touching inputs will cause dependents to rebuild.
-// 4. Edge is marked as clean, mtime is newest mtime of dependents.
-// Touching inputs will cause dependents to rebuild.
-// 5. Edge is marked as dirty, causing dependent edges to always rebuild
-// 6. Edge is marked as clean, mtime is newest mtime of dependents.
-// Touching inputs will cause dependents to rebuild.
-void TestPhonyUseCase(BuildTest* t, int i) {
- State& state_ = t->state_;
- Builder& builder_ = t->builder_;
- FakeCommandRunner& command_runner_ = t->command_runner_;
- VirtualFileSystem& fs_ = t->fs_;
-
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build notreal: phony blank\n"
-"build phony1: phony notreal\n"
-"build phony2: phony\n"
-"build phony3: phony blank\n"
-"build phony4: phony notreal\n"
-"build phony5: phony\n"
-"build phony6: phony blank\n"
-"\n"
-"build test1: touch phony1\n"
-"build test2: touch phony2\n"
-"build test3: touch phony3\n"
-"build test4: touch phony4\n"
-"build test5: touch phony5\n"
-"build test6: touch phony6\n"
-));
-
- // Set up test.
- builder_.command_runner_.release(); // BuildTest owns the CommandRunner
- builder_.command_runner_.reset(&command_runner_);
-
- fs_.Create("blank", ""); // a "real" file
- EXPECT_TRUE(builder_.AddTarget("test1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("test2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("test3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("test4", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("test5", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("test6", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
-
- string ci;
- ci += static_cast<char>('0' + i);
-
- // Tests 1, 3, 4, and 6 should rebuild when the input is updated.
- if (i != 2 && i != 5) {
- Node* testNode = t->GetNode("test" + ci);
- Node* phonyNode = t->GetNode("phony" + ci);
- Node* inputNode = t->GetNode("blank");
-
- state_.Reset();
- TimeStamp startTime = fs_.now_;
-
- // Build number 1
- EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
- ASSERT_EQ("", err);
- if (!builder_.AlreadyUpToDate())
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
-
- // Touch the input file
- state_.Reset();
- command_runner_.commands_ran_.clear();
- fs_.Tick();
- fs_.Create("blank", ""); // a "real" file
- EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
- ASSERT_EQ("", err);
-
- // Second build, expect testN edge to be rebuilt
- // and phonyN node's mtime to be updated.
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ(string("touch test") + ci, command_runner_.commands_ran_[0]);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- TimeStamp inputTime = inputNode->mtime();
-
- EXPECT_FALSE(phonyNode->exists());
- EXPECT_FALSE(phonyNode->dirty());
-
- EXPECT_GT(phonyNode->mtime(), startTime);
- EXPECT_EQ(phonyNode->mtime(), inputTime);
- ASSERT_TRUE(testNode->Stat(&fs_, &err));
- EXPECT_TRUE(testNode->exists());
- EXPECT_GT(testNode->mtime(), startTime);
- } else {
- // Tests 2 and 5: Expect dependents to always rebuild.
-
- state_.Reset();
- command_runner_.commands_ran_.clear();
- fs_.Tick();
- command_runner_.commands_ran_.clear();
- EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("touch test" + ci, command_runner_.commands_ran_[0]);
-
- state_.Reset();
- command_runner_.commands_ran_.clear();
- EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("touch test" + ci, command_runner_.commands_ran_[0]);
- }
-}
-
-TEST_F(BuildTest, PhonyUseCase1) { TestPhonyUseCase(this, 1); }
-TEST_F(BuildTest, PhonyUseCase2) { TestPhonyUseCase(this, 2); }
-TEST_F(BuildTest, PhonyUseCase3) { TestPhonyUseCase(this, 3); }
-TEST_F(BuildTest, PhonyUseCase4) { TestPhonyUseCase(this, 4); }
-TEST_F(BuildTest, PhonyUseCase5) { TestPhonyUseCase(this, 5); }
-TEST_F(BuildTest, PhonyUseCase6) { TestPhonyUseCase(this, 6); }
-
-TEST_F(BuildTest, Fail) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule fail\n"
-" command = fail\n"
-"build out1: fail\n"));
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- ASSERT_EQ("subcommand failed", err);
-}
-
-TEST_F(BuildTest, SwallowFailures) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule fail\n"
-" command = fail\n"
-"build out1: fail\n"
-"build out2: fail\n"
-"build out3: fail\n"
-"build all: phony out1 out2 out3\n"));
-
- // Swallow two failures, die on the third.
- config_.failures_allowed = 3;
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("all", &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- ASSERT_EQ("subcommands failed", err);
-}
-
-TEST_F(BuildTest, SwallowFailuresLimit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule fail\n"
-" command = fail\n"
-"build out1: fail\n"
-"build out2: fail\n"
-"build out3: fail\n"
-"build final: cat out1 out2 out3\n"));
-
- // Swallow ten failures; we should stop before building final.
- config_.failures_allowed = 11;
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("final", &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- ASSERT_EQ("cannot make progress due to previous errors", err);
-}
-
-TEST_F(BuildTest, SwallowFailuresPool) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"pool failpool\n"
-" depth = 1\n"
-"rule fail\n"
-" command = fail\n"
-" pool = failpool\n"
-"build out1: fail\n"
-"build out2: fail\n"
-"build out3: fail\n"
-"build final: cat out1 out2 out3\n"));
-
- // Swallow ten failures; we should stop before building final.
- config_.failures_allowed = 11;
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("final", &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- ASSERT_EQ("cannot make progress due to previous errors", err);
-}
-
-TEST_F(BuildTest, PoolEdgesReadyButNotWanted) {
- fs_.Create("x", "");
-
- const char* manifest =
- "pool some_pool\n"
- " depth = 4\n"
- "rule touch\n"
- " command = touch $out\n"
- " pool = some_pool\n"
- "rule cc\n"
- " command = touch grit\n"
- "\n"
- "build B.d.stamp: cc | x\n"
- "build C.stamp: touch B.d.stamp\n"
- "build final.stamp: touch || C.stamp\n";
-
- RebuildTarget("final.stamp", manifest);
-
- fs_.RemoveFile("B.d.stamp");
-
- State save_state;
- RebuildTarget("final.stamp", manifest, NULL, NULL, &save_state);
- EXPECT_GE(save_state.LookupPool("some_pool")->current_use(), 0);
-}
-
-struct BuildWithLogTest : public BuildTest {
- BuildWithLogTest() {
- builder_.SetBuildLog(&build_log_);
- }
-
- BuildLog build_log_;
-};
-
-TEST_F(BuildWithLogTest, ImplicitGeneratedOutOfDate) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-" generator = 1\n"
-"build out.imp: touch | in\n"));
- fs_.Create("out.imp", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
-
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- EXPECT_TRUE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(BuildWithLogTest, ImplicitGeneratedOutOfDate2) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch-implicit-dep-out\n"
-" command = sleep 1 ; touch $test_dependency ; sleep 1 ; touch $out\n"
-" generator = 1\n"
-"build out.imp: touch-implicit-dep-out | inimp inimp2\n"
-" test_dependency = inimp\n"));
- fs_.Create("inimp", "");
- fs_.Create("out.imp", "");
- fs_.Tick();
- fs_.Create("inimp2", "");
- fs_.Tick();
-
- string err;
-
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
- EXPECT_FALSE(GetNode("out.imp")->dirty());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- fs_.Tick();
- fs_.Create("inimp", "");
-
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
- EXPECT_FALSE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(BuildWithLogTest, NotInLogButOnDisk) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc\n"
-"build out1: cc in\n"));
-
- // Create input/output that would be considered up to date when
- // not considering the command line hash.
- fs_.Create("in", "");
- fs_.Create("out1", "");
- string err;
-
- // Because it's not in the log, it should not be up-to-date until
- // we build again.
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
-
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-TEST_F(BuildWithLogTest, RebuildAfterFailure) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch-fail-tick2\n"
-" command = touch-fail-tick2\n"
-"build out1: touch-fail-tick2 in\n"));
-
- string err;
-
- fs_.Create("in", "");
-
- // Run once successfully to get out1 in the log
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- fs_.Tick();
- fs_.Create("in", "");
-
- // Run again with a failure that updates the output file timestamp
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("subcommand failed", err);
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- fs_.Tick();
-
- // Run again, should rerun even though the output file is up to date on disk
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("", err);
-}
-
-TEST_F(BuildWithLogTest, RebuildWithNoInputs) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch\n"
-"build out1: touch\n"
-"build out2: touch in\n"));
-
- string err;
-
- fs_.Create("in", "");
-
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
-
- fs_.Tick();
-
- fs_.Create("in", "");
-
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildWithLogTest, RestatTest) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule true\n"
-" command = true\n"
-" restat = 1\n"
-"rule cc\n"
-" command = cc\n"
-" restat = 1\n"
-"build out1: cc in\n"
-"build out2: true out1\n"
-"build out3: cat out2\n"));
-
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Create("out3", "");
-
- fs_.Tick();
-
- fs_.Create("in", "");
-
- // Do a pre-build so that there's commands in the log for the outputs,
- // otherwise, the lack of an entry in the build log will cause out3 to rebuild
- // regardless of restat.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- EXPECT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ(3u, builder_.plan_.command_edge_count());
- command_runner_.commands_ran_.clear();
- state_.Reset();
-
- fs_.Tick();
-
- fs_.Create("in", "");
- // "cc" touches out1, so we should build out2. But because "true" does not
- // touch out2, we should cancel the build of out3.
- EXPECT_TRUE(builder_.AddTarget("out3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // If we run again, it should be a no-op, because the build log has recorded
- // that we've already built out2 with an input timestamp of 2 (from out1).
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- fs_.Tick();
-
- fs_.Create("in", "");
-
- // The build log entry should not, however, prevent us from rebuilding out2
- // if out1 changes.
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildWithLogTest, RestatMissingFile) {
- // If a restat rule doesn't create its output, and the output didn't
- // exist before the rule was run, consider that behavior equivalent
- // to a rule that doesn't modify its existent output file.
-
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule true\n"
-" command = true\n"
-" restat = 1\n"
-"rule cc\n"
-" command = cc\n"
-"build out1: true in\n"
-"build out2: cc out1\n"));
-
- fs_.Create("in", "");
- fs_.Create("out2", "");
-
- // Do a pre-build so that there's commands in the log for the outputs,
- // otherwise, the lack of an entry in the build log will cause out2 to rebuild
- // regardless of restat.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- command_runner_.commands_ran_.clear();
- state_.Reset();
-
- fs_.Tick();
- fs_.Create("in", "");
- fs_.Create("out2", "");
-
- // Run a build, expect only the first command to run.
- // It doesn't touch its output (due to being the "true" command), so
- // we shouldn't run the dependent build.
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildWithLogTest, RestatSingleDependentOutputDirty) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule true\n"
- " command = true\n"
- " restat = 1\n"
- "rule touch\n"
- " command = touch\n"
- "build out1: true in\n"
- "build out2 out3: touch out1\n"
- "build out4: touch out2\n"
- ));
-
- // Create the necessary files
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out4", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
-
- fs_.Tick();
- fs_.Create("in", "");
- fs_.RemoveFile("out3");
-
- // Since "in" is missing, out1 will be built. Since "out3" is missing,
- // out2 and out3 will be built even though "in" is not touched when built.
- // Then, since out2 is rebuilt, out4 should be rebuilt -- the restat on the
- // "true" rule should not lead to the "touch" edge writing out2 and out3 being
- // cleard.
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out4", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
-}
-
-// Test scenario, in which an input file is removed, but output isn't changed
-// https://github.com/ninja-build/ninja/issues/295
-TEST_F(BuildWithLogTest, RestatMissingInput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule true\n"
- " command = true\n"
- " depfile = $out.d\n"
- " restat = 1\n"
- "rule cc\n"
- " command = cc\n"
- "build out1: true in\n"
- "build out2: cc out1\n"));
-
- // Create all necessary files
- fs_.Create("in", "");
-
- // The implicit dependencies and the depfile itself
- // are newer than the output
- TimeStamp restat_mtime = fs_.Tick();
- fs_.Create("out1.d", "out1: will.be.deleted restat.file\n");
- fs_.Create("will.be.deleted", "");
- fs_.Create("restat.file", "");
-
- // Run the build, out1 and out2 get built
- string err;
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // See that an entry in the logfile is created, capturing
- // the right mtime
- BuildLog::LogEntry* log_entry = build_log_.LookupByOutput("out1");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(restat_mtime, log_entry->mtime);
-
- // Now remove a file, referenced from depfile, so that target becomes
- // dirty, but the output does not change
- fs_.RemoveFile("will.be.deleted");
-
- // Trigger the build again - only out1 gets built
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // Check that the logfile entry remains correctly set
- log_entry = build_log_.LookupByOutput("out1");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(restat_mtime, log_entry->mtime);
-}
-
-TEST_F(BuildWithLogTest, RestatInputChangesDueToRule) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule generate-depfile\n"
-" command = sleep 1 ; touch $touch_dependency; touch $out ; echo \"$out: $test_dependency\" > $depfile\n"
-"build out1: generate-depfile || cat1\n"
-" test_dependency = in2\n"
-" touch_dependency = 1\n"
-" restat = 1\n"
-" depfile = out.d\n"));
-
- // Perform the first build. out1 is a restat rule, so its recorded mtime in the build
- // log should be the time the command completes, not the time the command started. One
- // of out1's discovered dependencies will have a newer mtime than when out1 started
- // running, due to its command touching the dependency itself.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
- EXPECT_EQ(2u, builder_.plan_.command_edge_count());
- BuildLog::LogEntry* log_entry = build_log_.LookupByOutput("out1");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(2u, log_entry->mtime);
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- fs_.Tick();
- fs_.Create("in1", "");
-
- // Touching a dependency of an order-only dependency of out1 should not cause out1 to
- // rebuild. If out1 were not a restat rule, then it would rebuild here because its
- // recorded mtime would have been an earlier mtime than its most recent input's (in2)
- // mtime
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(!state_.GetNode("out1", 0)->dirty());
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ(1u, builder_.plan_.command_edge_count());
-}
-
-TEST_F(BuildWithLogTest, GeneratedPlainDepfileMtime) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule generate-depfile\n"
-" command = touch $out ; echo \"$out: $test_dependency\" > $depfile\n"
-"build out: generate-depfile\n"
-" test_dependency = inimp\n"
-" depfile = out.d\n"));
- fs_.Create("inimp", "");
- fs_.Tick();
-
- string err;
-
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- builder_.Cleanup();
- builder_.plan_.Reset();
-
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-struct BuildDryRun : public BuildWithLogTest {
- BuildDryRun() {
- config_.dry_run = true;
- }
-};
-
-TEST_F(BuildDryRun, AllCommandsShown) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule true\n"
-" command = true\n"
-" restat = 1\n"
-"rule cc\n"
-" command = cc\n"
-" restat = 1\n"
-"build out1: cc in\n"
-"build out2: true out1\n"
-"build out3: cat out2\n"));
-
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Create("out3", "");
-
- fs_.Tick();
-
- fs_.Create("in", "");
-
- // "cc" touches out1, so we should build out2. But because "true" does not
- // touch out2, we should cancel the build of out3.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out3", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
-}
-
-// Test that RSP files are created when & where appropriate and deleted after
-// successful execution.
-TEST_F(BuildTest, RspFileSuccess)
-{
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule cat_rsp\n"
- " command = cat $rspfile > $out\n"
- " rspfile = $rspfile\n"
- " rspfile_content = $long_command\n"
- "rule cat_rsp_out\n"
- " command = cat $rspfile > $out\n"
- " rspfile = $out.rsp\n"
- " rspfile_content = $long_command\n"
- "build out1: cat in\n"
- "build out2: cat_rsp in\n"
- " rspfile = out 2.rsp\n"
- " long_command = Some very long command\n"
- "build out$ 3: cat_rsp_out in\n"
- " long_command = Some very long command\n"));
-
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Create("out 3", "");
-
- fs_.Tick();
-
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AddTarget("out 3", &err));
- ASSERT_EQ("", err);
-
- size_t files_created = fs_.files_created_.size();
- size_t files_removed = fs_.files_removed_.size();
-
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
-
- // The RSP files and temp file to acquire output mtimes were created
- ASSERT_EQ(files_created + 3, fs_.files_created_.size());
- ASSERT_EQ(1u, fs_.files_created_.count("out 2.rsp"));
- ASSERT_EQ(1u, fs_.files_created_.count("out 3.rsp"));
- ASSERT_EQ(1u, fs_.files_created_.count(".ninja_lock"));
-
- // The RSP files were removed
- ASSERT_EQ(files_removed + 2, fs_.files_removed_.size());
- ASSERT_EQ(1u, fs_.files_removed_.count("out 2.rsp"));
- ASSERT_EQ(1u, fs_.files_removed_.count("out 3.rsp"));
-}
-
-// Test that RSP file is created but not removed for commands, which fail
-TEST_F(BuildTest, RspFileFailure) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule fail\n"
- " command = fail\n"
- " rspfile = $rspfile\n"
- " rspfile_content = $long_command\n"
- "build out: fail in\n"
- " rspfile = out.rsp\n"
- " long_command = Another very long command\n"));
-
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- size_t files_created = fs_.files_created_.size();
- size_t files_removed = fs_.files_removed_.size();
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ("subcommand failed", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // The RSP file and temp file to acquire output mtimes were created
- ASSERT_EQ(files_created + 2, fs_.files_created_.size());
- ASSERT_EQ(1u, fs_.files_created_.count("out.rsp"));
- ASSERT_EQ(1u, fs_.files_created_.count(".ninja_lock"));
-
- // The RSP file was NOT removed
- ASSERT_EQ(files_removed, fs_.files_removed_.size());
- ASSERT_EQ(0u, fs_.files_removed_.count("out.rsp"));
-
- // The RSP file contains what it should
- ASSERT_EQ("Another very long command", fs_.files_["out.rsp"].contents);
-}
-
-// Test that contents of the RSP file behaves like a regular part of
-// command line, i.e. triggers a rebuild if changed
-TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule cat_rsp\n"
- " command = cat $rspfile > $out\n"
- " rspfile = $rspfile\n"
- " rspfile_content = $long_command\n"
- "build out: cat_rsp in\n"
- " rspfile = out.rsp\n"
- " long_command = Original very long command\n"));
-
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- // 1. Build for the 1st time (-> populate log)
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // 2. Build again (no change)
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
- ASSERT_TRUE(builder_.AlreadyUpToDate());
-
- // 3. Alter the entry in the logfile
- // (to simulate a change in the command line between 2 builds)
- BuildLog::LogEntry* log_entry = build_log_.LookupByOutput("out");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_NO_FATAL_FAILURE(AssertHash(
- "cat out.rsp > out;rspfile=Original very long command",
- log_entry->command_hash));
- log_entry->command_hash++; // Change the command hash to something else.
- // Now expect the target to be rebuilt
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, InterruptCleanup) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule interrupt\n"
-" command = interrupt\n"
-"rule touch-interrupt\n"
-" command = touch-interrupt\n"
-"build out1: interrupt in1\n"
-"build out2: touch-interrupt in2\n"));
-
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Tick();
- fs_.Create("in1", "");
- fs_.Create("in2", "");
-
- // An untouched output of an interrupted command should be retained.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_EQ("", err);
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("interrupted by user", err);
- builder_.Cleanup();
- EXPECT_GT(fs_.Stat("out1", &err), 0);
- err = "";
-
- // A touched output of an interrupted command should be deleted.
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- EXPECT_EQ("", err);
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("interrupted by user", err);
- builder_.Cleanup();
- EXPECT_EQ(0, fs_.Stat("out2", &err));
-}
-
-TEST_F(BuildTest, StatFailureAbortsBuild) {
- const string kTooLongToStat(400, 'i');
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-("build " + kTooLongToStat + ": cat in\n").c_str()));
- fs_.Create("in", "");
-
- // This simulates a stat failure:
- fs_.files_[kTooLongToStat].mtime = -1;
- fs_.files_[kTooLongToStat].stat_error = "stat failed";
-
- string err;
- EXPECT_FALSE(builder_.AddTarget(kTooLongToStat, &err));
- EXPECT_EQ("stat failed", err);
-}
-
-TEST_F(BuildTest, PhonyWithNoInputs) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build nonexistent: phony\n"
-"build out1: cat || nonexistent\n"
-"build out2: cat nonexistent\n"));
- fs_.Create("out1", "");
- fs_.Create("out2", "");
-
- // out1 should be up to date even though its input is dirty, because its
- // order-only dependency has nothing to do.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-
- // out2 should still be out of date though, because its input is dirty.
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc\n"
-" deps = gcc\n"
-"build out: cc\n"));
- Dirty("out");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(builder_.AlreadyUpToDate());
-
- EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ("subcommand failed", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, StatusFormatElapsed) {
- status_.BuildStarted();
- // Before any task is done, the elapsed time must be zero.
- EXPECT_EQ("[%/e0.000]",
- status_.FormatProgressStatus("[%%/e%e]", 0));
-}
-
-TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
- EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
- status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", 0));
-}
-
-TEST_F(BuildTest, FailedDepsParse) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build bad_deps.o: cat in1\n"
-" deps = gcc\n"
-" depfile = in1.d\n"));
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("bad_deps.o", &err));
- ASSERT_EQ("", err);
-
- // These deps will fail to parse, as they should only have one
- // path to the left of the colon.
- fs_.Create("in1.d", "AAA BBB");
-
- EXPECT_FALSE(builder_.Build(&err));
- 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
-/// each build.
-struct BuildWithDepsLogTest : public BuildTest {
- BuildWithDepsLogTest() {}
-
- virtual void SetUp() {
- BuildTest::SetUp();
-
- temp_dir_.CreateAndEnter("BuildWithDepsLogTest");
- }
-
- virtual void TearDown() {
- temp_dir_.Cleanup();
- }
-
- ScopedTempDir temp_dir_;
-
- /// Shadow parent class builder_ so we don't accidentally use it.
- void* builder_;
-};
-
-/// Run a straightforward build where the deps log is used.
-TEST_F(BuildWithDepsLogTest, Straightforward) {
- string err;
- // Note: in1 was created by the superclass SetUp().
- const char* manifest =
- "build out: cat in1\n"
- " deps = gcc\n"
- " depfile = in1.d\n";
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Run the build once, everything should be ok.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- fs_.Create("in1.d", "out: in2");
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // The deps file should have been removed.
- EXPECT_EQ(0, fs_.Stat("in1.d", &err));
- // Recreate it for the next step.
- fs_.Create("in1.d", "out: in2");
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Touch the file only mentioned in the deps.
- fs_.Tick();
- fs_.Create("in2", "");
-
- // Run the build again.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // We should have rebuilt the output due to in2 being
- // out of date.
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-}
-
-/// Verify that obsolete dependency info causes a rebuild.
-/// 1) Run a successful build where everything has time t, record deps.
-/// 2) Move input/output to time t+1 -- despite files in alignment,
-/// should still need to rebuild due to deps at older time.
-TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
- string err;
- // Note: in1 was created by the superclass SetUp().
- const char* manifest =
- "build out: cat in1\n"
- " deps = gcc\n"
- " depfile = in1.d\n";
- {
- // Run an ordinary build that gathers dependencies.
- fs_.Create("in1", "");
- fs_.Create("in1.d", "out: ");
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Run the build once, everything should be ok.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- // Push all files one tick forward so that only the deps are out
- // of date.
- fs_.Tick();
- fs_.Create("in1", "");
- fs_.Create("out", "");
-
- // The deps file should have been removed, so no need to timestamp it.
- EXPECT_EQ(0, fs_.Stat("in1.d", &err));
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- // Recreate the deps file here because the build expects them to exist.
- fs_.Create("in1.d", "out: ");
-
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // We should have rebuilt the output due to the deps being
- // out of date.
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-}
-
-TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) {
- const char* manifest =
- "build out: cat in1\n"
- " deps = gcc\n"
- " depfile = in1.d\n";
-
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("in1", "");
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // The deps log is NULL in dry runs.
- config_.dry_run = true;
- Builder builder(&state, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- string err;
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
-}
-
-TEST_F(BuildWithDepsLogTest, TestInputMtimeRaceCondition) {
- string err;
- const char* manifest =
- "rule long-cc\n"
- " command = long-cc\n"
- "build out: long-cc in1\n"
- " test_dependency = in1\n";
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- BuildLog build_log;
- ASSERT_TRUE(build_log.Load("build_log", &err));
- ASSERT_TRUE(build_log.OpenForWrite("build_log", *this, &err));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
-
- BuildLog::LogEntry* log_entry = NULL;
- {
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- // Run the build, out gets built, dep file is created
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // See that an entry in the logfile is created. the input_mtime is 1 since that was
- // the mtime of in1 when the command was started
- log_entry = build_log.LookupByOutput("out");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(1u, log_entry->mtime);
-
- builder.command_runner_.release();
- }
-
- {
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- // Trigger the build again - "out" should rebuild despite having a newer mtime than
- // "in1", since "in1" was touched during the build of out (simulated by changing its
- // mtime in the the test builder's WaitForCommand() which runs before FinishCommand()
- command_runner_.commands_ran_.clear();
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // Check that the logfile entry is still correct
- log_entry = build_log.LookupByOutput("out");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_TRUE(fs_.files_["in1"].mtime < log_entry->mtime);
- builder.command_runner_.release();
- }
-
- {
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- // And a subsequent run should not have any work to do
- command_runner_.commands_ran_.clear();
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.AlreadyUpToDate());
-
- builder.command_runner_.release();
- }
-}
-
-TEST_F(BuildWithDepsLogTest, TestInputMtimeRaceConditionWithDepFile) {
- string err;
- const char* manifest =
- "rule long-cc\n"
- " command = long-cc\n"
- "build out: long-cc\n"
- " deps = gcc\n"
- " depfile = out.d\n"
- " test_dependency = header.h\n";
-
- fs_.Create("header.h", "");
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- BuildLog build_log;
- ASSERT_TRUE(build_log.Load("build_log", &err));
- ASSERT_TRUE(build_log.OpenForWrite("build_log", *this, &err));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
-
- {
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
- // Run the build, out gets built, dep file is created
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- // See that an entry in the logfile is created. the mtime is 1 due to the command
- // starting when the file system's mtime was 1.
- BuildLog::LogEntry* log_entry = build_log.LookupByOutput("out");
- ASSERT_TRUE(NULL != log_entry);
- ASSERT_EQ(1u, log_entry->mtime);
-
- builder.command_runner_.release();
- }
-
- {
- // Trigger the build again - "out" will rebuild since its newest input mtime (header.h)
- // is newer than the recorded mtime of out in the build log
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-
- {
- // Trigger the build again - "out" won't rebuild since the file wasn't updated during
- // the previous build
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- ASSERT_TRUE(builder.AlreadyUpToDate());
-
- builder.command_runner_.release();
- }
-
- // touch the header to trigger a rebuild
- fs_.Create("header.h", "");
- ASSERT_EQ(fs_.now_, 7);
-
- {
- // Rebuild. This time, long-cc will cause header.h to be updated while the build is
- // in progress
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-
- {
- // Rebuild. Because header.h is now in the deplog for out, it should be detectable as
- // a change-while-in-progress and should cause a rebuild of out.
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-
- {
- // This time, the header.h file was not updated during the build, so the target should
- // not be considered dirty.
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
-
- state.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.AlreadyUpToDate());
-
- builder.command_runner_.release();
- }
-}
-
-/// Check that a restat rule generating a header cancels compilations correctly.
-TEST_F(BuildTest, RestatDepfileDependency) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule true\n"
-" command = true\n" // Would be "write if out-of-date" in reality.
-" restat = 1\n"
-"build header.h: true header.in\n"
-"build out: cat in1\n"
-" depfile = in1.d\n"));
-
- fs_.Create("header.h", "");
- fs_.Create("in1.d", "out: header.h");
- fs_.Tick();
- fs_.Create("header.in", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-}
-
-/// Check that a restat rule generating a header cancels compilations correctly,
-/// depslog case.
-TEST_F(BuildWithDepsLogTest, RestatDepfileDependencyDepsLog) {
- string err;
- // Note: in1 was created by the superclass SetUp().
- const char* manifest =
- "rule true\n"
- " command = true\n" // Would be "write if out-of-date" in reality.
- " restat = 1\n"
- "build header.h: true header.in\n"
- "build out: cat in1\n"
- " deps = gcc\n"
- " depfile = in1.d\n";
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Run the build once, everything should be ok.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- fs_.Create("in1.d", "out: header.h");
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Touch the input of the restat rule.
- fs_.Tick();
- fs_.Create("header.in", "");
-
- // Run the build again.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
- EXPECT_TRUE(builder.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // Rule "true" should have run again, but the build of "out" should have
- // been cancelled due to restat propagating through the depfile header.
- EXPECT_EQ(1u, command_runner_.commands_ran_.size());
-
- builder.command_runner_.release();
- }
-}
-
-TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) {
- string err;
- const char* manifest =
- "rule cc\n command = cc $in\n depfile = $out.d\n deps = gcc\n"
- "build fo$ o.o: cc foo.c\n";
-
- fs_.Create("foo.c", "");
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Run the build once, everything should be ok.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("fo o.o", &err));
- ASSERT_EQ("", err);
- fs_.Create("fo o.o.d", "fo\\ o.o: blah.h bar.h\n");
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
- Edge* edge = state.edges_.back();
-
- state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing.
- EXPECT_TRUE(builder.AddTarget("fo o.o", &err));
- ASSERT_EQ("", err);
-
- // Expect one new edge generating fo o.o, loading the depfile should
- // note generate new edges.
- ASSERT_EQ(1u, state.edges_.size());
- // Expect our edge to now have three inputs: foo.c and two headers.
- ASSERT_EQ(3u, edge->inputs_.size());
-
- // Expect the command line we generate to only use the original input.
- ASSERT_EQ("cc foo.c", edge->EvaluateCommand());
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-}
-
-TEST_F(BuildWithDepsLogTest, DiscoveredDepDuringBuildChanged) {
- string err;
- const char* manifest =
- "rule touch-out-implicit-dep\n"
- " command = touch $out ; sleep 1 ; touch $test_dependency\n"
- "rule generate-depfile\n"
- " command = touch $out ; echo \"$out: $test_dependency\" > $depfile\n"
- "build out1: touch-out-implicit-dep in1\n"
- " test_dependency = inimp\n"
- "build out2: generate-depfile in1 || out1\n"
- " test_dependency = inimp\n"
- " depfile = out2.d\n"
- " deps = gcc\n";
-
- fs_.Create("in1", "");
- fs_.Tick();
-
- BuildLog build_log;
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out2", &err));
- EXPECT_FALSE(builder.AlreadyUpToDate());
-
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_TRUE(builder.AlreadyUpToDate());
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- fs_.Tick();
- fs_.Create("in1", "");
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out2", &err));
- EXPECT_FALSE(builder.AlreadyUpToDate());
-
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_TRUE(builder.AlreadyUpToDate());
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- fs_.Tick();
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("out2", &err));
- EXPECT_TRUE(builder.AlreadyUpToDate());
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-}
-
-#ifdef _WIN32
-TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) {
- string err;
- const char* manifest =
- "rule cc\n command = cc $in\n depfile = $out.d\n deps = gcc\n"
- "build a/b\\c\\d/e/fo$ o.o: cc x\\y/z\\foo.c\n";
-
- fs_.Create("x/y/z/foo.c", "");
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- // Run the build once, everything should be ok.
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err));
- ASSERT_EQ("", err);
- // Note, different slashes from manifest.
- fs_.Create("a/b\\c\\d/e/fo o.o.d",
- "a\\b\\c\\d\\e\\fo\\ o.o: blah.h bar.h\n");
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
- state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing.
- EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err));
- ASSERT_EQ("", err);
-
- // Expect one new edge generating fo o.o.
- ASSERT_EQ(1u, state.edges_.size());
- // Expect our edge to now have three inputs: foo.c and two headers.
- Edge* edge = state.edges_.back();
- ASSERT_EQ(3u, edge->inputs_.size());
-
- // Expect the command line we generate to only use the original input.
- // Note, slashes from manifest, not .d.
- ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand());
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-}
-#endif
-
-/// Check that a restat rule doesn't clear an edge if the depfile is missing.
-/// Follows from: https://github.com/ninja-build/ninja/issues/603
-TEST_F(BuildTest, RestatMissingDepfile) {
-const char* manifest =
-"rule true\n"
-" command = true\n" // Would be "write if out-of-date" in reality.
-" restat = 1\n"
-"build header.h: true header.in\n"
-"build out: cat header.h\n"
-" depfile = out.d\n";
-
- fs_.Create("header.h", "");
- fs_.Tick();
- fs_.Create("out", "");
- fs_.Create("header.in", "");
-
- // Normally, only 'header.h' would be rebuilt, as
- // its rule doesn't touch the output and has 'restat=1' set.
- // But we are also missing the depfile for 'out',
- // which should force its command to run anyway!
- RebuildTarget("out", manifest);
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-}
-
-/// Check that a restat rule doesn't clear an edge if the deps are missing.
-/// https://github.com/ninja-build/ninja/issues/603
-TEST_F(BuildWithDepsLogTest, RestatMissingDepfileDepslog) {
- string err;
- const char* manifest =
-"rule true\n"
-" command = true\n" // Would be "write if out-of-date" in reality.
-" restat = 1\n"
-"build header.h: true header.in\n"
-"build out: cat header.h\n"
-" deps = gcc\n"
-" depfile = out.d\n";
-
- // Build once to populate ninja deps logs from out.d
- fs_.Create("header.in", "");
- fs_.Create("out.d", "out: header.h");
- fs_.Create("header.h", "");
-
- RebuildTarget("out", manifest, "build_log", "ninja_deps");
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Sanity: this rebuild should be NOOP
- RebuildTarget("out", manifest, "build_log", "ninja_deps");
- ASSERT_EQ(0u, command_runner_.commands_ran_.size());
-
- // Touch 'header.in', blank dependencies log (create a different one).
- // Building header.h triggers 'restat' outputs cleanup.
- // Validate that out is rebuilt netherless, as deps are missing.
- fs_.Tick();
- fs_.Create("header.in", "");
-
- // (switch to a new blank deps_log "ninja_deps2")
- RebuildTarget("out", manifest, "build_log", "ninja_deps2");
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Sanity: this build should be NOOP
- RebuildTarget("out", manifest, "build_log", "ninja_deps2");
- ASSERT_EQ(0u, command_runner_.commands_ran_.size());
-
- // Check that invalidating deps by target timestamp also works here
- // Repeat the test but touch target instead of blanking the log.
- fs_.Tick();
- fs_.Create("header.in", "");
- fs_.Create("out", "");
- RebuildTarget("out", manifest, "build_log", "ninja_deps2");
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
-
- // And this build should be NOOP again
- RebuildTarget("out", manifest, "build_log", "ninja_deps2");
- ASSERT_EQ(0u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, WrongOutputInDepfileCausesRebuild) {
- string err;
- const char* manifest =
-"rule cc\n"
-" command = cc $in\n"
-" depfile = $out.d\n"
-"build foo.o: cc foo.c\n";
-
- fs_.Create("foo.c", "");
- fs_.Create("foo.o", "");
- fs_.Create("header.h", "");
- fs_.Create("foo.o.d", "bar.o.d: header.h\n");
-
- RebuildTarget("foo.o", manifest, "build_log", "ninja_deps");
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, Console) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule console\n"
-" command = console\n"
-" pool = console\n"
-"build cons: console in.txt\n"));
-
- fs_.Create("in.txt", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("cons", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
-}
-
-TEST_F(BuildTest, DyndepMissingAndNoRule) {
- // Verify that we can diagnose when a dyndep file is missing and
- // has no rule to build it.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
-
- string err;
- EXPECT_FALSE(builder_.AddTarget("out", &err));
- EXPECT_EQ("loading 'dd': No such file or directory", err);
-}
-
-TEST_F(BuildTest, DyndepReadyImplicitConnection) {
- // Verify that a dyndep file can be loaded immediately to discover
- // that one edge has an implicit output that is also an implicit
- // input of another edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"build tmp: touch || dd\n"
-" dyndep = dd\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out | out.imp: dyndep | tmp.imp\n"
-"build tmp | tmp.imp: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
- EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[1]);
-}
-
-TEST_F(BuildTest, DyndepReadySyntaxError) {
- // Verify that a dyndep file can be loaded immediately to discover
- // and reject a syntax error in it.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd",
-"build out: dyndep\n"
-);
-
- string err;
- EXPECT_FALSE(builder_.AddTarget("out", &err));
- EXPECT_EQ("dd:1: expected 'ninja_dyndep_version = ...'\n", err);
-}
-
-TEST_F(BuildTest, DyndepReadyCircular) {
- // Verify that a dyndep file can be loaded immediately to discover
- // and reject a circular dependency.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" dyndep = dd\n"
-"build in: r circ\n"
- ));
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out | circ: dyndep\n"
- );
- fs_.Create("out", "");
-
- string err;
- EXPECT_FALSE(builder_.AddTarget("out", &err));
- EXPECT_EQ("dependency cycle: circ -> in -> circ", err);
-}
-
-TEST_F(BuildTest, DyndepBuild) {
- // Verify that a dyndep file can be built and loaded to discover nothing.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- size_t files_created = fs_.files_created_.size();
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[1]);
- ASSERT_EQ(2u, fs_.files_read_.size());
- EXPECT_EQ("dd-in", fs_.files_read_[0]);
- EXPECT_EQ("dd", fs_.files_read_[1]);
- ASSERT_EQ(3u + files_created, fs_.files_created_.size());
- EXPECT_EQ(1u, fs_.files_created_.count("dd"));
- EXPECT_EQ(1u, fs_.files_created_.count("out"));
- EXPECT_EQ(1u, fs_.files_created_.count(".ninja_lock"));
-}
-
-TEST_F(BuildTest, DyndepBuildSyntaxError) {
- // Verify that a dyndep file can be built and loaded to discover
- // and reject a syntax error in it.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd-in",
-"build out: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("dd:1: expected 'ninja_dyndep_version = ...'\n", err);
-}
-
-TEST_F(BuildTest, DyndepBuildUnrelatedOutput) {
- // Verify that a dyndep file can have dependents that do not specify
- // it as their dyndep binding.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build unrelated: touch || dd\n"
-"build out: touch unrelated || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch unrelated", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewOutput) {
- // Verify that a dyndep file can be built and loaded to discover
- // a new output of an edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out: touch in || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("in", "");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out | out.imp: dyndep\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(2u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[1]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewOutputWithMultipleRules1) {
- // Verify that a dyndep file can be built and loaded to discover
- // a new output of an edge that is already the output of another edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out1 | out-twice.imp: touch in\n"
-"build out2: touch in || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("in", "");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out2 | out-twice.imp: dyndep\n"
-);
- fs_.Tick();
- fs_.Create("out1", "");
- fs_.Create("out2", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- EXPECT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("multiple rules generate out-twice.imp", err);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewOutputWithMultipleRules2) {
- // Verify that a dyndep file can be built and loaded to discover
- // a new output of an edge that is already the output of another
- // edge also discovered by dyndep.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd1: cp dd1-in\n"
-"build out1: touch || dd1\n"
-" dyndep = dd1\n"
-"build dd2: cp dd2-in || dd1\n" // make order predictable for test
-"build out2: touch || dd2\n"
-" dyndep = dd2\n"
-));
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Create("dd1-in",
-"ninja_dyndep_version = 1\n"
-"build out1 | out-twice.imp: dyndep\n"
-);
- fs_.Create("dd2-in", "");
- fs_.Create("dd2",
-"ninja_dyndep_version = 1\n"
-"build out2 | out-twice.imp: dyndep\n"
-);
- fs_.Tick();
- fs_.Create("out1", "");
- fs_.Create("out2", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- EXPECT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- EXPECT_EQ("multiple rules generate out-twice.imp", err);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewInput) {
- // Verify that a dyndep file can be built and loaded to discover
- // a new input to an edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build in: touch\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | in\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewInputWithValidation) {
- // Verify that a dyndep file cannot contain the |@ validation
- // syntax.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep |@ validation\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
-
- string err_first_line = err.substr(0, err.find("\n"));
- EXPECT_EQ("dd:2: expected newline, got '|@'", err_first_line);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNewInputWithTransitiveValidation) {
- // Verify that a dyndep file can be built and loaded to discover
- // a new input to an edge that has a validation edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build in: touch |@ validation\n"
-"build validation: touch in out\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | in\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(4u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[2]);
- EXPECT_EQ("touch validation", command_runner_.commands_ran_[3]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverImplicitConnection) {
- // Verify that a dyndep file can be built and loaded to discover
- // that one edge has an implicit output that is also an implicit
- // input of another edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build tmp: touch || dd\n"
-" dyndep = dd\n"
-"build out: touch || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out | out.imp: dyndep | tmp.imp\n"
-"build tmp | tmp.imp: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverOutputAndDepfileInput) {
- // Verify that a dyndep file can be built and loaded to discover
- // that one edge has an implicit output that is also reported by
- // a depfile as an input of another edge.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build tmp: touch || dd\n"
-" dyndep = dd\n"
-"build out: cp tmp\n"
-" depfile = out.d\n"
-));
- fs_.Create("out.d", "out: tmp.imp\n");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build tmp | tmp.imp: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- // Loading the depfile did not give tmp.imp a phony input edge.
- ASSERT_FALSE(GetNode("tmp.imp")->in_edge());
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- // Loading the dyndep file gave tmp.imp a real input edge.
- ASSERT_FALSE(GetNode("tmp.imp")->in_edge()->is_phony());
-
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("cp tmp out", command_runner_.commands_ran_[2]);
- EXPECT_EQ(1u, fs_.files_created_.count("tmp.imp"));
- EXPECT_TRUE(builder_.AlreadyUpToDate());
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNowWantEdge) {
- // Verify that a dyndep file can be built and loaded to discover
- // that an edge is actually wanted due to a missing implicit output.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build tmp: touch || dd\n"
-" dyndep = dd\n"
-"build out: touch tmp || dd\n"
-" dyndep = dd\n"
-));
- fs_.Create("tmp", "");
- fs_.Create("out", "");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-"build tmp | tmp.imp: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverNowWantEdgeAndDependent) {
- // Verify that a dyndep file can be built and loaded to discover
- // that an edge and a dependent are actually wanted.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build tmp: touch || dd\n"
-" dyndep = dd\n"
-"build out: touch tmp\n"
-));
- fs_.Create("tmp", "");
- fs_.Create("out", "");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build tmp | tmp.imp: dyndep\n"
-);
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverCircular) {
- // Verify that a dyndep file can be built and loaded to discover
- // and reject a circular dependency.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out: r in || dd\n"
-" depfile = out.d\n"
-" dyndep = dd\n"
-"build in: r || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("out.d", "out: inimp\n");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out | circ: dyndep\n"
-"build in: dyndep | circ\n"
- );
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_FALSE(builder_.Build(&err));
- // Depending on how the pointers in Plan::ready_ work out, we could have
- // discovered the cycle from either starting point.
- EXPECT_TRUE(err == "dependency cycle: circ -> in -> circ" ||
- err == "dependency cycle: in -> circ -> in");
-}
-
-TEST_F(BuildWithLogTest, DyndepBuildDiscoverRestat) {
- // Verify that a dyndep file can be built and loaded to discover
- // that an edge has a restat binding.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule true\n"
-" command = true\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd: cp dd-in\n"
-"build out1: true in || dd\n"
-" dyndep = dd\n"
-"build out2: cat out1\n"));
-
- fs_.Create("out1", "");
- fs_.Create("out2", "");
- fs_.Create("dd-in",
-"ninja_dyndep_version = 1\n"
-"build out1: dyndep\n"
-" restat = 1\n"
-);
- fs_.Tick();
- fs_.Create("in", "");
-
- // Do a pre-build so that there's commands in the log for the outputs,
- // otherwise, the lack of an entry in the build log will cause "out2" to
- // rebuild regardless of restat.
- string err;
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
- EXPECT_EQ("true", command_runner_.commands_ran_[1]);
- EXPECT_EQ("cat out1 > out2", command_runner_.commands_ran_[2]);
-
- command_runner_.commands_ran_.clear();
- state_.Reset();
- fs_.Tick();
- fs_.Create("in", "");
-
- // We touched "in", so we should build "out1". But because "true" does not
- // touch "out1", we should cancel the build of "out2".
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("true", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, DyndepBuildDiscoverScheduledEdge) {
- // Verify that a dyndep file can be built and loaded to discover a
- // new input that itself is an output from an edge that has already
- // been scheduled but not finished. We should not re-schedule it.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build out1 | out1.imp: touch\n"
-"build zdd: cp zdd-in\n"
-" verify_active_edge = out1\n" // verify out1 is active when zdd is finished
-"build out2: cp out1 || zdd\n"
-" dyndep = zdd\n"
-));
- fs_.Create("zdd-in",
-"ninja_dyndep_version = 1\n"
-"build out2: dyndep | out1.imp\n"
-);
-
- // Enable concurrent builds so that we can load the dyndep file
- // while another edge is still active.
- command_runner_.max_active_edges_ = 2;
-
- // During the build "out1" and "zdd" should be built concurrently.
- // The fake command runner will finish these in reverse order
- // of the names of the first outputs, so "zdd" will finish first
- // and we will load the dyndep file while the edge for "out1" is
- // still active. This will add a new dependency on "out1.imp",
- // also produced by the active edge. The builder should not
- // re-schedule the already-active edge.
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out1", &err));
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- // Depending on how the pointers in Plan::ready_ work out, the first
- // two commands may have run in either order.
- EXPECT_TRUE((command_runner_.commands_ran_[0] == "touch out1 out1.imp" &&
- command_runner_.commands_ran_[1] == "cp zdd-in zdd") ||
- (command_runner_.commands_ran_[1] == "touch out1 out1.imp" &&
- command_runner_.commands_ran_[0] == "cp zdd-in zdd"));
- EXPECT_EQ("cp out1 out2", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepTwoLevelDirect) {
- // Verify that a clean dyndep file can depend on a dirty dyndep file
- // and be loaded properly after the dirty one is built and loaded.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd1: cp dd1-in\n"
-"build out1 | out1.imp: touch || dd1\n"
-" dyndep = dd1\n"
-"build dd2: cp dd2-in || dd1\n" // direct order-only dep on dd1
-"build out2: touch || dd2\n"
-" dyndep = dd2\n"
-));
- fs_.Create("out1.imp", "");
- fs_.Create("out2", "");
- fs_.Create("out2.imp", "");
- fs_.Create("dd1-in",
-"ninja_dyndep_version = 1\n"
-"build out1: dyndep\n"
-);
- fs_.Create("dd2-in", "");
- fs_.Create("dd2",
-"ninja_dyndep_version = 1\n"
-"build out2 | out2.imp: dyndep | out1.imp\n"
-);
-
- // During the build dd1 should be built and loaded. The RecomputeDirty
- // called as a result of loading dd1 should not cause dd2 to be loaded
- // because the builder will never get a chance to update the build plan
- // to account for dd2. Instead dd2 should only be later loaded once the
- // builder recognizes that it is now ready (as its order-only dependency
- // on dd1 has been satisfied). This test case verifies that each dyndep
- // file is loaded to update the build graph independently.
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch out1 out1.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out2 out2.imp", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepTwoLevelIndirect) {
- // Verify that dyndep files can add to an edge new implicit inputs that
- // correspond to implicit outputs added to other edges by other dyndep
- // files on which they (order-only) depend.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out $out.imp\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd1: cp dd1-in\n"
-"build out1: touch || dd1\n"
-" dyndep = dd1\n"
-"build dd2: cp dd2-in || out1\n" // indirect order-only dep on dd1
-"build out2: touch || dd2\n"
-" dyndep = dd2\n"
-));
- fs_.Create("out1.imp", "");
- fs_.Create("out2", "");
- fs_.Create("out2.imp", "");
- fs_.Create("dd1-in",
-"ninja_dyndep_version = 1\n"
-"build out1 | out1.imp: dyndep\n"
-);
- fs_.Create("dd2-in", "");
- fs_.Create("dd2",
-"ninja_dyndep_version = 1\n"
-"build out2 | out2.imp: dyndep | out1.imp\n"
-);
-
- // During the build dd1 should be built and loaded. Then dd2 should
- // be built and loaded. Loading dd2 should cause the builder to
- // recognize that out2 needs to be built even though it was originally
- // clean without dyndep info.
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out2", &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch out1 out1.imp", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch out2 out2.imp", command_runner_.commands_ran_[2]);
-}
-
-TEST_F(BuildTest, DyndepTwoLevelDiscoveredReady) {
- // Verify that a dyndep file can discover a new input whose
- // edge also has a dyndep file that is ready to load immediately.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd0: cp dd0-in\n"
-"build dd1: cp dd1-in\n"
-"build in: touch\n"
-"build tmp: touch || dd0\n"
-" dyndep = dd0\n"
-"build out: touch || dd1\n"
-" dyndep = dd1\n"
- ));
- fs_.Create("dd1-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | tmp\n"
-);
- fs_.Create("dd0-in", "");
- fs_.Create("dd0",
-"ninja_dyndep_version = 1\n"
-"build tmp: dyndep | in\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(4u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
- EXPECT_EQ("touch in", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch tmp", command_runner_.commands_ran_[2]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[3]);
-}
-
-TEST_F(BuildTest, DyndepTwoLevelDiscoveredDirty) {
- // Verify that a dyndep file can discover a new input whose
- // edge also has a dyndep file that needs to be built.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"rule cp\n"
-" command = cp $in $out\n"
-"build dd0: cp dd0-in\n"
-"build dd1: cp dd1-in\n"
-"build in: touch\n"
-"build tmp: touch || dd0\n"
-" dyndep = dd0\n"
-"build out: touch || dd1\n"
-" dyndep = dd1\n"
- ));
- fs_.Create("dd1-in",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | tmp\n"
-);
- fs_.Create("dd0-in",
-"ninja_dyndep_version = 1\n"
-"build tmp: dyndep | in\n"
-);
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
- ASSERT_EQ(5u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cp dd1-in dd1", command_runner_.commands_ran_[0]);
- EXPECT_EQ("cp dd0-in dd0", command_runner_.commands_ran_[1]);
- EXPECT_EQ("touch in", command_runner_.commands_ran_[2]);
- EXPECT_EQ("touch tmp", command_runner_.commands_ran_[3]);
- EXPECT_EQ("touch out", command_runner_.commands_ran_[4]);
-}
-
-TEST_F(BuildTest, Validation) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build out: cat in |@ validate\n"
- "build validate: cat in2\n"));
-
- fs_.Create("in", "");
- fs_.Create("in2", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Test touching "in" only rebuilds "out" ("validate" doesn't depend on
- // "out").
- fs_.Tick();
- fs_.Create("in", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
-
- // Test touching "in2" only rebuilds "validate" ("out" doesn't depend on
- // "validate").
- fs_.Tick();
- fs_.Create("in2", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in2 > validate", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, ValidationDependsOnOutput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build out: cat in |@ validate\n"
- "build validate: cat in2 | out\n"));
-
- fs_.Create("in", "");
- fs_.Create("in2", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Test touching "in" rebuilds "out" and "validate".
- fs_.Tick();
- fs_.Create("in", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Test touching "in2" only rebuilds "validate" ("out" doesn't depend on
- // "validate").
- fs_.Tick();
- fs_.Create("in2", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in2 > validate", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildWithDepsLogTest, ValidationThroughDepfile) {
- const char* manifest =
- "build out: cat in |@ validate\n"
- "build validate: cat in2 | out\n"
- "build out2: cat in3\n"
- " deps = gcc\n"
- " depfile = out2.d\n";
-
- string err;
-
- {
- fs_.Create("in", "");
- fs_.Create("in2", "");
- fs_.Create("in3", "");
- fs_.Create("out2.d", "out: out");
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
- EXPECT_TRUE(builder.AddTarget("out2", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // On the first build, only the out2 command is run.
- ASSERT_EQ(command_runner_.commands_ran_.size(), 1);
- EXPECT_EQ("cat in3 > out2", command_runner_.commands_ran_[0]);
-
- // The deps file should have been removed.
- EXPECT_EQ(0, fs_.Stat("out2.d", &err));
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-
- fs_.Tick();
- command_runner_.commands_ran_.clear();
-
- {
- fs_.Create("in2", "");
- fs_.Create("in3", "");
-
- State state;
- ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
-
- DepsLog deps_log;
- ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
- ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- ASSERT_EQ("", err);
-
- Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
- EXPECT_TRUE(builder.AddTarget("out2", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder.Build(&err));
- EXPECT_EQ("", err);
-
- // The out and validate actions should have been run as well as out2.
- ASSERT_EQ(command_runner_.commands_ran_.size(), 3);
- // out has to run first, as both out2 and validate depend on it.
- EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
-
- deps_log.Close();
- builder.command_runner_.release();
- }
-}
-
-TEST_F(BuildTest, ValidationCircular) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build out: cat in |@ out2\n"
- "build out2: cat in2 |@ out\n"));
-
- fs_.Create("in", "");
- fs_.Create("in2", "");
-
- string err;
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- EXPECT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- EXPECT_EQ(2u, command_runner_.commands_ran_.size());
-
- // Test touching "in" rebuilds "out".
- fs_.Tick();
- fs_.Create("in", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in > out", command_runner_.commands_ran_[0]);
-
- // Test touching "in2" rebuilds "out2".
- fs_.Tick();
- fs_.Create("in2", "");
-
- err.clear();
- command_runner_.commands_ran_.clear();
- state_.Reset();
- EXPECT_TRUE(builder_.AddTarget("out", &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- EXPECT_EQ("cat in2 > out2", command_runner_.commands_ran_[0]);
-}
-
-TEST_F(BuildTest, ValidationWithCircularDependency) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build out: cat in |@ validate\n"
- "build validate: cat validate_in | out\n"
- "build validate_in: cat validate\n"));
-
- fs_.Create("in", "");
-
- string err;
- EXPECT_FALSE(builder_.AddTarget("out", &err));
- EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err);
-}
diff --git a/src/canon_perftest.cc b/src/canon_perftest.cc
deleted file mode 100644
index 6b5e382..0000000
--- a/src/canon_perftest.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2012 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 <stdio.h>
-#include <string.h>
-
-#include "util.h"
-#include "metrics.h"
-
-using namespace std;
-
-const char kPath[] =
- "../../third_party/WebKit/Source/WebCore/"
- "platform/leveldb/LevelDBWriteBatch.cpp";
-
-int main() {
- vector<int> times;
-
- char buf[200];
- size_t len = strlen(kPath);
- strcpy(buf, kPath);
-
- for (int j = 0; j < 5; ++j) {
- const int kNumRepetitions = 2000000;
- int64_t start = GetTimeMillis();
- uint64_t slash_bits;
- for (int i = 0; i < kNumRepetitions; ++i) {
- CanonicalizePath(buf, &len, &slash_bits);
- }
- int delta = (int)(GetTimeMillis() - start);
- times.push_back(delta);
- }
-
- int min = times[0];
- int max = times[0];
- float total = 0;
- for (size_t i = 0; i < times.size(); ++i) {
- total += times[i];
- if (times[i] < min)
- min = times[i];
- else if (times[i] > max)
- max = times[i];
- }
-
- printf("min %dms max %dms avg %.1fms\n",
- min, max, total / times.size());
-}
diff --git a/src/clean.cc b/src/clean.cc
deleted file mode 100644
index e48eb8e..0000000
--- a/src/clean.cc
+++ /dev/null
@@ -1,302 +0,0 @@
-// 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.
-
-#include "clean.h"
-
-#include <assert.h>
-#include <stdio.h>
-
-#include "disk_interface.h"
-#include "graph.h"
-#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),
- cleaned_files_count_(0),
- disk_interface_(disk_interface),
- status_(0) {
-}
-
-int Cleaner::RemoveFile(const string& path) {
- return disk_interface_->RemoveFile(path);
-}
-
-bool Cleaner::FileExists(const string& path) {
- string err;
- TimeStamp mtime = disk_interface_->Stat(path, &err);
- if (mtime == -1)
- Error("%s", err.c_str());
- return mtime > 0; // Treat Stat() errors as "file does not exist".
-}
-
-void Cleaner::Report(const string& path) {
- ++cleaned_files_count_;
- if (IsVerbose())
- printf("Remove %s\n", path.c_str());
-}
-
-void Cleaner::Remove(const string& path) {
- if (!IsAlreadyRemoved(path)) {
- removed_.insert(path);
- if (config_.dry_run) {
- if (FileExists(path))
- Report(path);
- } else {
- int ret = RemoveFile(path);
- if (ret == 0)
- Report(path);
- else if (ret == -1)
- status_ = 1;
- }
- }
-}
-
-bool Cleaner::IsAlreadyRemoved(const string& path) {
- set<string>::iterator i = removed_.find(path);
- return (i != removed_.end());
-}
-
-void Cleaner::RemoveEdgeFiles(Edge* edge) {
- string depfile = edge->GetUnescapedDepfile();
- if (!depfile.empty())
- Remove(depfile);
-
- string rspfile = edge->GetUnescapedRspfile();
- if (!rspfile.empty())
- Remove(rspfile);
-}
-
-void Cleaner::PrintHeader() {
- if (config_.verbosity == BuildConfig::QUIET)
- return;
- printf("Cleaning...");
- if (IsVerbose())
- printf("\n");
- else
- printf(" ");
- fflush(stdout);
-}
-
-void Cleaner::PrintFooter() {
- if (config_.verbosity == BuildConfig::QUIET)
- return;
- printf("%d files.\n", cleaned_files_count_);
-}
-
-int Cleaner::CleanAll(bool generator) {
- Reset();
- PrintHeader();
- LoadDyndeps();
- for (vector<Edge*>::iterator e = state_->edges_.begin();
- e != state_->edges_.end(); ++e) {
- // Do not try to remove phony targets
- if ((*e)->is_phony())
- continue;
- // Do not remove generator's files unless generator specified.
- if (!generator && (*e)->is_generator())
- continue;
- for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
- out_node != (*e)->outputs_.end(); ++out_node) {
- Remove((*out_node)->path());
- }
-
- RemoveEdgeFiles(*e);
- }
- PrintFooter();
- 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);
- // Detecting stale outputs works as follows:
- //
- // - If it has no Node, it is not in the build graph, or the deps log
- // anymore, hence is stale.
- //
- // - If it isn't an output or input for any edge, it comes from a stale
- // entry in the deps log, but no longer referenced from the build
- // graph.
- //
- if (!n || (!n->in_edge() && n->out_edges().empty())) {
- 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
- if (!e->is_phony()) {
- Remove(target->path());
- RemoveEdgeFiles(e);
- }
- for (vector<Node*>::iterator n = e->inputs_.begin(); n != e->inputs_.end();
- ++n) {
- Node* next = *n;
- // call DoCleanTarget recursively if this node has not been visited
- if (cleaned_.count(next) == 0) {
- DoCleanTarget(next);
- }
- }
- }
-
- // mark this target to be cleaned already
- cleaned_.insert(target);
-}
-
-int Cleaner::CleanTarget(Node* target) {
- assert(target);
-
- Reset();
- PrintHeader();
- LoadDyndeps();
- DoCleanTarget(target);
- PrintFooter();
- return status_;
-}
-
-int Cleaner::CleanTarget(const char* target) {
- assert(target);
-
- Reset();
- Node* node = state_->LookupNode(target);
- if (node) {
- CleanTarget(node);
- } else {
- Error("unknown target '%s'", target);
- status_ = 1;
- }
- return status_;
-}
-
-int Cleaner::CleanTargets(int target_count, char* targets[]) {
- Reset();
- PrintHeader();
- LoadDyndeps();
- for (int i = 0; i < target_count; ++i) {
- string target_name = targets[i];
- if (target_name.empty()) {
- Error("failed to canonicalize '': empty path");
- status_ = 1;
- continue;
- }
- uint64_t slash_bits;
- CanonicalizePath(&target_name, &slash_bits);
- Node* target = state_->LookupNode(target_name);
- if (target) {
- if (IsVerbose())
- printf("Target %s\n", target_name.c_str());
- DoCleanTarget(target);
- } else {
- Error("unknown target '%s'", target_name.c_str());
- status_ = 1;
- }
- }
- PrintFooter();
- return status_;
-}
-
-void Cleaner::DoCleanRule(const Rule* rule) {
- assert(rule);
-
- for (vector<Edge*>::iterator e = state_->edges_.begin();
- e != state_->edges_.end(); ++e) {
- if ((*e)->rule().name() == rule->name()) {
- for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
- out_node != (*e)->outputs_.end(); ++out_node) {
- Remove((*out_node)->path());
- RemoveEdgeFiles(*e);
- }
- }
- }
-}
-
-int Cleaner::CleanRule(const Rule* rule) {
- assert(rule);
-
- Reset();
- PrintHeader();
- LoadDyndeps();
- DoCleanRule(rule);
- PrintFooter();
- return status_;
-}
-
-int Cleaner::CleanRule(const char* rule) {
- assert(rule);
-
- Reset();
- const Rule* r = state_->bindings().LookupRule(rule);
- if (r) {
- CleanRule(r);
- } else {
- Error("unknown rule '%s'", rule);
- status_ = 1;
- }
- return status_;
-}
-
-int Cleaner::CleanRules(int rule_count, char* rules[]) {
- assert(rules);
-
- Reset();
- PrintHeader();
- LoadDyndeps();
- for (int i = 0; i < rule_count; ++i) {
- const char* rule_name = rules[i];
- const Rule* rule = state_->bindings().LookupRule(rule_name);
- if (rule) {
- if (IsVerbose())
- printf("Rule %s\n", rule_name);
- DoCleanRule(rule);
- } else {
- Error("unknown rule '%s'", rule_name);
- status_ = 1;
- }
- }
- PrintFooter();
- return status_;
-}
-
-void Cleaner::Reset() {
- status_ = 0;
- cleaned_files_count_ = 0;
- removed_.clear();
- cleaned_.clear();
-}
-
-void Cleaner::LoadDyndeps() {
- // Load dyndep files that exist, before they are cleaned.
- for (vector<Edge*>::iterator e = state_->edges_.begin();
- e != state_->edges_.end(); ++e) {
- if (Node* dyndep = (*e)->dyndep_) {
- // Capture and ignore errors loading the dyndep file.
- // We clean as much of the graph as we know.
- std::string err;
- dyndep_loader_.LoadDyndeps(dyndep, &err);
- }
- }
-}
diff --git a/src/clean.h b/src/clean.h
deleted file mode 100644
index 0ff1221..0000000
--- a/src/clean.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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.
-
-#ifndef NINJA_CLEAN_H_
-#define NINJA_CLEAN_H_
-
-#include <set>
-#include <string>
-
-#include "build_config.h"
-#include "build_log.h"
-#include "dyndep.h"
-
-struct State;
-struct Node;
-struct Rule;
-struct DiskInterface;
-
-struct Cleaner {
- /// Build a cleaner object with the given @a disk_interface
- Cleaner(State* state,
- const BuildConfig& config,
- DiskInterface* disk_interface);
-
- /// Clean the given @a target and all the file built for it.
- /// @return non-zero if an error occurs.
- int CleanTarget(Node* target);
- /// Clean the given target @a target.
- /// @return non-zero if an error occurs.
- int CleanTarget(const char* target);
- /// Clean the given target @a targets.
- /// @return non-zero if an error occurs.
- int CleanTargets(int target_count, char* targets[]);
-
- /// Clean all built files, except for files created by generator rules.
- /// @param generator If set, also clean files created by generator rules.
- /// @return non-zero if an error occurs.
- int CleanAll(bool generator = false);
-
- /// Clean all the file built with the given rule @a rule.
- /// @return non-zero if an error occurs.
- int CleanRule(const Rule* rule);
- /// Clean the file produced by the given @a rule.
- /// @return non-zero if an error occurs.
- int CleanRule(const char* rule);
- /// 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 {
- return cleaned_files_count_;
- }
-
- /// @return whether the cleaner is in verbose mode.
- bool IsVerbose() const {
- return (config_.verbosity != BuildConfig::QUIET
- && (config_.verbosity == BuildConfig::VERBOSE || config_.dry_run));
- }
-
- private:
- /// Remove the file @a path.
- /// @return whether the file has been removed.
- int RemoveFile(const std::string& path);
- /// @returns whether the file @a path exists.
- 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 std::string& path);
- /// @return whether the given @a path has already been removed.
- bool IsAlreadyRemoved(const std::string& path);
- /// Remove the depfile and rspfile for an Edge.
- void RemoveEdgeFiles(Edge* edge);
-
- /// Helper recursive method for CleanTarget().
- void DoCleanTarget(Node* target);
- void PrintHeader();
- void PrintFooter();
- void DoCleanRule(const Rule* rule);
- void Reset();
-
- /// Load dependencies from dyndep bindings.
- void LoadDyndeps();
-
- State* state_;
- const BuildConfig& config_;
- DyndepLoader dyndep_loader_;
- std::set<std::string> removed_;
- std::set<Node*> cleaned_;
- int cleaned_files_count_;
- DiskInterface* disk_interface_;
- int status_;
-};
-
-#endif // NINJA_CLEAN_H_
diff --git a/src/clean_test.cc b/src/clean_test.cc
deleted file mode 100644
index e99909c..0000000
--- a/src/clean_test.cc
+++ /dev/null
@@ -1,601 +0,0 @@
-// 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.
-
-#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_;
- virtual void SetUp() {
- config_.verbosity = BuildConfig::QUIET;
- }
-};
-
-TEST_F(CleanTest, CleanAll) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build in1: cat src1\n"
-"build out1: cat in1\n"
-"build in2: cat src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(4, cleaner.cleaned_files_count());
- EXPECT_EQ(4u, fs_.files_removed_.size());
-
- // Check they are removed.
- string err;
- EXPECT_EQ(0, fs_.Stat("in1", &err));
- EXPECT_EQ(0, fs_.Stat("out1", &err));
- EXPECT_EQ(0, fs_.Stat("in2", &err));
- EXPECT_EQ(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanAllDryRun) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build in1: cat src1\n"
-"build out1: cat in1\n"
-"build in2: cat src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- config_.dry_run = true;
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(4, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-
- // Check they are not removed.
- string err;
- EXPECT_LT(0, fs_.Stat("in1", &err));
- EXPECT_LT(0, fs_.Stat("out1", &err));
- EXPECT_LT(0, fs_.Stat("in2", &err));
- EXPECT_LT(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(4, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanTarget) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build in1: cat src1\n"
-"build out1: cat in1\n"
-"build in2: cat src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-
- // Check they are removed.
- string err;
- EXPECT_EQ(0, fs_.Stat("in1", &err));
- EXPECT_EQ(0, fs_.Stat("out1", &err));
- EXPECT_LT(0, fs_.Stat("in2", &err));
- EXPECT_LT(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- ASSERT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanTargetDryRun) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build in1: cat src1\n"
-"build out1: cat in1\n"
-"build in2: cat src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- config_.dry_run = true;
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-
- // Check they are not removed.
- string err;
- EXPECT_LT(0, fs_.Stat("in1", &err));
- EXPECT_LT(0, fs_.Stat("out1", &err));
- EXPECT_LT(0, fs_.Stat("in2", &err));
- EXPECT_LT(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- ASSERT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanRule) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cat_e\n"
-" command = cat -e $in > $out\n"
-"build in1: cat_e src1\n"
-"build out1: cat in1\n"
-"build in2: cat_e src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanRule("cat_e"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-
- // Check they are removed.
- string err;
- EXPECT_EQ(0, fs_.Stat("in1", &err));
- EXPECT_LT(0, fs_.Stat("out1", &err));
- EXPECT_EQ(0, fs_.Stat("in2", &err));
- EXPECT_LT(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- ASSERT_EQ(0, cleaner.CleanRule("cat_e"));
- EXPECT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanRuleDryRun) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cat_e\n"
-" command = cat -e $in > $out\n"
-"build in1: cat_e src1\n"
-"build out1: cat in1\n"
-"build in2: cat_e src2\n"
-"build out2: cat in2\n"));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- config_.dry_run = true;
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanRule("cat_e"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-
- // Check they are not removed.
- string err;
- EXPECT_LT(0, fs_.Stat("in1", &err));
- EXPECT_LT(0, fs_.Stat("out1", &err));
- EXPECT_LT(0, fs_.Stat("in2", &err));
- EXPECT_LT(0, fs_.Stat("out2", &err));
- fs_.files_removed_.clear();
-
- ASSERT_EQ(0, cleaner.CleanRule("cat_e"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(0u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanRuleGenerator) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule regen\n"
-" command = cat $in > $out\n"
-" generator = 1\n"
-"build out1: cat in1\n"
-"build out2: regen in2\n"));
- fs_.Create("out1", "");
- fs_.Create("out2", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(1, cleaner.cleaned_files_count());
- EXPECT_EQ(1u, fs_.files_removed_.size());
-
- fs_.Create("out1", "");
-
- EXPECT_EQ(0, cleaner.CleanAll(/*generator=*/true));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanDepFile) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc $in > $out\n"
-" depfile = $out.d\n"
-"build out1: cc in1\n"));
- fs_.Create("out1", "");
- fs_.Create("out1.d", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanDepFileOnCleanTarget) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc $in > $out\n"
-" depfile = $out.d\n"
-"build out1: cc in1\n"));
- fs_.Create("out1", "");
- fs_.Create("out1.d", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanDepFileOnCleanRule) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc $in > $out\n"
-" depfile = $out.d\n"
-"build out1: cc in1\n"));
- fs_.Create("out1", "");
- fs_.Create("out1.d", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanRule("cc"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanDyndep) {
- // Verify that a dyndep file can be loaded to discover a new output
- // to be cleaned.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("in", "");
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out | out.imp: dyndep\n"
-);
- fs_.Create("out", "");
- fs_.Create("out.imp", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-
- string err;
- EXPECT_EQ(0, fs_.Stat("out", &err));
- EXPECT_EQ(0, fs_.Stat("out.imp", &err));
-}
-
-TEST_F(CleanTest, CleanDyndepMissing) {
- // Verify that a missing dyndep file is tolerated.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in || dd\n"
-" dyndep = dd\n"
- ));
- fs_.Create("in", "");
- fs_.Create("out", "");
- fs_.Create("out.imp", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
-
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(1, cleaner.cleaned_files_count());
- EXPECT_EQ(1u, fs_.files_removed_.size());
-
- string err;
- EXPECT_EQ(0, fs_.Stat("out", &err));
- EXPECT_EQ(1, fs_.Stat("out.imp", &err));
-}
-
-TEST_F(CleanTest, CleanRspFile) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc\n"
-" command = cc $in > $out\n"
-" rspfile = $rspfile\n"
-" rspfile_content=$in\n"
-"build out1: cc in1\n"
-" rspfile = cc1.rsp\n"));
- fs_.Create("out1", "");
- fs_.Create("cc1.rsp", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_EQ(2u, fs_.files_removed_.size());
-}
-
-TEST_F(CleanTest, CleanRsp) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cat_rsp \n"
-" command = cat $rspfile > $out\n"
-" rspfile = $rspfile\n"
-" rspfile_content = $in\n"
-"build in1: cat src1\n"
-"build out1: cat in1\n"
-"build in2: cat_rsp src2\n"
-" rspfile=in2.rsp\n"
-"build out2: cat_rsp in2\n"
-" rspfile=out2.rsp\n"
-));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- fs_.Create("in2.rsp", "");
- fs_.Create("out2.rsp", "");
- fs_.Create("in2", "");
- fs_.Create("out2", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- ASSERT_EQ(0, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanTarget("out1"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanTarget("in2"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- ASSERT_EQ(0, cleaner.CleanRule("cat_rsp"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
-
- EXPECT_EQ(6u, fs_.files_removed_.size());
-
- // Check they are removed.
- string err;
- EXPECT_EQ(0, fs_.Stat("in1", &err));
- EXPECT_EQ(0, fs_.Stat("out1", &err));
- EXPECT_EQ(0, fs_.Stat("in2", &err));
- EXPECT_EQ(0, fs_.Stat("out2", &err));
- EXPECT_EQ(0, fs_.Stat("in2.rsp", &err));
- EXPECT_EQ(0, fs_.Stat("out2.rsp", &err));
-}
-
-TEST_F(CleanTest, CleanFailure) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "build dir: cat src1\n"));
- fs_.MakeDir("dir");
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_NE(0, cleaner.CleanAll());
-}
-
-TEST_F(CleanTest, CleanPhony) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build phony: phony t1 t2\n"
-"build t1: cat\n"
-"build t2: cat\n"));
-
- fs_.Create("phony", "");
- fs_.Create("t1", "");
- fs_.Create("t2", "");
-
- // Check that CleanAll does not remove "phony".
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_LT(0, fs_.Stat("phony", &err));
-
- fs_.Create("t1", "");
- fs_.Create("t2", "");
-
- // Check that CleanTarget does not remove "phony".
- EXPECT_EQ(0, cleaner.CleanTarget("phony"));
- EXPECT_EQ(2, cleaner.cleaned_files_count());
- EXPECT_LT(0, fs_.Stat("phony", &err));
-}
-
-TEST_F(CleanTest, CleanDepFileAndRspFileWithSpaces) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule cc_dep\n"
-" command = cc $in > $out\n"
-" depfile = $out.d\n"
-"rule cc_rsp\n"
-" command = cc $in > $out\n"
-" rspfile = $out.rsp\n"
-" rspfile_content = $in\n"
-"build out$ 1: cc_dep in$ 1\n"
-"build out$ 2: cc_rsp in$ 1\n"
-));
- fs_.Create("out 1", "");
- fs_.Create("out 2", "");
- fs_.Create("out 1.d", "");
- fs_.Create("out 2.rsp", "");
-
- Cleaner cleaner(&state_, config_, &fs_);
- EXPECT_EQ(0, cleaner.CleanAll());
- EXPECT_EQ(4, cleaner.cleaned_files_count());
- EXPECT_EQ(4u, fs_.files_removed_.size());
-
- string err;
- EXPECT_EQ(0, fs_.Stat("out 1", &err));
- EXPECT_EQ(0, fs_.Stat("out 2", &err));
- 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();
-}
-
-TEST_F(CleanDeadTest, CleanDeadPreservesInputs) {
- 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"
-));
- // This manifest does not build out1 anymore, but makes
- // it an implicit input. CleanDead should detect this
- // and preserve it.
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out2: cat in | out1\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(0, cleaner2.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));
-
- // Nothing to do now.
- EXPECT_EQ(0, cleaner2.CleanDead(log2.entries()));
- EXPECT_EQ(0, cleaner2.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));
- log2.Close();
-}
-} // anonymous namespace
diff --git a/src/clparser.cc b/src/clparser.cc
deleted file mode 100644
index 3d3e7de..0000000
--- a/src/clparser.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2015 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 "clparser.h"
-
-#include <algorithm>
-#include <assert.h>
-#include <string.h>
-
-#include "metrics.h"
-#include "string_piece_util.h"
-
-#ifdef _WIN32
-#include "includes_normalize.h"
-#include "string_piece.h"
-#else
-#include "util.h"
-#endif
-
-using namespace std;
-
-namespace {
-
-/// Return true if \a input ends with \a needle.
-bool EndsWith(const string& input, const string& needle) {
- return (input.size() >= needle.size() &&
- input.substr(input.size() - needle.size()) == needle);
-}
-
-} // anonymous namespace
-
-// static
-string CLParser::FilterShowIncludes(const string& line,
- const string& deps_prefix) {
- const string kDepsPrefixEnglish = "Note: including file: ";
- const char* in = line.c_str();
- const char* end = in + line.size();
- const string& prefix = deps_prefix.empty() ? kDepsPrefixEnglish : deps_prefix;
- if (end - in > (int)prefix.size() &&
- memcmp(in, prefix.c_str(), (int)prefix.size()) == 0) {
- in += prefix.size();
- while (*in == ' ')
- ++in;
- return line.substr(in - line.c_str());
- }
- return "";
-}
-
-// static
-bool CLParser::IsSystemInclude(string path) {
- transform(path.begin(), path.end(), path.begin(), ToLowerASCII);
- // TODO: this is a heuristic, perhaps there's a better way?
- return (path.find("program files") != string::npos ||
- path.find("microsoft visual studio") != string::npos);
-}
-
-// static
-bool CLParser::FilterInputFilename(string line) {
- transform(line.begin(), line.end(), line.begin(), ToLowerASCII);
- // TODO: other extensions, like .asm?
- return EndsWith(line, ".c") ||
- EndsWith(line, ".cc") ||
- EndsWith(line, ".cxx") ||
- EndsWith(line, ".cpp") ||
- EndsWith(line, ".c++");
-}
-
-// static
-bool CLParser::Parse(const string& output, const string& deps_prefix,
- string* filtered_output, string* err) {
- METRIC_RECORD("CLParser::Parse");
-
- // Loop over all lines in the output to process them.
- assert(&output != filtered_output);
- size_t start = 0;
- bool seen_show_includes = false;
-#ifdef _WIN32
- IncludesNormalize normalizer(".");
-#endif
-
- while (start < output.size()) {
- size_t end = output.find_first_of("\r\n", start);
- if (end == string::npos)
- end = output.size();
- string line = output.substr(start, end - start);
-
- string include = FilterShowIncludes(line, deps_prefix);
- if (!include.empty()) {
- seen_show_includes = true;
- string normalized;
-#ifdef _WIN32
- if (!normalizer.Normalize(include, &normalized, err))
- return false;
-#else
- // TODO: should this make the path relative to cwd?
- normalized = include;
- uint64_t slash_bits;
- CanonicalizePath(&normalized, &slash_bits);
-#endif
- if (!IsSystemInclude(normalized))
- includes_.insert(normalized);
- } else if (!seen_show_includes && FilterInputFilename(line)) {
- // Drop it.
- // TODO: if we support compiling multiple output files in a single
- // cl.exe invocation, we should stash the filename.
- } else {
- filtered_output->append(line);
- filtered_output->append("\n");
- }
-
- if (end < output.size() && output[end] == '\r')
- ++end;
- if (end < output.size() && output[end] == '\n')
- ++end;
- start = end;
- }
-
- return true;
-}
diff --git a/src/clparser.h b/src/clparser.h
deleted file mode 100644
index 2a33628..0000000
--- a/src/clparser.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2015 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_CLPARSER_H_
-#define NINJA_CLPARSER_H_
-
-#include <set>
-#include <string>
-
-/// Visual Studio's cl.exe requires some massaging to work with Ninja;
-/// for example, it emits include information on stderr in a funny
-/// format when building with /showIncludes. This class parses this
-/// output.
-struct CLParser {
- /// Parse a line of cl.exe output and extract /showIncludes info.
- /// If a dependency is extracted, returns a nonempty string.
- /// Exposed for testing.
- 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(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(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 std::string& output, const std::string& deps_prefix,
- std::string* filtered_output, std::string* err);
-
- std::set<std::string> includes_;
-};
-
-#endif // NINJA_CLPARSER_H_
diff --git a/src/clparser_perftest.cc b/src/clparser_perftest.cc
deleted file mode 100644
index 008ac46..0000000
--- a/src/clparser_perftest.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2017 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 <stdio.h>
-#include <stdlib.h>
-
-#include "clparser.h"
-#include "metrics.h"
-
-using namespace std;
-
-int main(int argc, char* argv[]) {
- // Output of /showIncludes from #include <iostream>
- string perf_testdata =
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\iostream\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\istream\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ostream\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ios\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocnum\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\climits\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\yvals.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xkeycheck.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\crtdefs.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\sal.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ConcurrencySal.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vadefs.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\use_ansi.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\limits.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cmath\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\math.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xtgmath.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xtr1common\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdlib\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stdlib.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_malloc.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_search.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stddef.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstdlib.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdio\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\stdio.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstdio.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_stdio_config.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\streambuf\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xiosbase\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocale\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstring\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\string.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_memory.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_memcpy_s.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\errno.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_string.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wstring.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\stdexcept\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\exception\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\type_traits\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xstddef\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstddef\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\initializer_list\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\malloc.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_exception.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\eh.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_terminate.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xstring\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xmemory0\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cstdint\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\stdint.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\limits\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ymath.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cfloat\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\float.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cwchar\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\wchar.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wconio.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wctype.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wdirect.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wio.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_share.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wprocess.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\corecrt_wtime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\sys/stat.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\sys/types.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\new\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_new.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xutility\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\utility\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\iosfwd\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\crtdbg.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_new_debug.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xatomic0.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\intrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\setjmp.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\immintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\wmmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\nmmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\smmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\tmmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\pmmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\emmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xmmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\mmintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\ammintrin.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\mm3dnow.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\typeinfo\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime_typeinfo.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\vcruntime.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocinfo\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xlocinfo.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\ctype.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\locale.h\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\xfacet\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\system_error\r\n"
- "Note: including file: C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE\\cerrno\r\n"
- "Note: including file: C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt\\share.h\r\n";
-
- for (int limit = 1 << 10; limit < (1<<20); limit *= 2) {
- int64_t start = GetTimeMillis();
- for (int rep = 0; rep < limit; ++rep) {
- string output;
- string err;
-
- CLParser parser;
- if (!parser.Parse(perf_testdata, "", &output, &err)) {
- printf("%s\n", err.c_str());
- return 1;
- }
- }
- int64_t end = GetTimeMillis();
-
- if (end - start > 2000) {
- int delta_ms = (int)(end - start);
- printf("Parse %d times in %dms avg %.1fus\n",
- limit, delta_ms, float(delta_ms * 1000) / limit);
- break;
- }
- }
-
- return 0;
-}
diff --git a/src/clparser_test.cc b/src/clparser_test.cc
deleted file mode 100644
index f141680..0000000
--- a/src/clparser_test.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// 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.
-
-#include "clparser.h"
-
-#include "test.h"
-#include "util.h"
-
-using namespace std;
-
-TEST(CLParserTest, ShowIncludes) {
- ASSERT_EQ("", CLParser::FilterShowIncludes("", ""));
-
- ASSERT_EQ("", CLParser::FilterShowIncludes("Sample compiler output", ""));
- ASSERT_EQ("c:\\Some Files\\foobar.h",
- CLParser::FilterShowIncludes("Note: including file: "
- "c:\\Some Files\\foobar.h", ""));
- ASSERT_EQ("c:\\initspaces.h",
- CLParser::FilterShowIncludes("Note: including file: "
- "c:\\initspaces.h", ""));
- ASSERT_EQ("c:\\initspaces.h",
- CLParser::FilterShowIncludes("Non-default prefix: inc file: "
- "c:\\initspaces.h",
- "Non-default prefix: inc file:"));
-}
-
-TEST(CLParserTest, FilterInputFilename) {
- ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc"));
- ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc"));
- ASSERT_TRUE(CLParser::FilterInputFilename("baz.c"));
- ASSERT_TRUE(CLParser::FilterInputFilename("FOOBAR.CC"));
-
- ASSERT_FALSE(CLParser::FilterInputFilename(
- "src\\cl_helper.cc(166) : fatal error C1075: end "
- "of file found ..."));
-}
-
-TEST(CLParserTest, ParseSimple) {
- CLParser parser;
- string output, err;
- ASSERT_TRUE(parser.Parse(
- "foo\r\n"
- "Note: inc file prefix: foo.h\r\n"
- "bar\r\n",
- "Note: inc file prefix:", &output, &err));
-
- ASSERT_EQ("foo\nbar\n", output);
- ASSERT_EQ(1u, parser.includes_.size());
- ASSERT_EQ("foo.h", *parser.includes_.begin());
-}
-
-TEST(CLParserTest, ParseFilenameFilter) {
- CLParser parser;
- string output, err;
- ASSERT_TRUE(parser.Parse(
- "foo.cc\r\n"
- "cl: warning\r\n",
- "", &output, &err));
- ASSERT_EQ("cl: warning\n", output);
-}
-
-TEST(CLParserTest, NoFilenameFilterAfterShowIncludes) {
- CLParser parser;
- string output, err;
- ASSERT_TRUE(parser.Parse(
- "foo.cc\r\n"
- "Note: including file: foo.h\r\n"
- "something something foo.cc\r\n",
- "", &output, &err));
- ASSERT_EQ("something something foo.cc\n", output);
-}
-
-TEST(CLParserTest, ParseSystemInclude) {
- CLParser parser;
- string output, err;
- ASSERT_TRUE(parser.Parse(
- "Note: including file: c:\\Program Files\\foo.h\r\n"
- "Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n"
- "Note: including file: path.h\r\n",
- "", &output, &err));
- // We should have dropped the first two includes because they look like
- // system headers.
- ASSERT_EQ("", output);
- ASSERT_EQ(1u, parser.includes_.size());
- ASSERT_EQ("path.h", *parser.includes_.begin());
-}
-
-TEST(CLParserTest, DuplicatedHeader) {
- CLParser parser;
- string output, err;
- ASSERT_TRUE(parser.Parse(
- "Note: including file: foo.h\r\n"
- "Note: including file: bar.h\r\n"
- "Note: including file: foo.h\r\n",
- "", &output, &err));
- // We should have dropped one copy of foo.h.
- ASSERT_EQ("", output);
- ASSERT_EQ(2u, parser.includes_.size());
-}
-
-TEST(CLParserTest, DuplicatedHeaderPathConverted) {
- CLParser parser;
- string output, err;
-
- // This isn't inline in the Parse() call below because the #ifdef in
- // a macro expansion would confuse MSVC2013's preprocessor.
- const char kInput[] =
- "Note: including file: sub/./foo.h\r\n"
- "Note: including file: bar.h\r\n"
-#ifdef _WIN32
- "Note: including file: sub\\foo.h\r\n";
-#else
- "Note: including file: sub/foo.h\r\n";
-#endif
- ASSERT_TRUE(parser.Parse(kInput, "", &output, &err));
- // We should have dropped one copy of foo.h.
- ASSERT_EQ("", output);
- ASSERT_EQ(2u, parser.includes_.size());
-}
diff --git a/src/debug_flags.cc b/src/debug_flags.cc
deleted file mode 100644
index 44b14c4..0000000
--- a/src/debug_flags.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2012 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.
-
-bool g_explaining = false;
-
-bool g_keep_depfile = false;
-
-bool g_keep_rsp = false;
-
-bool g_experimental_statcache = true;
diff --git a/src/debug_flags.h b/src/debug_flags.h
deleted file mode 100644
index e08a43b..0000000
--- a/src/debug_flags.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2012 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_EXPLAIN_H_
-#define NINJA_EXPLAIN_H_
-
-#include <stdio.h>
-
-#define EXPLAIN(fmt, ...) { \
- if (g_explaining) \
- fprintf(stderr, "ninja explain: " fmt "\n", __VA_ARGS__); \
-}
-
-extern bool g_explaining;
-
-extern bool g_keep_depfile;
-
-extern bool g_keep_rsp;
-
-extern bool g_experimental_statcache;
-
-#endif // NINJA_EXPLAIN_H_
diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc
deleted file mode 100644
index 98fba2e..0000000
--- a/src/depfile_parser.cc
+++ /dev/null
@@ -1,371 +0,0 @@
-/* Generated by re2c */
-// 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.
-
-#include "depfile_parser.h"
-#include "util.h"
-
-#include <algorithm>
-
-using namespace std;
-
-DepfileParser::DepfileParser(DepfileParserOptions options)
- : options_(options)
-{
-}
-
-// A note on backslashes in Makefiles, from reading the docs:
-// Backslash-newline is the line continuation character.
-// Backslash-# escapes a # (otherwise meaningful as a comment start).
-// Backslash-% escapes a % (otherwise meaningful as a special).
-// Finally, quoting the GNU manual, "Backslashes that are not in danger
-// of quoting ‘%’ characters go unmolested."
-// How do you end a line with a backslash? The netbsd Make docs suggest
-// reading the result of a shell command echoing a backslash!
-//
-// Rather than implement all of above, we follow what GCC/Clang produces:
-// Backslashes escape a space or hash sign.
-// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
-// followed by space.
-// When a space is preceded by 2N backslashes, it represents 2N backslashes at
-// the end of a filename.
-// A hash sign is escaped by a single backslash. All other backslashes remain
-// unchanged.
-//
-// If anyone actually has depfiles that rely on the more complicated
-// behavior we can adjust this.
-bool DepfileParser::Parse(string* content, string* err) {
- // in: current parser input point.
- // end: end of input.
- // parsing_targets: whether we are parsing targets or dependencies.
- char* in = &(*content)[0];
- char* end = in + content->size();
- bool have_target = 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
- // as we de-escape backslashes).
- char* out = in;
- // filename: start of the current parsed filename.
- char* filename = out;
- for (;;) {
- // start: beginning of the current parsed span.
- const char* start = in;
- char* yymarker = NULL;
-
- {
- unsigned char yych;
- static const unsigned char yybm[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 128, 0, 0, 0, 128, 0, 0,
- 128, 128, 0, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 0, 0, 128, 0, 0,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 0, 128, 0, 128,
- 0, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 0, 128, 128, 0,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- };
- yych = *in;
- if (yybm[0+yych] & 128) {
- goto yy9;
- }
- if (yych <= '\r') {
- if (yych <= '\t') {
- if (yych >= 0x01) goto yy4;
- } else {
- if (yych <= '\n') goto yy6;
- if (yych <= '\f') goto yy4;
- goto yy8;
- }
- } else {
- if (yych <= '$') {
- if (yych <= '#') goto yy4;
- goto yy12;
- } else {
- if (yych <= '?') goto yy4;
- if (yych <= '\\') goto yy13;
- goto yy4;
- }
- }
- ++in;
- {
- break;
- }
-yy4:
- ++in;
-yy5:
- {
- // For any other character (e.g. whitespace), swallow it here,
- // allowing the outer logic to loop around again.
- break;
- }
-yy6:
- ++in;
- {
- // A newline ends the current file name and the current rule.
- have_newline = true;
- break;
- }
-yy8:
- yych = *++in;
- if (yych == '\n') goto yy6;
- goto yy5;
-yy9:
- yych = *++in;
- if (yybm[0+yych] & 128) {
- goto yy9;
- }
-yy11:
- {
- // Got a span of plain text.
- int len = (int)(in - start);
- // Need to shift it over if we're overwriting backslashes.
- if (out < start)
- memmove(out, start, len);
- out += len;
- continue;
- }
-yy12:
- yych = *++in;
- if (yych == '$') goto yy14;
- goto yy5;
-yy13:
- yych = *(yymarker = ++in);
- if (yych <= ' ') {
- if (yych <= '\n') {
- if (yych <= 0x00) goto yy5;
- if (yych <= '\t') goto yy16;
- goto yy17;
- } else {
- if (yych == '\r') goto yy19;
- if (yych <= 0x1F) goto yy16;
- goto yy21;
- }
- } else {
- if (yych <= '9') {
- if (yych == '#') goto yy23;
- goto yy16;
- } else {
- if (yych <= ':') goto yy25;
- if (yych == '\\') goto yy27;
- goto yy16;
- }
- }
-yy14:
- ++in;
- {
- // De-escape dollar character.
- *out++ = '$';
- continue;
- }
-yy16:
- ++in;
- goto yy11;
-yy17:
- ++in;
- {
- // A line continuation ends the current file name.
- break;
- }
-yy19:
- yych = *++in;
- if (yych == '\n') goto yy17;
- in = yymarker;
- goto yy5;
-yy21:
- ++in;
- {
- // 2N+1 backslashes plus space -> N backslashes plus space.
- int len = (int)(in - start);
- int n = len / 2 - 1;
- if (out < start)
- memset(out, '\\', n);
- out += n;
- *out++ = ' ';
- continue;
- }
-yy23:
- ++in;
- {
- // De-escape hash sign, but preserve other leading backslashes.
- int len = (int)(in - start);
- if (len > 2 && out < start)
- memset(out, '\\', len - 2);
- out += len - 2;
- *out++ = '#';
- continue;
- }
-yy25:
- yych = *++in;
- if (yych <= '\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;
- if (yych <= 0x1F) goto yy16;
- goto yy30;
- }
- } else {
- if (yych <= '9') {
- if (yych == '#') goto yy23;
- goto yy16;
- } else {
- if (yych <= ':') goto yy25;
- if (yych == '\\') goto yy32;
- goto yy16;
- }
- }
-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.
- int len = (int)(in - start);
- if (out < start)
- memset(out, '\\', len - 1);
- out += len - 1;
- break;
- }
-yy32:
- 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;
- if (yych <= 0x1F) goto yy16;
- goto yy21;
- }
- } else {
- if (yych <= '9') {
- if (yych == '#') goto yy23;
- goto yy16;
- } else {
- if (yych <= ':') goto yy25;
- if (yych == '\\') goto yy27;
- goto yy16;
- }
- }
- }
-
- }
-
- int len = (int)(out - filename);
- const bool is_dependency = !parsing_targets;
- if (len > 0 && filename[len - 1] == ':') {
- len--; // Strip off trailing colon, if any.
- parsing_targets = false;
- have_target = true;
- }
-
- if (len > 0) {
- 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;
- }
- // 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);
- }
- } 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;
- poisoned_input = false;
- }
- }
- if (!have_target) {
- *err = "expected ':' in depfile";
- return false;
- }
- return true;
-}
diff --git a/src/depfile_parser.h b/src/depfile_parser.h
deleted file mode 100644
index 88210dc..0000000
--- a/src/depfile_parser.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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.
-
-#ifndef NINJA_DEPFILE_PARSER_H_
-#define NINJA_DEPFILE_PARSER_H_
-
-#include <string>
-#include <vector>
-
-#include "string_piece.h"
-
-struct DepfileParserOptions {
- DepfileParserOptions() {}
-
- bool operator==(const DepfileParserOptions&) const {
- // struct is empty so always return true for now.
- return true;
- }
-};
-
-/// Parser for the dependency information emitted by gcc's -M flags.
-struct DepfileParser {
- explicit DepfileParser(DepfileParserOptions options =
- DepfileParserOptions());
-
- /// 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(std::string* content, std::string* err);
-
- std::vector<StringPiece> outs_;
- std::vector<StringPiece> ins_;
- DepfileParserOptions options_;
-};
-
-#endif // NINJA_DEPFILE_PARSER_H_
diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc
deleted file mode 100644
index 75ba982..0000000
--- a/src/depfile_parser.in.cc
+++ /dev/null
@@ -1,207 +0,0 @@
-// 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.
-
-#include "depfile_parser.h"
-#include "util.h"
-
-#include <algorithm>
-
-using namespace std;
-
-DepfileParser::DepfileParser(DepfileParserOptions options)
- : options_(options)
-{
-}
-
-// A note on backslashes in Makefiles, from reading the docs:
-// Backslash-newline is the line continuation character.
-// Backslash-# escapes a # (otherwise meaningful as a comment start).
-// Backslash-% escapes a % (otherwise meaningful as a special).
-// Finally, quoting the GNU manual, "Backslashes that are not in danger
-// of quoting ‘%’ characters go unmolested."
-// How do you end a line with a backslash? The netbsd Make docs suggest
-// reading the result of a shell command echoing a backslash!
-//
-// Rather than implement all of above, we follow what GCC/Clang produces:
-// Backslashes escape a space or hash sign.
-// When a space is preceded by 2N+1 backslashes, it is represents N backslashes
-// followed by space.
-// When a space is preceded by 2N backslashes, it represents 2N backslashes at
-// the end of a filename.
-// A hash sign is escaped by a single backslash. All other backslashes remain
-// unchanged.
-//
-// If anyone actually has depfiles that rely on the more complicated
-// behavior we can adjust this.
-bool DepfileParser::Parse(string* content, string* err) {
- // in: current parser input point.
- // end: end of input.
- // parsing_targets: whether we are parsing targets or dependencies.
- char* in = &(*content)[0];
- char* end = in + content->size();
- bool have_target = 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
- // as we de-escape backslashes).
- char* out = in;
- // filename: start of the current parsed filename.
- char* filename = out;
- for (;;) {
- // start: beginning of the current parsed span.
- const char* start = in;
- char* yymarker = NULL;
- /*!re2c
- re2c:define:YYCTYPE = "unsigned char";
- re2c:define:YYCURSOR = in;
- re2c:define:YYLIMIT = end;
- re2c:define:YYMARKER = yymarker;
-
- re2c:yyfill:enable = 0;
-
- re2c:indent:top = 2;
- re2c:indent:string = " ";
-
- nul = "\000";
- newline = '\r'?'\n';
-
- '\\\\'* '\\ ' {
- // 2N+1 backslashes plus space -> N backslashes plus space.
- int len = (int)(in - start);
- int n = len / 2 - 1;
- if (out < start)
- memset(out, '\\', n);
- out += n;
- *out++ = ' ';
- continue;
- }
- '\\\\'+ ' ' {
- // 2N backslashes plus space -> 2N backslashes, end of filename.
- int len = (int)(in - start);
- if (out < start)
- memset(out, '\\', len - 1);
- out += len - 1;
- break;
- }
- '\\'+ '#' {
- // De-escape hash sign, but preserve other leading backslashes.
- int len = (int)(in - start);
- if (len > 2 && out < start)
- memset(out, '\\', len - 2);
- out += len - 2;
- *out++ = '#';
- continue;
- }
- '\\'+ ':' [\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++ = '$';
- continue;
- }
- '\\'+ [^\000\r\n] | [a-zA-Z0-9+,/_:.~()}{%=@\x5B\x5D!\x80-\xFF-]+ {
- // Got a span of plain text.
- int len = (int)(in - start);
- // Need to shift it over if we're overwriting backslashes.
- if (out < start)
- memmove(out, start, len);
- out += len;
- continue;
- }
- nul {
- break;
- }
- '\\' newline {
- // A line continuation ends the current file name.
- break;
- }
- newline {
- // A newline ends the current file name and the current rule.
- have_newline = true;
- break;
- }
- [^] {
- // For any other character (e.g. whitespace), swallow it here,
- // allowing the outer logic to loop around again.
- break;
- }
- */
- }
-
- int len = (int)(out - filename);
- const bool is_dependency = !parsing_targets;
- if (len > 0 && filename[len - 1] == ':') {
- len--; // Strip off trailing colon, if any.
- parsing_targets = false;
- have_target = true;
- }
-
- if (len > 0) {
- 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;
- }
- // 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);
- }
- } 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;
- poisoned_input = false;
- }
- }
- if (!have_target) {
- *err = "expected ':' in depfile";
- return false;
- }
- return true;
-}
diff --git a/src/depfile_parser_perftest.cc b/src/depfile_parser_perftest.cc
deleted file mode 100644
index 52555e6..0000000
--- a/src/depfile_parser_perftest.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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.
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "depfile_parser.h"
-#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]);
- return 1;
- }
-
- vector<float> times;
- for (int i = 1; i < argc; ++i) {
- const char* filename = argv[i];
-
- for (int limit = 1 << 10; limit < (1<<20); limit *= 2) {
- int64_t start = GetTimeMillis();
- for (int rep = 0; rep < limit; ++rep) {
- string buf;
- string err;
- if (ReadFile(filename, &buf, &err) < 0) {
- printf("%s: %s\n", filename, err.c_str());
- return 1;
- }
-
- DepfileParser parser;
- if (!parser.Parse(&buf, &err)) {
- printf("%s: %s\n", filename, err.c_str());
- return 1;
- }
- }
- int64_t end = GetTimeMillis();
-
- if (end - start > 100) {
- int delta = (int)(end - start);
- float time = delta*1000 / (float)limit;
- printf("%s: %.1fus\n", filename, time);
- times.push_back(time);
- break;
- }
- }
- }
-
- if (!times.empty()) {
- float min = times[0];
- float max = times[0];
- float total = 0;
- for (size_t i = 0; i < times.size(); ++i) {
- total += times[i];
- if (times[i] < min)
- min = times[i];
- else if (times[i] > max)
- max = times[i];
- }
-
- printf("min %.1fus max %.1fus avg %.1fus\n",
- min, max, total / times.size());
- }
-
- return 0;
-}
diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc
deleted file mode 100644
index 8886258..0000000
--- a/src/depfile_parser_test.cc
+++ /dev/null
@@ -1,380 +0,0 @@
-// 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.
-
-#include "depfile_parser.h"
-
-#include "test.h"
-
-using namespace std;
-
-struct DepfileParserTest : public testing::Test {
- bool Parse(const char* input, string* err);
-
- DepfileParser parser_;
- string input_;
-};
-
-bool DepfileParserTest::Parse(const char* input, string* err) {
- input_ = input;
- return parser_.Parse(&input_, err);
-}
-
-TEST_F(DepfileParserTest, Basic) {
- string err;
- EXPECT_TRUE(Parse(
-"build/ninja.o: ninja.cc ninja.h eval_env.h manifest_parser.h\n",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("build/ninja.o", parser_.outs_[0].AsString());
- EXPECT_EQ(4u, parser_.ins_.size());
-}
-
-TEST_F(DepfileParserTest, EarlyNewlineAndWhitespace) {
- string err;
- EXPECT_TRUE(Parse(
-" \\\n"
-" out: in\n",
- &err));
- ASSERT_EQ("", err);
-}
-
-TEST_F(DepfileParserTest, Continuation) {
- string err;
- EXPECT_TRUE(Parse(
-"foo.o: \\\n"
-" bar.h baz.h\n",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("foo.o", parser_.outs_[0].AsString());
- EXPECT_EQ(2u, parser_.ins_.size());
-}
-
-TEST_F(DepfileParserTest, CarriageReturnContinuation) {
- string err;
- EXPECT_TRUE(Parse(
-"foo.o: \\\r\n"
-" bar.h baz.h\r\n",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("foo.o", parser_.outs_[0].AsString());
- EXPECT_EQ(2u, parser_.ins_.size());
-}
-
-TEST_F(DepfileParserTest, BackSlashes) {
- string err;
- EXPECT_TRUE(Parse(
-"Project\\Dir\\Build\\Release8\\Foo\\Foo.res : \\\n"
-" Dir\\Library\\Foo.rc \\\n"
-" Dir\\Library\\Version\\Bar.h \\\n"
-" Dir\\Library\\Foo.ico \\\n"
-" 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_.outs_[0].AsString());
- EXPECT_EQ(4u, parser_.ins_.size());
-}
-
-TEST_F(DepfileParserTest, Spaces) {
- string err;
- EXPECT_TRUE(Parse(
-"a\\ bc\\ def: a\\ b c d",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("a bc def",
- parser_.outs_[0].AsString());
- ASSERT_EQ(3u, parser_.ins_.size());
- EXPECT_EQ("a b",
- parser_.ins_[0].AsString());
- EXPECT_EQ("c",
- parser_.ins_[1].AsString());
- EXPECT_EQ("d",
- parser_.ins_[2].AsString());
-}
-
-TEST_F(DepfileParserTest, MultipleBackslashes) {
- // Successive 2N+1 backslashes followed by space (' ') are replaced by N >= 0
- // backslashes and the space. A single backslash before hash sign is removed.
- // Other backslashes remain untouched (including 2N backslashes followed by
- // space).
- string err;
- EXPECT_TRUE(Parse(
-"a\\ b\\#c.h: \\\\\\\\\\ \\\\\\\\ \\\\share\\info\\\\#1",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("a b#c.h",
- parser_.outs_[0].AsString());
- ASSERT_EQ(3u, parser_.ins_.size());
- EXPECT_EQ("\\\\ ",
- parser_.ins_[0].AsString());
- EXPECT_EQ("\\\\\\\\",
- parser_.ins_[1].AsString());
- EXPECT_EQ("\\\\share\\info\\#1",
- parser_.ins_[2].AsString());
-}
-
-TEST_F(DepfileParserTest, Escapes) {
- // Put backslashes before a variety of characters, see which ones make
- // it through.
- string err;
- EXPECT_TRUE(Parse(
-"\\!\\@\\#$$\\%\\^\\&\\[\\]\\\\:",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("\\!\\@#$\\%\\^\\&\\[\\]\\\\",
- 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/
- string err;
- EXPECT_TRUE(Parse(
-"C:/Program\\ Files\\ (x86)/Microsoft\\ crtdefs.h: \\\n"
-" en@quot.header~ t+t-x!=1 \\\n"
-" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif\\\n"
-" Fu\303\244ball\\\n"
-" a[1]b@2%c",
- &err));
- ASSERT_EQ("", err);
- ASSERT_EQ(1u, parser_.outs_.size());
- EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h",
- parser_.outs_[0].AsString());
- ASSERT_EQ(5u, parser_.ins_.size());
- EXPECT_EQ("en@quot.header~",
- parser_.ins_[0].AsString());
- EXPECT_EQ("t+t-x!=1",
- parser_.ins_[1].AsString());
- EXPECT_EQ("openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif",
- parser_.ins_[2].AsString());
- EXPECT_EQ("Fu\303\244ball",
- parser_.ins_[3].AsString());
- EXPECT_EQ("a[1]b@2%c",
- parser_.ins_[4].AsString());
-}
-
-TEST_F(DepfileParserTest, UnifyMultipleOutputs) {
- // check that multiple duplicate targets are properly unified
- string err;
- EXPECT_TRUE(Parse("foo foo: x y z", &err));
- ASSERT_EQ(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, MultipleDifferentOutputs) {
- // check that multiple different outputs are accepted by the parser
- string 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) {
- string err;
- EXPECT_TRUE(Parse("foo: x\n"
- "foo: \n"
- "foo:\n", &err));
- 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());
-}
-
-TEST_F(DepfileParserTest, UnifyMultipleRulesLF) {
- string err;
- EXPECT_TRUE(Parse("foo: x\n"
- "foo: y\n"
- "foo \\\n"
- "foo: z\n", &err));
- 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, UnifyMultipleRulesCRLF) {
- string err;
- EXPECT_TRUE(Parse("foo: x\r\n"
- "foo: y\r\n"
- "foo \\\r\n"
- "foo: z\r\n", &err));
- 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, UnifyMixedRulesLF) {
- string err;
- EXPECT_TRUE(Parse("foo: x\\\n"
- " y\n"
- "foo \\\n"
- "foo: z\n", &err));
- 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, UnifyMixedRulesCRLF) {
- string err;
- EXPECT_TRUE(Parse("foo: x\\\r\n"
- " y\r\n"
- "foo \\\r\n"
- "foo: z\r\n", &err));
- 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, IndentedRulesLF) {
- string err;
- EXPECT_TRUE(Parse(" foo: x\n"
- " foo: y\n"
- " foo: z\n", &err));
- 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, IndentedRulesCRLF) {
- string err;
- EXPECT_TRUE(Parse(" foo: x\r\n"
- " foo: y\r\n"
- " foo: z\r\n", &err));
- 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, TolerateMP) {
- string err;
- EXPECT_TRUE(Parse("foo: x y z\n"
- "x:\n"
- "y:\n"
- "z:\n", &err));
- 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, MultipleRulesTolerateMP) {
- string err;
- EXPECT_TRUE(Parse("foo: x\n"
- "x:\n"
- "foo: y\n"
- "y:\n"
- "foo: z\n"
- "z:\n", &err));
- 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, MultipleRulesDifferentOutputs) {
- // check that multiple different outputs are accepted by the parser
- // when spread across multiple rules
- string 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
deleted file mode 100644
index b728379..0000000
--- a/src/deps_log.cc
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2012 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 "deps_log.h"
-
-#include <assert.h>
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#ifndef _WIN32
-#include <unistd.h>
-#elif defined(_MSC_VER) && (_MSC_VER < 1900)
-typedef __int32 int32_t;
-typedef unsigned __int32 uint32_t;
-#endif
-
-#include "graph.h"
-#include "metrics.h"
-#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";
-const int kCurrentVersion = 4;
-
-// Record size is currently limited to less than the full 32 bit, due to
-// internal buffers having to have this size.
-const unsigned kMaxRecordSize = (1 << 19) - 1;
-
-DepsLog::~DepsLog() {
- Close();
- for (const Deps* deps : deps_)
- delete deps;
-}
-
-bool DepsLog::OpenForWrite(const string& path, string* err) {
- if (needs_recompaction_) {
- if (!Recompact(path, err))
- 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;
-}
-
-bool DepsLog::RecordDeps(Node* node, TimeStamp mtime,
- const vector<Node*>& nodes) {
- return RecordDeps(node, mtime, nodes.size(),
- nodes.empty() ? NULL : (Node**)&nodes.front());
-}
-
-bool DepsLog::RecordDeps(Node* node, TimeStamp mtime,
- int node_count, Node** nodes) {
- // Track whether there's any new data to be recorded.
- bool made_change = false;
-
- // Assign ids to all nodes that are missing one.
- if (node->id() < 0) {
- if (!RecordId(node))
- return false;
- made_change = true;
- }
- for (int i = 0; i < node_count; ++i) {
- if (nodes[i]->id() < 0) {
- if (!RecordId(nodes[i]))
- return false;
- made_change = true;
- }
- }
-
- // See if the new data is different than the existing data, if any.
- if (!made_change) {
- Deps* deps = GetDeps(node);
- if (!deps ||
- deps->mtime != mtime ||
- deps->node_count != node_count) {
- made_change = true;
- } else {
- for (int i = 0; i < node_count; ++i) {
- if (deps->nodes[i] != nodes[i]) {
- made_change = true;
- break;
- }
- }
- }
- }
-
- // Don't write anything if there's no new info.
- if (!made_change)
- return true;
-
- // Update on-disk representation.
- unsigned size = 4 * (1 + 2 + node_count);
- if (size > kMaxRecordSize) {
- errno = ERANGE;
- return false;
- }
-
- if (!OpenForWriteIfNeeded()) {
- return false;
- }
- size |= 0x80000000; // Deps record: set high bit.
- if (fwrite(&size, 4, 1, file_) < 1)
- return false;
- int id = node->id();
- if (fwrite(&id, 4, 1, file_) < 1)
- return false;
- uint32_t mtime_part = static_cast<uint32_t>(mtime & 0xffffffff);
- if (fwrite(&mtime_part, 4, 1, file_) < 1)
- return false;
- mtime_part = static_cast<uint32_t>((mtime >> 32) & 0xffffffff);
- if (fwrite(&mtime_part, 4, 1, file_) < 1)
- return false;
- for (int i = 0; i < node_count; ++i) {
- id = nodes[i]->id();
- if (fwrite(&id, 4, 1, file_) < 1)
- return false;
- }
- if (fflush(file_) != 0)
- return false;
-
- // Update in-memory representation.
- Deps* deps = new Deps(mtime, node_count);
- for (int i = 0; i < node_count; ++i)
- deps->nodes[i] = nodes[i];
- UpdateDeps(node->id(), deps);
-
- return true;
-}
-
-void DepsLog::Close() {
- OpenForWriteIfNeeded(); // create the file even if nothing has been recorded
- if (file_)
- fclose(file_);
- file_ = NULL;
-}
-
-LoadStatus DepsLog::Load(const string& path, State* state, string* err) {
- METRIC_RECORD_LOAD(".ninja_deps load");
- char buf[kMaxRecordSize + 1];
- FILE* f = fopen(path.c_str(), "rb");
- if (!f) {
- if (errno == ENOENT)
- return LOAD_NOT_FOUND;
- *err = strerror(errno);
- return LOAD_ERROR;
- }
-
- bool valid_header = true;
- int version = 0;
- if (!fgets(buf, sizeof(buf), f) || fread(&version, 4, 1, f) < 1)
- valid_header = false;
- // Note: For version differences, this should migrate to the new format.
- // But the v1 format could sometimes (rarely) end up with invalid data, so
- // don't migrate v1 to v3 to force a rebuild. (v2 only existed for a few days,
- // and there was no release with it, so pretend that it never happened.)
- if (!valid_header || strcmp(buf, kFileSignature) != 0 ||
- version != kCurrentVersion) {
- if (version == 1)
- *err = "deps log version change; rebuilding";
- else
- *err = "bad deps log signature or version; starting over";
- fclose(f);
- unlink(path.c_str());
- // Don't report this as a failure. An empty deps log will cause
- // us to rebuild the outputs anyway.
- return LOAD_SUCCESS;
- }
-
- long offset;
- bool read_failed = false;
- int unique_dep_record_count = 0;
- int total_dep_record_count = 0;
- for (;;) {
- offset = ftell(f);
-
- unsigned size;
- if (fread(&size, 4, 1, f) < 1) {
- if (!feof(f))
- read_failed = true;
- break;
- }
- bool is_deps = (size >> 31) != 0;
- size = size & 0x7FFFFFFF;
-
- if (size > kMaxRecordSize || fread(buf, size, 1, f) < 1) {
- read_failed = true;
- break;
- }
-
- if (is_deps) {
- assert(size % 4 == 0);
- int* deps_data = reinterpret_cast<int*>(buf);
- int out_id = deps_data[0];
- TimeStamp mtime;
- mtime = (TimeStamp)(((uint64_t)(unsigned int)deps_data[2] << 32) |
- (uint64_t)(unsigned int)deps_data[1]);
- deps_data += 3;
- int deps_count = (size / 4) - 3;
-
- Deps* deps = new Deps(mtime, deps_count);
- for (int i = 0; i < deps_count; ++i) {
- assert(deps_data[i] < (int)nodes_.size());
- assert(nodes_[deps_data[i]]);
- deps->nodes[i] = nodes_[deps_data[i]];
- }
-
- total_dep_record_count++;
- if (!UpdateDeps(out_id, deps))
- ++unique_dep_record_count;
- } else {
- int path_size = size - 4;
- assert(path_size > 0); // CanonicalizePath() rejects empty paths.
- // There can be up to 3 bytes of padding.
- if (buf[path_size - 1] == '\0') --path_size;
- if (buf[path_size - 1] == '\0') --path_size;
- if (buf[path_size - 1] == '\0') --path_size;
- StringPiece subpath(buf, path_size);
- // It is not necessary to pass in a correct slash_bits here. It will
- // either be a Node that's in the manifest (in which case it will already
- // have a correct slash_bits that GetNode will look up), or it is an
- // implicit dependency from a .d which does not affect the build command
- // (and so need not have its slashes maintained).
- Node* node = state->GetNode(subpath, 0);
-
- // Check that the expected index matches the actual index. This can only
- // happen if two ninja processes write to the same deps log concurrently.
- // (This uses unary complement to make the checksum look less like a
- // dependency record entry.)
- unsigned checksum = *reinterpret_cast<unsigned*>(buf + size - 4);
- int expected_id = ~checksum;
- int id = nodes_.size();
- if (id != expected_id) {
- read_failed = true;
- break;
- }
-
- assert(node->id() < 0);
- node->set_id(id);
- nodes_.push_back(node);
- }
- }
-
- if (read_failed) {
- // An error occurred while loading; try to recover by truncating the
- // file to the last fully-read record.
- if (ferror(f)) {
- *err = strerror(ferror(f));
- } else {
- *err = "premature end of file";
- }
- fclose(f);
-
- if (!Truncate(path, offset, err))
- return LOAD_ERROR;
-
- // The truncate succeeded; we'll just report the load error as a
- // warning because the build can proceed.
- *err += "; recovering";
- return LOAD_SUCCESS;
- }
-
- fclose(f);
-
- // Rebuild the log if there are too many dead records.
- int kMinCompactionEntryCount = 1000;
- int kCompactionRatio = 3;
- if (total_dep_record_count > kMinCompactionEntryCount &&
- total_dep_record_count > unique_dep_record_count * kCompactionRatio) {
- needs_recompaction_ = true;
- }
-
- return LOAD_SUCCESS;
-}
-
-DepsLog::Deps* DepsLog::GetDeps(Node* node) {
- // Abort if the node has no id (never referenced in the deps) or if
- // there's no deps recorded for the node.
- if (node->id() < 0 || node->id() >= (int)deps_.size())
- return NULL;
- return deps_[node->id()];
-}
-
-Node* DepsLog::GetFirstReverseDepsNode(Node* node) {
- for (size_t id = 0; id < deps_.size(); ++id) {
- Deps* deps = deps_[id];
- if (!deps)
- continue;
- for (int i = 0; i < deps->node_count; ++i) {
- if (deps->nodes[i] == node)
- return nodes_[id];
- }
- }
- return NULL;
-}
-
-bool DepsLog::Recompact(const string& path, string* err) {
- METRIC_RECORD(".ninja_deps recompact");
-
- Close();
- string temp_path = path + ".recompact";
-
- // OpenForWrite() opens for append. Make sure it's not appending to a
- // left-over file from a previous recompaction attempt that crashed somehow.
- unlink(temp_path.c_str());
-
- DepsLog new_log;
- if (!new_log.OpenForWrite(temp_path, err))
- return false;
-
- // Clear all known ids so that new ones can be reassigned. The new indices
- // will refer to the ordering in new_log, not in the current log.
- for (vector<Node*>::iterator i = nodes_.begin(); i != nodes_.end(); ++i)
- (*i)->set_id(-1);
-
- // Write out all deps again.
- for (int old_id = 0; old_id < (int)deps_.size(); ++old_id) {
- Deps* deps = deps_[old_id];
- if (!deps) continue; // If nodes_[old_id] is a leaf, it has no deps.
-
- if (!IsDepsEntryLiveFor(nodes_[old_id]))
- continue;
-
- if (!new_log.RecordDeps(nodes_[old_id], deps->mtime,
- deps->node_count, deps->nodes)) {
- new_log.Close();
- return false;
- }
- }
-
- new_log.Close();
-
- // All nodes now have ids that refer to new_log, so steal its data.
- deps_.swap(new_log.deps_);
- nodes_.swap(new_log.nodes_);
-
- if (unlink(path.c_str()) < 0) {
- *err = strerror(errno);
- return false;
- }
-
- if (rename(temp_path.c_str(), path.c_str()) < 0) {
- *err = strerror(errno);
- return false;
- }
-
- return true;
-}
-
-bool DepsLog::IsDepsEntryLiveFor(const Node* node) {
- // Skip entries that don't have in-edges or whose edges don't have a
- // "deps" attribute. They were in the deps log from previous builds, but
- // the the files they were for were removed from the build and their deps
- // entries are no longer needed.
- // (Without the check for "deps", a chain of two or more nodes that each
- // had deps wouldn't be collected in a single recompaction.)
- return node->in_edge() && !node->in_edge()->GetBinding("deps").empty();
-}
-
-bool DepsLog::UpdateDeps(int out_id, Deps* deps) {
- if (out_id >= (int)deps_.size()) {
- deps_.resize(out_id + 1);
- }
-
- bool delete_old = deps_[out_id] != NULL;
- if (delete_old)
- delete deps_[out_id];
- deps_[out_id] = deps;
- return delete_old;
-}
-
-bool DepsLog::RecordId(Node* node) {
- int path_size = node->path().size();
- int padding = (4 - path_size % 4) % 4; // Pad path to 4 byte boundary.
-
- unsigned size = path_size + padding + 4;
- if (size > kMaxRecordSize) {
- 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().empty());
- return false;
- }
- if (padding && fwrite("\0\0", padding, 1, file_) < 1)
- return false;
- int id = nodes_.size();
- unsigned checksum = ~(unsigned)id;
- if (fwrite(&checksum, 4, 1, file_) < 1)
- return false;
- if (fflush(file_) != 0)
- return false;
-
- node->set_id(id);
- nodes_.push_back(node);
-
- 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
deleted file mode 100644
index 166310f..0000000
--- a/src/deps_log.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2012 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_DEPS_LOG_H_
-#define NINJA_DEPS_LOG_H_
-
-#include <string>
-#include <vector>
-
-#include <stdio.h>
-
-#include "load_status.h"
-#include "timestamp.h"
-
-struct Node;
-struct State;
-
-/// As build commands run they can output extra dependency information
-/// (e.g. header dependencies for C source) dynamically. DepsLog collects
-/// that information at build time and uses it for subsequent builds.
-///
-/// The on-disk format is based on two primary design constraints:
-/// - it must be written to as a stream (during the build, which may be
-/// interrupted);
-/// - it can be read all at once on startup. (Alternative designs, where
-/// it contains indexing information, were considered and discarded as
-/// too complicated to implement; if the file is small than reading it
-/// fully on startup is acceptable.)
-/// Here are some stats from the Windows Chrome dependency files, to
-/// help guide the design space. The total text in the files sums to
-/// 90mb so some compression is warranted to keep load-time fast.
-/// There's about 10k files worth of dependencies that reference about
-/// 40k total paths totalling 2mb of unique strings.
-///
-/// Based on these stats, here's the current design.
-/// The file is structured as version header followed by a sequence of records.
-/// Each record is either a path string or a dependency list.
-/// Numbering the path strings in file order gives them dense integer ids.
-/// A dependency list maps an output id to a list of input ids.
-///
-/// Concretely, a record is:
-/// four bytes record length, high bit indicates record type
-/// (but max record sizes are capped at 512kB)
-/// path records contain the string name of the path, followed by up to 3
-/// padding bytes to align on 4 byte boundaries, followed by the
-/// one's complement of the expected index of the record (to detect
-/// concurrent writes of multiple ninja processes to the log).
-/// dependency records are an array of 4-byte integers
-/// [output path id,
-/// output path mtime (lower 4 bytes), output path mtime (upper 4 bytes),
-/// input path id, input path id...]
-/// (The mtime is compared against the on-disk output path mtime
-/// to verify the stored data is up-to-date.)
-/// If two records reference the same output the latter one in the file
-/// wins, allowing updates to just be appended to the file. A separate
-/// repacking step can run occasionally to remove dead records.
-struct DepsLog {
- DepsLog() : needs_recompaction_(false), file_(NULL) {}
- ~DepsLog();
-
- // Writing (build-time) interface.
- 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();
-
- // Reading (startup-time) interface.
- struct Deps {
- Deps(int64_t mtime, int node_count)
- : mtime(mtime), node_count(node_count), nodes(new Node*[node_count]) {}
- ~Deps() { delete [] nodes; }
- TimeStamp mtime;
- int node_count;
- Node** nodes;
- };
- LoadStatus Load(const std::string& path, State* state, std::string* err);
- Deps* GetDeps(Node* node);
- Node* GetFirstReverseDepsNode(Node* node);
-
- /// Rewrite the known log entries, throwing away old data.
- bool Recompact(const std::string& path, std::string* err);
-
- /// Returns if the deps entry for a node is still reachable from the manifest.
- ///
- /// The deps log can contain deps entries for files that were built in the
- /// past but are no longer part of the manifest. This function returns if
- /// this is the case for a given node. This function is slow, don't call
- /// it from code that runs on every build.
- static bool IsDepsEntryLiveFor(const Node* node);
-
- /// Used for tests.
- const std::vector<Node*>& nodes() const { return nodes_; }
- const std::vector<Deps*>& deps() const { return deps_; }
-
- /// Return the number of output paths in the deps log.
- size_t size() const { return nodes_.size(); }
-
- private:
- // Updates the in-memory representation. Takes ownership of |deps|.
- // Returns true if a prior deps record was deleted.
- bool UpdateDeps(int out_id, Deps* deps);
- // 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.
- std::vector<Node*> nodes_;
- /// Maps id -> deps of that id.
- std::vector<Deps*> deps_;
-
- friend struct DepsLogTest;
-};
-
-#endif // NINJA_DEPS_LOG_H_
diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc
deleted file mode 100644
index 274e700..0000000
--- a/src/deps_log_test.cc
+++ /dev/null
@@ -1,545 +0,0 @@
-// Copyright 2012 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 "deps_log.h"
-
-#include <sys/stat.h>
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
-#include "graph.h"
-#include "util.h"
-#include "test.h"
-
-using namespace std;
-
-namespace {
-
-const char kTestFilename[] = "DepsLogTest-tempfile";
-
-struct DepsLogTest : public testing::Test {
- virtual void SetUp() {
- // In case a crashing test left a stale file behind.
- unlink(kTestFilename);
- }
- virtual void TearDown() {
- unlink(kTestFilename);
- }
-};
-
-TEST_F(DepsLogTest, WriteRead) {
- State state1;
- DepsLog log1;
- string err;
- EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- {
- vector<Node*> deps;
- deps.push_back(state1.GetNode("foo.h", 0));
- deps.push_back(state1.GetNode("bar.h", 0));
- log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps);
-
- deps.clear();
- deps.push_back(state1.GetNode("foo.h", 0));
- deps.push_back(state1.GetNode("bar2.h", 0));
- log1.RecordDeps(state1.GetNode("out2.o", 0), 2, deps);
-
- DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0));
- ASSERT_TRUE(log_deps);
- ASSERT_EQ(1, log_deps->mtime);
- ASSERT_EQ(2, log_deps->node_count);
- ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
- ASSERT_EQ("bar.h", log_deps->nodes[1]->path());
- }
-
- log1.Close();
-
- State state2;
- DepsLog log2;
- EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err));
- ASSERT_EQ("", err);
-
- ASSERT_EQ(log1.nodes().size(), log2.nodes().size());
- for (int i = 0; i < (int)log1.nodes().size(); ++i) {
- Node* node1 = log1.nodes()[i];
- Node* node2 = log2.nodes()[i];
- ASSERT_EQ(i, node1->id());
- ASSERT_EQ(node1->id(), node2->id());
- }
-
- // Spot-check the entries in log2.
- DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o", 0));
- ASSERT_TRUE(log_deps);
- ASSERT_EQ(2, log_deps->mtime);
- ASSERT_EQ(2, log_deps->node_count);
- ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
- ASSERT_EQ("bar2.h", log_deps->nodes[1]->path());
-}
-
-TEST_F(DepsLogTest, LotsOfDeps) {
- const int kNumDeps = 100000; // More than 64k.
-
- State state1;
- DepsLog log1;
- string err;
- EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- {
- vector<Node*> deps;
- for (int i = 0; i < kNumDeps; ++i) {
- char buf[32];
- sprintf(buf, "file%d.h", i);
- deps.push_back(state1.GetNode(buf, 0));
- }
- log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps);
-
- DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0));
- ASSERT_EQ(kNumDeps, log_deps->node_count);
- }
-
- log1.Close();
-
- State state2;
- DepsLog log2;
- EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err));
- ASSERT_EQ("", err);
-
- DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o", 0));
- ASSERT_EQ(kNumDeps, log_deps->node_count);
-}
-
-// Verify that adding the same deps twice doesn't grow the file.
-TEST_F(DepsLogTest, DoubleEntry) {
- // Write some deps to the file and grab its size.
- int file_size;
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
- log.Close();
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- file_size = (int)st.st_size;
- ASSERT_GT(file_size, 0);
- }
-
- // Now reload the file, and read the same deps.
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
-
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
- log.Close();
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- int file_size_2 = (int)st.st_size;
- ASSERT_EQ(file_size, file_size_2);
- }
-}
-
-// Verify that adding the new deps works and can be compacted away.
-TEST_F(DepsLogTest, Recompact) {
- const char kManifest[] =
-"rule cc\n"
-" command = cc\n"
-" deps = gcc\n"
-"build out.o: cc\n"
-"build other_out.o: cc\n";
-
- // Write some deps to the file and grab its size.
- int file_size;
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
- DepsLog log;
- string err;
- ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
-
- deps.clear();
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("baz.h", 0));
- log.RecordDeps(state.GetNode("other_out.o", 0), 1, deps);
-
- log.Close();
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- file_size = (int)st.st_size;
- ASSERT_GT(file_size, 0);
- }
-
- // Now reload the file, and add slightly different deps.
- int file_size_2;
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
- DepsLog log;
- string err;
- ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
-
- ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
- log.Close();
-
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- file_size_2 = (int)st.st_size;
- // The file should grow to record the new deps.
- ASSERT_GT(file_size_2, file_size);
- }
-
- // Now reload the file, verify the new deps have replaced the old, then
- // recompact.
- int file_size_3;
- {
- State state;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
- DepsLog log;
- string err;
- ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
-
- Node* out = state.GetNode("out.o", 0);
- DepsLog::Deps* deps = log.GetDeps(out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(1, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
-
- Node* other_out = state.GetNode("other_out.o", 0);
- deps = log.GetDeps(other_out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(2, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
- ASSERT_EQ("baz.h", deps->nodes[1]->path());
-
- ASSERT_TRUE(log.Recompact(kTestFilename, &err));
-
- // The in-memory deps graph should still be valid after recompaction.
- deps = log.GetDeps(out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(1, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
- ASSERT_EQ(out, log.nodes()[out->id()]);
-
- deps = log.GetDeps(other_out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(2, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
- ASSERT_EQ("baz.h", deps->nodes[1]->path());
- ASSERT_EQ(other_out, log.nodes()[other_out->id()]);
-
- // The file should have shrunk a bit for the smaller deps.
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- file_size_3 = (int)st.st_size;
- ASSERT_LT(file_size_3, file_size_2);
- }
-
- // Now reload the file and recompact with an empty manifest. The previous
- // entries should be removed.
- {
- State state;
- // Intentionally not parsing kManifest here.
- DepsLog log;
- string err;
- ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
-
- Node* out = state.GetNode("out.o", 0);
- DepsLog::Deps* deps = log.GetDeps(out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(1, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
-
- Node* other_out = state.GetNode("other_out.o", 0);
- deps = log.GetDeps(other_out);
- ASSERT_TRUE(deps);
- ASSERT_EQ(1, deps->mtime);
- ASSERT_EQ(2, deps->node_count);
- ASSERT_EQ("foo.h", deps->nodes[0]->path());
- ASSERT_EQ("baz.h", deps->nodes[1]->path());
-
- ASSERT_TRUE(log.Recompact(kTestFilename, &err));
-
- // The previous entries should have been removed.
- deps = log.GetDeps(out);
- ASSERT_FALSE(deps);
-
- deps = log.GetDeps(other_out);
- ASSERT_FALSE(deps);
-
- // The .h files pulled in via deps should no longer have ids either.
- ASSERT_EQ(-1, state.LookupNode("foo.h")->id());
- ASSERT_EQ(-1, state.LookupNode("baz.h")->id());
-
- // The file should have shrunk more.
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- int file_size_4 = (int)st.st_size;
- ASSERT_LT(file_size_4, file_size_3);
- }
-}
-
-// Verify that invalid file headers cause a new build.
-TEST_F(DepsLogTest, InvalidHeader) {
- const char *kInvalidHeaders[] = {
- "", // Empty file.
- "# ninjad", // Truncated first line.
- "# ninjadeps\n", // No version int.
- "# ninjadeps\n\001\002", // Truncated version int.
- "# ninjadeps\n\001\002\003\004" // Invalid version int.
- };
- for (size_t i = 0; i < sizeof(kInvalidHeaders) / sizeof(kInvalidHeaders[0]);
- ++i) {
- FILE* deps_log = fopen(kTestFilename, "wb");
- ASSERT_TRUE(deps_log != NULL);
- ASSERT_EQ(
- strlen(kInvalidHeaders[i]),
- fwrite(kInvalidHeaders[i], 1, strlen(kInvalidHeaders[i]), deps_log));
- ASSERT_EQ(0 ,fclose(deps_log));
-
- string err;
- DepsLog log;
- State state;
- ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
- EXPECT_EQ("bad deps log signature or version; starting over", err);
- }
-}
-
-// Simulate what happens when loading a truncated log file.
-TEST_F(DepsLogTest, Truncated) {
- // Create a file with some entries.
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
-
- deps.clear();
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar2.h", 0));
- log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
-
- log.Close();
- }
-
- // Get the file size.
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
-
- // Try reloading at truncated sizes.
- // Track how many nodes/deps were found; they should decrease with
- // smaller sizes.
- int node_count = 5;
- int deps_count = 2;
- for (int size = (int)st.st_size; size > 0; --size) {
- string err;
- ASSERT_TRUE(Truncate(kTestFilename, size, &err));
-
- State state;
- DepsLog log;
- EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
- if (!err.empty()) {
- // At some point the log will be so short as to be unparsable.
- break;
- }
-
- ASSERT_GE(node_count, (int)log.nodes().size());
- node_count = log.nodes().size();
-
- // Count how many non-NULL deps entries there are.
- int new_deps_count = 0;
- for (vector<DepsLog::Deps*>::const_iterator i = log.deps().begin();
- i != log.deps().end(); ++i) {
- if (*i)
- ++new_deps_count;
- }
- ASSERT_GE(deps_count, new_deps_count);
- deps_count = new_deps_count;
- }
-}
-
-// Run the truncation-recovery logic.
-TEST_F(DepsLogTest, TruncatedRecovery) {
- // Create a file with some entries.
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
-
- deps.clear();
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar2.h", 0));
- log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
-
- log.Close();
- }
-
- // Shorten the file, corrupting the last record.
- {
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- ASSERT_EQ(0, stat64(kTestFilename, &st));
-#else
- struct stat st;
- ASSERT_EQ(0, stat(kTestFilename, &st));
-#endif
- string err;
- ASSERT_TRUE(Truncate(kTestFilename, st.st_size - 2, &err));
- }
-
- // Load the file again, add an entry.
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
- ASSERT_EQ("premature end of file; recovering", err);
- err.clear();
-
- // The truncated entry should've been discarded.
- EXPECT_NULL(log.GetDeps(state.GetNode("out2.o", 0)));
-
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- // Add a new entry.
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar2.h", 0));
- log.RecordDeps(state.GetNode("out2.o", 0), 3, deps);
-
- log.Close();
- }
-
- // Load the file a third time to verify appending after a mangled
- // entry doesn't break things.
- {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
-
- // The truncated entry should exist.
- DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o", 0));
- ASSERT_TRUE(deps);
- }
-}
-
-TEST_F(DepsLogTest, ReverseDepsNodes) {
- State state;
- DepsLog log;
- string err;
- EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
- ASSERT_EQ("", err);
-
- vector<Node*> deps;
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar.h", 0));
- log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
-
- deps.clear();
- deps.push_back(state.GetNode("foo.h", 0));
- deps.push_back(state.GetNode("bar2.h", 0));
- log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
-
- log.Close();
-
- Node* rev_deps = log.GetFirstReverseDepsNode(state.GetNode("foo.h", 0));
- EXPECT_TRUE(rev_deps == state.GetNode("out.o", 0) ||
- rev_deps == state.GetNode("out2.o", 0));
-
- rev_deps = log.GetFirstReverseDepsNode(state.GetNode("bar.h", 0));
- EXPECT_TRUE(rev_deps == state.GetNode("out.o", 0));
-}
-
-} // anonymous namespace
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
deleted file mode 100644
index ff7f515..0000000
--- a/src/disk_interface.cc
+++ /dev/null
@@ -1,212 +0,0 @@
-// 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.
-
-#include "disk_interface.h"
-
-#include <algorithm>
-
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#ifdef _WIN32
-#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) {
-#ifdef _WIN32
- static const char kPathSeparators[] = "\\/";
-#else
- static const char kPathSeparators[] = "/";
-#endif
- static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1;
-
- string::size_type slash_pos = path.find_last_of(kPathSeparators);
- if (slash_pos == string::npos)
- return string(); // Nothing to do.
- while (slash_pos > 0 &&
- std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd)
- --slash_pos;
- return path.substr(0, slash_pos);
-}
-
-int MakeDir(const string& path) {
-#ifdef _WIN32
- return _mkdir(path.c_str());
-#else
- return mkdir(path.c_str(), 0777);
-#endif
-}
-
-} // namespace
-
-// DiskInterface ---------------------------------------------------------------
-
-bool DiskInterface::MakeDirs(const string& path) {
- string dir = DirName(path);
- if (dir.empty())
- return true; // Reached root; assume it's there.
- string err;
- TimeStamp mtime = Stat(dir, &err);
- if (mtime < 0) {
- Error("%s", err.c_str());
- return false;
- }
- if (mtime > 0)
- return true; // Exists already; we're done.
-
- // Directory doesn't exist. Try creating its parent first.
- bool success = MakeDirs(dir);
- if (!success)
- return false;
- return MakeDir(dir);
-}
-
-// RealDiskInterface -----------------------------------------------------------
-
-TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
- METRIC_RECORD("node stat");
- return stat_cache_.Stat(path, err);
-}
-
-bool RealDiskInterface::WriteFile(const string& path, const string& contents) {
- FILE* fp = fopen(path.c_str(), "w");
- if (fp == NULL) {
- Error("WriteFile(%s): Unable to create file. %s",
- path.c_str(), strerror(errno));
- return false;
- }
-
- stat_cache_.Invalidate(path);
- if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
- Error("WriteFile(%s): Unable to write to the file. %s",
- path.c_str(), strerror(errno));
- fclose(fp);
- return false;
- }
-
- if (fclose(fp) == EOF) {
- Error("WriteFile(%s): Unable to close the file. %s",
- path.c_str(), strerror(errno));
- return false;
- }
-
- return true;
-}
-
-bool RealDiskInterface::MakeDir(const string& path) {
- int ret = ::MakeDir(path);
- if (ret < 0 && errno == EEXIST)
- return true;
-
- int saved_errno = errno;
- // Invalidate just in case the call modified something on the disk.
- stat_cache_.Invalidate(path);
- if (ret < 0) {
- Error("mkdir(%s): %s", path.c_str(), strerror(saved_errno));
- return false;
- }
- return true;
-}
-
-FileReader::Status RealDiskInterface::ReadFile(const string& path,
- string* contents,
- string* err) {
- switch (::ReadFile(path, contents, err)) {
- case 0: return Okay;
- case -ENOENT: return NotFound;
- default: return OtherError;
- }
-}
-
-int RealDiskInterface::RemoveFile(const string& path) {
- stat_cache_.Invalidate(path);
-#ifdef _WIN32
- DWORD attributes = GetFileAttributesA(path.c_str());
- if (attributes == INVALID_FILE_ATTRIBUTES) {
- DWORD win_err = GetLastError();
- if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
- return 1;
- }
- } else if (attributes & FILE_ATTRIBUTE_READONLY) {
- // On non-Windows systems, remove() will happily delete read-only files.
- // On Windows Ninja should behave the same:
- // https://github.com/ninja-build/ninja/issues/1886
- // Skip error checking. If this fails, accept whatever happens below.
- SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
- }
- if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
- // remove() deletes both files and directories. On Windows we have to
- // select the correct function (DeleteFile will yield Permission Denied when
- // used on a directory)
- // This fixes the behavior of ninja -t clean in some cases
- // https://github.com/ninja-build/ninja/issues/828
- if (!RemoveDirectoryA(path.c_str())) {
- DWORD win_err = GetLastError();
- if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
- return 1;
- }
- // Report remove(), not RemoveDirectory(), for cross-platform consistency.
- Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
- return -1;
- }
- } else {
- if (!DeleteFileA(path.c_str())) {
- DWORD win_err = GetLastError();
- if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
- return 1;
- }
- // Report as remove(), not DeleteFile(), for cross-platform consistency.
- Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
- return -1;
- }
- }
-#else
- if (remove(path.c_str()) < 0) {
- switch (errno) {
- case ENOENT:
- return 1;
- default:
- Error("remove(%s): %s", path.c_str(), strerror(errno));
- return -1;
- }
- }
-#endif
- return 0;
-}
-
-void RealDiskInterface::AllowStatCache(bool allow) {
- stat_cache_.Enable(allow);
-}
-
-void RealDiskInterface::Sync() {
- stat_cache_.Sync();
-}
-
-void RealDiskInterface::FlushCache() {
- stat_cache_.Flush();
-}
diff --git a/src/disk_interface.h b/src/disk_interface.h
deleted file mode 100644
index 27960aa..0000000
--- a/src/disk_interface.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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.
-
-#ifndef NINJA_DISK_INTERFACE_H_
-#define NINJA_DISK_INTERFACE_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "stat_cache.h"
-#include "timestamp.h"
-
-/// Interface for reading files from disk. See DiskInterface for details.
-/// This base offers the minimum interface needed just to read files.
-struct FileReader {
- virtual ~FileReader() {}
-
- /// Result of ReadFile.
- enum Status {
- Okay,
- NotFound,
- OtherError
- };
-
- /// Read and store in given string. On success, return Okay.
- /// On error, return another Status and fill |err|.
- virtual Status ReadFile(const std::string& path, std::string* contents,
- std::string* err) = 0;
-};
-
-/// Interface for accessing the disk.
-///
-/// Abstract so it can be mocked out for tests. The real implementation
-/// is RealDiskInterface.
-struct DiskInterface: public FileReader {
- /// stat() a file, returning the mtime, or 0 if missing and -1 on
- /// other errors.
- virtual TimeStamp Stat(const std::string& path, std::string* err) const = 0;
-
- /// Create a directory, returning false on failure.
- 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 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 std::string& path) = 0;
-
- /// Create all the parent directories for path; like mkdir -p
- /// `basename path`.
- bool MakeDirs(const std::string& path);
-
- /// Sync with real filesystem state, which is useful when a timestamp cache
- /// is implemented using a directory-watching system service. Calling this
- /// is only needed when calling Stat() on files that may have been modified
- /// through a different interface (e.g. files modified by command
- /// sub-processes).
- virtual void Sync() {}
-};
-
-/// Implementation of FileReader that tracks opened file paths and their
-/// timestamps. This is useful to quickly check whether one of the Ninja
-/// input manifests has changed, since even for a large Fuchsia build, there
-/// are only around 7000 files, compared to the 1,000,000 file paths in the
-/// corresponding build graph. This class will probably become obsolete when a
-/// proper filesystem-watching timestamp cache is implemented.
-struct PathRecordingFileReader : public FileReader {
- explicit PathRecordingFileReader(DiskInterface* disk_interface)
- : disk_interface_(disk_interface) {}
-
- Status ReadFile(const std::string& path, std::string* contents,
- std::string* err) override {
- entries_.emplace_back(path, disk_interface_->Stat(path, err));
- return disk_interface_->ReadFile(path, contents, err);
- }
-
- /// Drop all timestamps from the cache.
- void Reset() { entries_.clear(); }
-
- /// Return true if any recorded path was modified.
- bool CheckOutOfDate() const {
- for (const auto& entry : entries_) {
- /// Note that Ninja never follows symlinks by design, even for its input
- /// manifest files, so this implementation just calls Stat() to avoid
- /// changing its behavior.
- if (disk_interface_->Stat(entry.path, nullptr) != entry.mtime)
- return true;
- }
- return false;
- }
-
- private:
- struct Entry {
- Entry(const std::string& path, TimeStamp mtime)
- : path(path), mtime(mtime) {}
-
- std::string path;
- TimeStamp mtime;
- };
-
- DiskInterface* disk_interface_;
- std::vector<Entry> entries_;
-};
-
-/// Implementation of DiskInterface that actually hits the disk.
-struct RealDiskInterface : public DiskInterface {
- RealDiskInterface() = default;
- virtual ~RealDiskInterface() = default;
- TimeStamp Stat(const std::string& path, std::string* err) const override;
- bool MakeDir(const std::string& path) override;
- bool WriteFile(const std::string& path, const std::string& contents) override;
- Status ReadFile(const std::string& path, std::string* contents,
- std::string* err) override;
- int RemoveFile(const std::string& path) override;
-
- /// Whether stat information can be cached. Only has an effect on Windows.
- void AllowStatCache(bool allow);
-
- /// If StatCache is allowed, synchronize it with the current filesystem
- /// state. This is used to drain pending events from filesystem monitoring
- /// services like inotify on Linux.
- void Sync() override;
-
- /// Remove all cached stat information, if any.
- void FlushCache();
-
- private:
- StatCache stat_cache_;
-};
-
-#endif // NINJA_DISK_INTERFACE_H_
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
deleted file mode 100644
index d37c412..0000000
--- a/src/disk_interface_test.cc
+++ /dev/null
@@ -1,359 +0,0 @@
-// 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.
-
-#include <assert.h>
-#include <stdio.h>
-#ifdef _WIN32
-#include <io.h>
-#include <windows.h>
-#endif
-
-#include "disk_interface.h"
-#include "graph.h"
-#include "test.h"
-
-using namespace std;
-
-namespace {
-
-struct DiskInterfaceTest : public testing::Test {
- virtual void SetUp() {
- // These tests do real disk accesses, so create a temp dir.
- temp_dir_.CreateAndEnter("Ninja-DiskInterfaceTest");
- }
-
- virtual void TearDown() {
- temp_dir_.Cleanup();
- }
-
- bool Touch(const char* path) {
- FILE *f = fopen(path, "w");
- if (!f)
- return false;
- return fclose(f) == 0;
- }
-
- ScopedTempDir temp_dir_;
- RealDiskInterface disk_;
-};
-
-TEST_F(DiskInterfaceTest, StatMissingFile) {
- string err;
- EXPECT_EQ(0, disk_.Stat("nosuchfile", &err));
- EXPECT_EQ("", err);
-
- // On Windows, the errno for a file in a nonexistent directory
- // is different.
- EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err));
- EXPECT_EQ("", err);
-
- // On POSIX systems, the errno is different if a component of the
- // path prefix is not a directory.
- ASSERT_TRUE(Touch("notadir"));
- EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err));
- EXPECT_EQ("", err);
-}
-
-TEST_F(DiskInterfaceTest, StatMissingFileWithCache) {
- disk_.AllowStatCache(true);
- string err;
-
- if (RunsUnderWine()) // This test fails under Wine.
- return;
-
- // On Windows, the errno for FindFirstFileExA, which is used when the stat
- // cache is enabled, is different when the directory name is not a directory.
- ASSERT_TRUE(Touch("notadir"));
- EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err));
- EXPECT_EQ("", err);
-}
-
-TEST_F(DiskInterfaceTest, StatBadPath) {
- // This test fails under Wine.
- if (RunsUnderWine())
- return;
-
- string err;
-#ifdef _WIN32
- string bad_path("cc:\\foo");
- EXPECT_EQ(-1, disk_.Stat(bad_path, &err));
- EXPECT_NE("", err);
-#else
- string too_long_name(512, 'x');
- EXPECT_EQ(-1, disk_.Stat(too_long_name, &err));
- EXPECT_NE("", err);
-#endif
-}
-
-TEST_F(DiskInterfaceTest, StatExistingFile) {
- string err;
- ASSERT_TRUE(Touch("file"));
- EXPECT_GT(disk_.Stat("file", &err), 1);
- EXPECT_EQ("", err);
-}
-
-TEST_F(DiskInterfaceTest, StatExistingDir) {
- string err;
- ASSERT_TRUE(disk_.MakeDir("subdir"));
- ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir"));
- EXPECT_GT(disk_.Stat("..", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat(".", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("subdir", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1);
- EXPECT_EQ("", err);
-
- EXPECT_EQ(disk_.Stat("subdir", &err),
- disk_.Stat("subdir/.", &err));
- EXPECT_EQ(disk_.Stat("subdir", &err),
- disk_.Stat("subdir/subsubdir/..", &err));
- EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err),
- disk_.Stat("subdir/subsubdir/.", &err));
-}
-
-#ifdef _WIN32
-TEST_F(DiskInterfaceTest, StatCache) {
- string err;
-
- ASSERT_TRUE(Touch("file1"));
- ASSERT_TRUE(Touch("fiLE2"));
- ASSERT_TRUE(disk_.MakeDir("subdir"));
- ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir"));
- ASSERT_TRUE(Touch("subdir\\subfile1"));
- ASSERT_TRUE(Touch("subdir\\SUBFILE2"));
- ASSERT_TRUE(Touch("subdir\\SUBFILE3"));
-
- disk_.AllowStatCache(false);
- TimeStamp parent_stat_uncached = disk_.Stat("..", &err);
- disk_.AllowStatCache(true);
-
- EXPECT_GT(disk_.Stat("FIle1", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("file1", &err), 1);
- EXPECT_EQ("", err);
-
- EXPECT_GT(disk_.Stat("subdir/subfile2", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1);
- EXPECT_EQ("", err);
-
- EXPECT_GT(disk_.Stat("..", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat(".", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("subdir", &err), 1);
- EXPECT_EQ("", err);
- EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1);
- EXPECT_EQ("", err);
-
-#if 0 // TODO: Investigate why. Also see https://github.com/ninja-build/ninja/pull/1423
- EXPECT_EQ(disk_.Stat("subdir", &err),
- disk_.Stat("subdir/.", &err));
- EXPECT_EQ("", err);
- EXPECT_EQ(disk_.Stat("subdir", &err),
- disk_.Stat("subdir/subsubdir/..", &err));
-#endif
- EXPECT_EQ("", err);
- EXPECT_EQ(disk_.Stat("..", &err), parent_stat_uncached);
- EXPECT_EQ("", err);
- EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err),
- disk_.Stat("subdir/subsubdir/.", &err));
- EXPECT_EQ("", err);
-
- if (RunsUnderWine()) // This test fails under Wine.
- return;
-
- // Test error cases.
- string bad_path("cc:\\foo");
- EXPECT_EQ(-1, disk_.Stat(bad_path, &err));
- EXPECT_NE("", err); err.clear();
- EXPECT_EQ(-1, disk_.Stat(bad_path, &err));
- EXPECT_NE("", err); err.clear();
- EXPECT_EQ(0, disk_.Stat("nosuchfile", &err));
- EXPECT_EQ("", err);
- EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err));
- EXPECT_EQ("", err);
-}
-#endif
-
-TEST_F(DiskInterfaceTest, ReadFile) {
- string err;
- std::string content;
- ASSERT_EQ(DiskInterface::NotFound,
- disk_.ReadFile("foobar", &content, &err));
- EXPECT_EQ("", content);
- EXPECT_NE("", err); // actual value is platform-specific
- err.clear();
-
- const char* kTestFile = "testfile";
- FILE* f = fopen(kTestFile, "wb");
- ASSERT_TRUE(f);
- const char* kTestContent = "test content\nok";
- fprintf(f, "%s", kTestContent);
- ASSERT_EQ(0, fclose(f));
-
- ASSERT_EQ(DiskInterface::Okay,
- disk_.ReadFile(kTestFile, &content, &err));
- EXPECT_EQ(kTestContent, content);
- EXPECT_EQ("", err);
-}
-
-TEST_F(DiskInterfaceTest, MakeDirs) {
- string path = "path/with/double//slash/";
- EXPECT_TRUE(disk_.MakeDirs(path));
- FILE* f = fopen((path + "a_file").c_str(), "w");
- EXPECT_TRUE(f);
- EXPECT_EQ(0, fclose(f));
-#ifdef _WIN32
- string path2 = "another\\with\\back\\\\slashes\\";
- EXPECT_TRUE(disk_.MakeDirs(path2));
- FILE* f2 = fopen((path2 + "a_file").c_str(), "w");
- EXPECT_TRUE(f2);
- EXPECT_EQ(0, fclose(f2));
-#endif
-}
-
-TEST_F(DiskInterfaceTest, RemoveFile) {
- const char* kFileName = "file-to-remove";
- ASSERT_TRUE(Touch(kFileName));
- EXPECT_EQ(0, disk_.RemoveFile(kFileName));
- EXPECT_EQ(1, disk_.RemoveFile(kFileName));
- EXPECT_EQ(1, disk_.RemoveFile("does not exist"));
-#ifdef _WIN32
- ASSERT_TRUE(Touch(kFileName));
- EXPECT_EQ(0, system((std::string("attrib +R ") + kFileName).c_str()));
- EXPECT_EQ(0, disk_.RemoveFile(kFileName));
- EXPECT_EQ(1, disk_.RemoveFile(kFileName));
-#endif
-}
-
-TEST_F(DiskInterfaceTest, RemoveDirectory) {
- const char* kDirectoryName = "directory-to-remove";
- EXPECT_TRUE(disk_.MakeDir(kDirectoryName));
- EXPECT_EQ(0, disk_.RemoveFile(kDirectoryName));
- EXPECT_EQ(1, disk_.RemoveFile(kDirectoryName));
- EXPECT_EQ(1, disk_.RemoveFile("does not exist"));
-}
-
-struct StatTest : public StateTestWithBuiltinRules,
- public DiskInterface {
- StatTest() : scan_(&state_, NULL, NULL, this, NULL) {}
-
- // DiskInterface implementation.
- virtual TimeStamp Stat(const string& path, string* err) const;
- 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;
- }
-
- DependencyScan scan_;
- map<string, TimeStamp> mtimes_;
- mutable vector<string> stats_;
-};
-
-TimeStamp StatTest::Stat(const string& path, string* err) const {
- stats_.push_back(path);
- map<string, TimeStamp>::const_iterator i = mtimes_.find(path);
- if (i == mtimes_.end())
- return 0; // File not found.
- return i->second;
-}
-
-TEST_F(StatTest, Simple) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in\n"));
-
- Node* out = GetNode("out");
- string err;
- EXPECT_TRUE(out->Stat(this, &err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL, NULL);
- ASSERT_EQ(2u, stats_.size());
- ASSERT_EQ("out", stats_[0]);
- ASSERT_EQ("in", stats_[1]);
-}
-
-TEST_F(StatTest, TwoStep) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n"));
-
- Node* out = GetNode("out");
- string err;
- EXPECT_TRUE(out->Stat(this, &err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL, NULL);
- ASSERT_EQ(3u, stats_.size());
- ASSERT_EQ("out", stats_[0]);
- ASSERT_TRUE(GetNode("out")->dirty());
- ASSERT_EQ("mid", stats_[1]);
- ASSERT_TRUE(GetNode("mid")->dirty());
- ASSERT_EQ("in", stats_[2]);
-}
-
-TEST_F(StatTest, Tree) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid1 mid2\n"
-"build mid1: cat in11 in12\n"
-"build mid2: cat in21 in22\n"));
-
- Node* out = GetNode("out");
- string err;
- EXPECT_TRUE(out->Stat(this, &err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL, NULL);
- ASSERT_EQ(1u + 6u, stats_.size());
- ASSERT_EQ("mid1", stats_[1]);
- ASSERT_TRUE(GetNode("mid1")->dirty());
- ASSERT_EQ("in11", stats_[2]);
-}
-
-TEST_F(StatTest, Middle) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n"));
-
- mtimes_["in"] = 1;
- mtimes_["mid"] = 0; // missing
- mtimes_["out"] = 1;
-
- Node* out = GetNode("out");
- string err;
- EXPECT_TRUE(out->Stat(this, &err));
- EXPECT_EQ("", err);
- ASSERT_EQ(1u, stats_.size());
- scan_.RecomputeDirty(out, NULL, NULL);
- ASSERT_FALSE(GetNode("in")->dirty());
- ASSERT_TRUE(GetNode("mid")->dirty());
- ASSERT_TRUE(GetNode("out")->dirty());
-}
-
-} // namespace
diff --git a/src/dyndep.cc b/src/dyndep.cc
deleted file mode 100644
index 7e49a75..0000000
--- a/src/dyndep.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2015 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 "dyndep.h"
-
-#include <assert.h>
-#include <stdio.h>
-
-#include "debug_flags.h"
-#include "disk_interface.h"
-#include "dyndep_parser.h"
-#include "graph.h"
-#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);
-}
-
-bool DyndepLoader::LoadDyndeps(Node* node, DyndepFile* ddf,
- std::string* err) const {
- // We are loading the dyndep file now so it is no longer pending.
- node->set_dyndep_pending(false);
-
- // Load the dyndep information from the file.
- EXPLAIN("loading dyndep file '%s'", node->path().c_str());
- if (!LoadDyndepFile(node, ddf, err))
- return false;
-
- // Update each edge that specified this node as its dyndep binding.
- std::vector<Edge*> const& out_edges = node->out_edges();
- for (std::vector<Edge*>::const_iterator oe = out_edges.begin();
- oe != out_edges.end(); ++oe) {
- Edge* const edge = *oe;
- if (edge->dyndep_ != node)
- continue;
-
- DyndepFile::iterator ddi = ddf->find(edge);
- if (ddi == ddf->end()) {
- *err = ("'" + edge->outputs_[0]->path() + "' "
- "not mentioned in its dyndep file "
- "'" + node->path() + "'");
- return false;
- }
-
- ddi->second.used_ = true;
- Dyndeps const& dyndeps = ddi->second;
- if (!UpdateEdge(edge, &dyndeps, err)) {
- return false;
- }
- }
-
- // Reject extra outputs in dyndep file.
- for (DyndepFile::const_iterator oe = ddf->begin(); oe != ddf->end();
- ++oe) {
- if (!oe->second.used_) {
- Edge* const edge = oe->first;
- *err = ("dyndep file '" + node->path() + "' mentions output "
- "'" + edge->outputs_[0]->path() + "' whose build statement "
- "does not have a dyndep binding for the file");
- return false;
- }
- }
-
- return true;
-}
-
-bool DyndepLoader::UpdateEdge(Edge* edge, Dyndeps const* dyndeps,
- std::string* err) const {
- // Add dyndep-discovered bindings to the edge.
- // We know the edge already has its own binding
- // scope because it has a "dyndep" binding.
- if (dyndeps->restat_)
- edge->SetRestat();
-
- if (!edge->UpdateDynamicImplicitOutputs(dyndeps->implicit_outputs_.size(),
- dyndeps->implicit_outputs_.data(),
- err)) {
- return false;
- }
-
- // Add the dyndep-discovered inputs to the edge.
- edge->UpdateDynamicImplicitDeps(dyndeps->implicit_inputs_.size(),
- dyndeps->implicit_inputs_.data());
-
- return true;
-}
-
-bool DyndepLoader::LoadDyndepFile(Node* file, DyndepFile* ddf,
- std::string* err) const {
- DyndepParser parser(state_, disk_interface_, ddf);
- return parser.Load(file->path(), err);
-}
diff --git a/src/dyndep.h b/src/dyndep.h
deleted file mode 100644
index 907f921..0000000
--- a/src/dyndep.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2015 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_DYNDEP_LOADER_H_
-#define NINJA_DYNDEP_LOADER_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-struct DiskInterface;
-struct Edge;
-struct Node;
-struct State;
-
-/// Store dynamically-discovered dependency information for one edge.
-struct Dyndeps {
- Dyndeps() : used_(false), restat_(false) {}
- bool used_;
- bool restat_;
- std::vector<Node*> implicit_inputs_;
- std::vector<Node*> implicit_outputs_;
-};
-
-/// Store data loaded from one dyndep file. Map from an edge
-/// to its dynamically-discovered dependency information.
-/// This is a struct rather than a typedef so that we can
-/// forward-declare it in other headers.
-struct DyndepFile: public std::map<Edge*, Dyndeps> {};
-
-/// DyndepLoader loads dynamically discovered dependencies, as
-/// referenced via the "dyndep" attribute in build files.
-struct DyndepLoader {
- DyndepLoader(State* state, DiskInterface* disk_interface)
- : state_(state), disk_interface_(disk_interface) {}
-
- /// Load a dyndep file from the given node's path and update the
- /// 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, std::string* err) const;
- bool LoadDyndeps(Node* node, DyndepFile* ddf, std::string* err) const;
-
- private:
- bool LoadDyndepFile(Node* file, DyndepFile* ddf, std::string* err) const;
-
- bool UpdateEdge(Edge* edge, Dyndeps const* dyndeps, std::string* err) const;
-
- State* state_;
- DiskInterface* disk_interface_;
-};
-
-#endif // NINJA_DYNDEP_LOADER_H_
diff --git a/src/dyndep_parser.cc b/src/dyndep_parser.cc
deleted file mode 100644
index 1b4dddd..0000000
--- a/src/dyndep_parser.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2015 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 "dyndep_parser.h"
-
-#include <vector>
-
-#include "dyndep.h"
-#include "graph.h"
-#include "state.h"
-#include "util.h"
-#include "version.h"
-
-using namespace std;
-
-DyndepParser::DyndepParser(State* state, FileReader* file_reader,
- DyndepFile* dyndep_file)
- : Parser(state, file_reader)
- , dyndep_file_(dyndep_file) {
-}
-
-bool DyndepParser::Parse(const string& filename, const string& input,
- string* err) {
- lexer_.Start(filename, input);
-
- // Require a supported ninja_dyndep_version value immediately so
- // we can exit before encountering any syntactic surprises.
- bool haveDyndepVersion = false;
-
- for (;;) {
- Lexer::Token token = lexer_.ReadToken();
- switch (token) {
- case Lexer::BUILD: {
- if (!haveDyndepVersion)
- return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
- if (!ParseEdge(err))
- return false;
- break;
- }
- case Lexer::IDENT: {
- lexer_.UnreadToken();
- if (haveDyndepVersion)
- return lexer_.Error(string("unexpected ") + Lexer::TokenName(token),
- err);
- if (!ParseDyndepVersion(err))
- return false;
- haveDyndepVersion = true;
- break;
- }
- case Lexer::ERROR:
- return lexer_.Error(lexer_.DescribeLastError(), err);
- case Lexer::TEOF:
- if (!haveDyndepVersion)
- return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
- return true;
- case Lexer::NEWLINE:
- break;
- default:
- return lexer_.Error(string("unexpected ") + Lexer::TokenName(token),
- err);
- }
- }
- return false; // not reached
-}
-
-bool DyndepParser::ParseDyndepVersion(string* err) {
- string name;
- EvalString let_value;
- if (!ParseLet(&name, &let_value, err))
- return false;
- if (name != "ninja_dyndep_version") {
- return lexer_.Error("expected 'ninja_dyndep_version = ...'", err);
- }
- string version = let_value.Evaluate(&env_);
- int major, minor;
- ParseVersion(version, &major, &minor);
- if (major != 1 || minor != 0) {
- return lexer_.Error(
- string("unsupported 'ninja_dyndep_version = ") + version + "'", err);
- return false;
- }
- return true;
-}
-
-bool DyndepParser::ParseLet(string* key, EvalString* value, string* err) {
- if (!lexer_.ReadIdent(key))
- return lexer_.Error("expected variable name", err);
- if (!ExpectToken(Lexer::EQUALS, err))
- return false;
- if (!lexer_.ReadVarValue(value, err))
- return false;
- return true;
-}
-
-bool DyndepParser::ParseEdge(string* err) {
- // Parse one explicit output. We expect it to already have an edge.
- // We will record its dynamically-discovered dependency information.
- Dyndeps* dyndeps = NULL;
- {
- EvalString out0;
- if (!lexer_.ReadPath(&out0, err))
- return false;
- if (out0.empty())
- return lexer_.Error("expected path", err);
-
- string path = out0.Evaluate(&env_);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- Node* node = state_->LookupNode(path);
- if (!node || !node->in_edge())
- return lexer_.Error("no build statement exists for '" + path + "'", err);
- Edge* edge = node->in_edge();
- std::pair<DyndepFile::iterator, bool> res =
- dyndep_file_->insert(DyndepFile::value_type(edge, Dyndeps()));
- if (!res.second)
- return lexer_.Error("multiple statements for '" + path + "'", err);
- dyndeps = &res.first->second;
- }
-
- // Disallow explicit outputs.
- {
- EvalString out;
- if (!lexer_.ReadPath(&out, err))
- return false;
- if (!out.empty())
- return lexer_.Error("explicit outputs not supported", err);
- }
-
- // Parse implicit outputs, if any.
- vector<EvalString> outs;
- if (lexer_.PeekToken(Lexer::PIPE)) {
- for (;;) {
- EvalString out;
- if (!lexer_.ReadPath(&out, err))
- return err;
- if (out.empty())
- break;
- outs.push_back(out);
- }
- }
-
- if (!ExpectToken(Lexer::COLON, err))
- return false;
-
- string rule_name;
- if (!lexer_.ReadIdent(&rule_name) || rule_name != "dyndep")
- return lexer_.Error("expected build command name 'dyndep'", err);
-
- // Disallow explicit inputs.
- {
- EvalString in;
- if (!lexer_.ReadPath(&in, err))
- return false;
- if (!in.empty())
- return lexer_.Error("explicit inputs not supported", err);
- }
-
- // Parse implicit inputs, if any.
- vector<EvalString> ins;
- if (lexer_.PeekToken(Lexer::PIPE)) {
- for (;;) {
- EvalString in;
- if (!lexer_.ReadPath(&in, err))
- return err;
- if (in.empty())
- break;
- ins.push_back(in);
- }
- }
-
- // Disallow order-only inputs.
- if (lexer_.PeekToken(Lexer::PIPE2))
- return lexer_.Error("order-only inputs not supported", err);
-
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- if (lexer_.PeekToken(Lexer::INDENT)) {
- string key;
- EvalString val;
- if (!ParseLet(&key, &val, err))
- return false;
- if (key != "restat")
- return lexer_.Error("binding is not 'restat'", err);
- string value = val.Evaluate(&env_);
- dyndeps->restat_ = !value.empty();
- }
-
- dyndeps->implicit_inputs_.reserve(ins.size());
- for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
- string path = i->Evaluate(&env_);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- Node* n = state_->GetNode(path, slash_bits);
- dyndeps->implicit_inputs_.push_back(n);
- }
-
- dyndeps->implicit_outputs_.reserve(outs.size());
- for (vector<EvalString>::iterator i = outs.begin(); i != outs.end(); ++i) {
- string path = i->Evaluate(&env_);
- if (path.empty())
- return lexer_.Error("empty path", err);
- string path_err;
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- Node* n = state_->GetNode(path, slash_bits);
- dyndeps->implicit_outputs_.push_back(n);
- }
-
- return true;
-}
diff --git a/src/dyndep_parser.h b/src/dyndep_parser.h
deleted file mode 100644
index 8f4c28d..0000000
--- a/src/dyndep_parser.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2015 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_DYNDEP_PARSER_H_
-#define NINJA_DYNDEP_PARSER_H_
-
-#include "eval_env.h"
-#include "parser.h"
-
-struct DyndepFile;
-struct EvalString;
-
-/// Parses dyndep files.
-struct DyndepParser: public Parser {
- DyndepParser(State* state, FileReader* file_reader,
- DyndepFile* dyndep_file);
-
- /// Parse a text string of input. Used by tests.
- 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 std::string& filename, const std::string& input,
- std:: 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_;
-};
-
-#endif // NINJA_DYNDEP_PARSER_H_
diff --git a/src/dyndep_parser_test.cc b/src/dyndep_parser_test.cc
deleted file mode 100644
index 1bba7ba..0000000
--- a/src/dyndep_parser_test.cc
+++ /dev/null
@@ -1,514 +0,0 @@
-// Copyright 2015 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 "dyndep_parser.h"
-
-#include <map>
-#include <vector>
-
-#include "dyndep.h"
-#include "graph.h"
-#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_);
- string err;
- EXPECT_TRUE(parser.ParseTest(input, &err));
- ASSERT_EQ("", err);
- }
-
- virtual void SetUp() {
- ::AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build out otherout: touch\n");
- }
-
- State state_;
- VirtualFileSystem fs_;
- DyndepFile dyndep_file_;
-};
-
-TEST_F(DyndepParserTest, Empty) {
- const char kInput[] =
-"";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
-}
-
-TEST_F(DyndepParserTest, Version1) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"));
-}
-
-TEST_F(DyndepParserTest, Version1Extra) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1-extra\n"));
-}
-
-TEST_F(DyndepParserTest, Version1_0) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1.0\n"));
-}
-
-TEST_F(DyndepParserTest, Version1_0Extra) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1.0-extra\n"));
-}
-
-TEST_F(DyndepParserTest, CommentVersion) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"# comment\n"
-"ninja_dyndep_version = 1\n"));
-}
-
-TEST_F(DyndepParserTest, BlankLineVersion) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"\n"
-"ninja_dyndep_version = 1\n"));
-}
-
-TEST_F(DyndepParserTest, VersionCRLF) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\r\n"));
-}
-
-TEST_F(DyndepParserTest, CommentVersionCRLF) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"# comment\r\n"
-"ninja_dyndep_version = 1\r\n"));
-}
-
-TEST_F(DyndepParserTest, BlankLineVersionCRLF) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"\r\n"
-"ninja_dyndep_version = 1\r\n"));
-}
-
-TEST_F(DyndepParserTest, VersionUnexpectedEOF) {
- const char kInput[] =
-"ninja_dyndep_version = 1.0";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: unexpected EOF\n"
- "ninja_dyndep_version = 1.0\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, UnsupportedVersion0) {
- const char kInput[] =
-"ninja_dyndep_version = 0\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 0'\n"
- "ninja_dyndep_version = 0\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, UnsupportedVersion1_1) {
- const char kInput[] =
-"ninja_dyndep_version = 1.1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: unsupported 'ninja_dyndep_version = 1.1'\n"
- "ninja_dyndep_version = 1.1\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, DuplicateVersion) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"ninja_dyndep_version = 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: unexpected identifier\n", err);
-}
-
-TEST_F(DyndepParserTest, MissingVersionOtherVar) {
- const char kInput[] =
-"not_ninja_dyndep_version = 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n"
- "not_ninja_dyndep_version = 1\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, MissingVersionBuild) {
- const char kInput[] =
-"build out: dyndep\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: expected 'ninja_dyndep_version = ...'\n", err);
-}
-
-TEST_F(DyndepParserTest, UnexpectedEqual) {
- const char kInput[] =
-"= 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: unexpected '='\n", err);
-}
-
-TEST_F(DyndepParserTest, UnexpectedIndent) {
- const char kInput[] =
-" = 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:1: unexpected indent\n", err);
-}
-
-TEST_F(DyndepParserTest, OutDuplicate) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-"build out: dyndep\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:3: multiple statements for 'out'\n"
- "build out: dyndep\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OutDuplicateThroughOther) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-"build otherout: dyndep\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:3: multiple statements for 'otherout'\n"
- "build otherout: dyndep\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, NoOutEOF) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: unexpected EOF\n"
- "build\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, NoOutColon) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build :\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: expected path\n"
- "build :\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OutNoStatement) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build missing: dyndep\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: no build statement exists for 'missing'\n"
- "build missing: dyndep\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OutEOF) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: unexpected EOF\n"
- "build out\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OutNoRule) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out:";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
- "build out:\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OutBadRule) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: touch";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: expected build command name 'dyndep'\n"
- "build out: touch\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, BuildEOF) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: unexpected EOF\n"
- "build out: dyndep\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, ExplicitOut) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out exp: dyndep\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: explicit outputs not supported\n"
- "build out exp: dyndep\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, ExplicitIn) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep exp\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: explicit inputs not supported\n"
- "build out: dyndep exp\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, OrderOnlyIn) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep ||\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:2: order-only inputs not supported\n"
- "build out: dyndep ||\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, BadBinding) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-" not_restat = 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:3: binding is not 'restat'\n"
- " not_restat = 1\n"
- " ^ near here", err);
-}
-
-TEST_F(DyndepParserTest, RestatTwice) {
- const char kInput[] =
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-" restat = 1\n"
-" restat = 1\n";
- DyndepParser parser(&state_, &fs_, &dyndep_file_);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:4: unexpected indent\n", err);
-}
-
-TEST_F(DyndepParserTest, NoImplicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, EmptyImplicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out | : dyndep |\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, ImplicitIn) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | impin\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- ASSERT_EQ(1u, i->second.implicit_inputs_.size());
- EXPECT_EQ("impin", i->second.implicit_inputs_[0]->path());
-}
-
-TEST_F(DyndepParserTest, ImplicitIns) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | impin1 impin2\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- ASSERT_EQ(2u, i->second.implicit_inputs_.size());
- EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
- EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
-}
-
-TEST_F(DyndepParserTest, ImplicitOut) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out | impout: dyndep\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- ASSERT_EQ(1u, i->second.implicit_outputs_.size());
- EXPECT_EQ("impout", i->second.implicit_outputs_[0]->path());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, ImplicitOuts) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out | impout1 impout2 : dyndep\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- ASSERT_EQ(2u, i->second.implicit_outputs_.size());
- EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
- EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, ImplicitInsAndOuts) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out | impout1 impout2: dyndep | impin1 impin2\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- ASSERT_EQ(2u, i->second.implicit_outputs_.size());
- EXPECT_EQ("impout1", i->second.implicit_outputs_[0]->path());
- EXPECT_EQ("impout2", i->second.implicit_outputs_[1]->path());
- ASSERT_EQ(2u, i->second.implicit_inputs_.size());
- EXPECT_EQ("impin1", i->second.implicit_inputs_[0]->path());
- EXPECT_EQ("impin2", i->second.implicit_inputs_[1]->path());
-}
-
-TEST_F(DyndepParserTest, Restat) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-" restat = 1\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(true, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, OtherOutput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build otherout: dyndep\n"));
-
- EXPECT_EQ(1u, dyndep_file_.size());
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
-}
-
-TEST_F(DyndepParserTest, MultipleEdges) {
- ::AssertParse(&state_,
-"build out2: touch\n");
- ASSERT_EQ(2u, state_.edges_.size());
- ASSERT_EQ(1u, state_.edges_[1]->outputs_.size());
- EXPECT_EQ("out2", state_.edges_[1]->outputs_[0]->path());
- EXPECT_EQ(0u, state_.edges_[0]->inputs_.size());
-
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-"build out2: dyndep\n"
-" restat = 1\n"));
-
- EXPECT_EQ(2u, dyndep_file_.size());
- {
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[0]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(false, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
- }
- {
- DyndepFile::iterator i = dyndep_file_.find(state_.edges_[1]);
- ASSERT_NE(i, dyndep_file_.end());
- EXPECT_EQ(true, i->second.restat_);
- EXPECT_EQ(0u, i->second.implicit_outputs_.size());
- EXPECT_EQ(0u, i->second.implicit_inputs_.size());
- }
-}
diff --git a/src/edit_distance.cc b/src/edit_distance.cc
deleted file mode 100644
index 34bf0e5..0000000
--- a/src/edit_distance.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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.
-
-#include "edit_distance.h"
-
-#include <algorithm>
-#include <vector>
-
-using namespace std;
-
-int EditDistance(const StringPiece& s1,
- const StringPiece& s2,
- bool allow_replacements,
- int max_edit_distance) {
- // The algorithm implemented below is the "classic"
- // dynamic-programming algorithm for computing the Levenshtein
- // distance, which is described here:
- //
- // http://en.wikipedia.org/wiki/Levenshtein_distance
- //
- // Although the algorithm is typically described using an m x n
- // array, only one row plus one element are used at a time, so this
- // implementation just keeps one vector for the row. To update one entry,
- // only the entries to the left, top, and top-left are needed. The left
- // entry is in row[x-1], the top entry is what's in row[x] from the last
- // iteration, and the top-left entry is stored in previous.
- int m = s1.len_;
- int n = s2.len_;
-
- vector<int> row(n + 1);
- for (int i = 1; i <= n; ++i)
- row[i] = i;
-
- for (int y = 1; y <= m; ++y) {
- row[0] = y;
- int best_this_row = row[0];
-
- int previous = y - 1;
- for (int x = 1; x <= n; ++x) {
- int old_row = row[x];
- if (allow_replacements) {
- row[x] = min(previous + (s1.str_[y - 1] == s2.str_[x - 1] ? 0 : 1),
- min(row[x - 1], row[x]) + 1);
- }
- else {
- if (s1.str_[y - 1] == s2.str_[x - 1])
- row[x] = previous;
- else
- row[x] = min(row[x - 1], row[x]) + 1;
- }
- previous = old_row;
- best_this_row = min(best_this_row, row[x]);
- }
-
- if (max_edit_distance && best_this_row > max_edit_distance)
- return max_edit_distance + 1;
- }
-
- return row[n];
-}
diff --git a/src/edit_distance.h b/src/edit_distance.h
deleted file mode 100644
index 45ae4ae..0000000
--- a/src/edit_distance.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.
-
-#ifndef NINJA_EDIT_DISTANCE_H_
-#define NINJA_EDIT_DISTANCE_H_
-
-#include "string_piece.h"
-
-int EditDistance(const StringPiece& s1,
- const StringPiece& s2,
- bool allow_replacements = true,
- int max_edit_distance = 0);
-
-#endif // NINJA_EDIT_DISTANCE_H_
diff --git a/src/edit_distance_test.cc b/src/edit_distance_test.cc
deleted file mode 100644
index 9dc0f82..0000000
--- a/src/edit_distance_test.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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.
-
-#include "edit_distance.h"
-
-#include "test.h"
-
-TEST(EditDistanceTest, TestEmpty) {
- EXPECT_EQ(5, EditDistance("", "ninja"));
- EXPECT_EQ(5, EditDistance("ninja", ""));
- EXPECT_EQ(0, EditDistance("", ""));
-}
-
-TEST(EditDistanceTest, TestMaxDistance) {
- const bool allow_replacements = true;
- for (int max_distance = 1; max_distance < 7; ++max_distance) {
- EXPECT_EQ(max_distance + 1,
- EditDistance("abcdefghijklmnop", "ponmlkjihgfedcba",
- allow_replacements, max_distance));
- }
-}
-
-TEST(EditDistanceTest, TestAllowReplacements) {
- bool allow_replacements = true;
- EXPECT_EQ(1, EditDistance("ninja", "njnja", allow_replacements));
- EXPECT_EQ(1, EditDistance("njnja", "ninja", allow_replacements));
-
- allow_replacements = false;
- EXPECT_EQ(2, EditDistance("ninja", "njnja", allow_replacements));
- EXPECT_EQ(2, EditDistance("njnja", "ninja", allow_replacements));
-}
-
-TEST(EditDistanceTest, TestBasics) {
- EXPECT_EQ(0, EditDistance("browser_tests", "browser_tests"));
- EXPECT_EQ(1, EditDistance("browser_test", "browser_tests"));
- EXPECT_EQ(1, EditDistance("browser_tests", "browser_test"));
-}
diff --git a/src/eval_env.cc b/src/eval_env.cc
deleted file mode 100644
index 675bb83..0000000
--- a/src/eval_env.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// 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.
-
-#include <assert.h>
-
-#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())
- return i->second;
- if (parent_)
- return parent_->LookupVariable(var);
- return "";
-}
-
-void BindingEnv::AddBinding(const string& key, const string& val) {
- bindings_[key] = val;
-}
-
-void BindingEnv::AddRule(Rule* rule) {
- assert(LookupRuleCurrentScope(rule->name()) == NULL);
- rules_.emplace(rule->name(), std::unique_ptr<Rule>(rule));
-}
-
-const Rule* BindingEnv::LookupRuleCurrentScope(const string& rule_name) {
- auto it = rules_.find(rule_name);
- if (it == rules_.end())
- return NULL;
- return it->second.get();
-}
-
-const Rule* BindingEnv::LookupRule(const string& rule_name) {
- auto it = rules_.find(rule_name);
- if (it != rules_.end())
- return it->second.get();
- if (parent_)
- return parent_->LookupRule(rule_name);
- return NULL;
-}
-
-void Rule::AddBinding(const string& key, const EvalString& val) {
- bindings_[key] = val;
-}
-
-const EvalString* Rule::GetBinding(const string& key) const {
- Bindings::const_iterator i = bindings_.find(key);
- if (i == bindings_.end())
- return NULL;
- return &i->second;
-}
-
-// static
-bool Rule::IsReservedBinding(const string& var) {
- return var == "command" ||
- var == "depfile" ||
- var == "dyndep" ||
- var == "description" ||
- var == "deps" ||
- var == "generator" ||
- var == "pool" ||
- var == "restat" ||
- var == "rspfile" ||
- var == "rspfile_content" ||
- var == "msvc_deps_prefix";
-}
-
-string BindingEnv::LookupWithFallback(const string& var,
- const EvalString* eval,
- Env* env) {
- map<string, string>::iterator i = bindings_.find(var);
- if (i != bindings_.end())
- return i->second;
-
- if (eval)
- return eval->Evaluate(env);
-
- if (parent_)
- return parent_->LookupVariable(var);
-
- return "";
-}
-
-string EvalString::Evaluate(Env* env) const {
- string result;
- for (TokenList::const_iterator i = parsed_.begin(); i != parsed_.end(); ++i) {
- if (i->second == RAW)
- result.append(i->first);
- else
- result.append(env->LookupVariable(i->first));
- }
- return result;
-}
-
-void EvalString::AddText(StringPiece text) {
- // Add it to the end of an existing RAW token if possible.
- if (!parsed_.empty() && parsed_.back().second == RAW) {
- parsed_.back().first.append(text.str_, text.len_);
- } else {
- parsed_.push_back(make_pair(text.AsString(), RAW));
- }
-}
-void EvalString::AddSpecial(StringPiece text) {
- parsed_.push_back(make_pair(text.AsString(), SPECIAL));
-}
-
-string EvalString::Serialize() const {
- string result;
- for (TokenList::const_iterator i = parsed_.begin();
- i != parsed_.end(); ++i) {
- result.append("[");
- if (i->second == SPECIAL)
- result.append("$");
- result.append(i->first);
- result.append("]");
- }
- return result;
-}
-
-string EvalString::Unparse() const {
- string result;
- for (TokenList::const_iterator i = parsed_.begin();
- i != parsed_.end(); ++i) {
- bool special = (i->second == SPECIAL);
- if (special)
- result.append("${");
- result.append(i->first);
- if (special)
- result.append("}");
- }
- return result;
-}
diff --git a/src/eval_env.h b/src/eval_env.h
deleted file mode 100644
index 2c69d94..0000000
--- a/src/eval_env.h
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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.
-
-#ifndef NINJA_EVAL_ENV_H_
-#define NINJA_EVAL_ENV_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "string_piece.h"
-
-struct Rule;
-
-/// An interface for a scope for variable (e.g. "$foo") lookups.
-struct Env {
- virtual ~Env() {}
- virtual std::string LookupVariable(const std::string& var) = 0;
-};
-
-/// A tokenized string that contains variable references.
-/// Can be evaluated relative to an Env.
-struct EvalString {
- /// @return The evaluated string with variable expanded using value found in
- /// environment @a env.
- std::string Evaluate(Env* env) const;
-
- /// @return The string with variables not expanded.
- std::string Unparse() const;
-
- void Clear() { parsed_.clear(); }
- bool empty() const { return parsed_.empty(); }
-
- void AddText(StringPiece text);
- void AddSpecial(StringPiece text);
-
- /// Construct a human-readable representation of the parsed state,
- /// for use in tests.
- std::string Serialize() const;
-
-private:
- enum TokenType { RAW, SPECIAL };
- typedef std::vector<std::pair<std::string, TokenType> > TokenList;
- TokenList parsed_;
-};
-
-/// An invocable build command and associated metadata (description, etc.).
-struct Rule {
- Rule(StringPiece name)
- : name_(name.str_, name.len_), is_phony_(name_ == "phony") {}
-
- const std::string& name() const { return name_; }
-
- bool is_phony() const { return is_phony_; }
-
- void AddBinding(const std::string& key, const EvalString& val);
-
- static bool IsReservedBinding(const std::string& var);
-
- 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;
-
- std::string name_;
- bool is_phony_;
- typedef std::map<std::string, EvalString> Bindings;
- Bindings bindings_;
-};
-
-/// An Env which contains a mapping of variables to values
-/// as well as a pointer to a parent scope.
-struct BindingEnv : public Env {
- BindingEnv() : parent_(NULL) {}
- explicit BindingEnv(BindingEnv* parent) : parent_(parent) {}
-
- BindingEnv(const BindingEnv&) = delete;
- BindingEnv& operator=(const BindingEnv&) = delete;
-
- BindingEnv(BindingEnv&&) noexcept = default;
- BindingEnv& operator=(BindingEnv&&) noexcept = default;
-
- virtual ~BindingEnv() {}
- virtual std::string LookupVariable(const std::string& var);
-
- /// Add a new rule, takes ownership.
- void AddRule(Rule* rule);
- const Rule* LookupRule(const std::string& rule_name);
- const Rule* LookupRuleCurrentScope(const std::string& rule_name);
-
- using RuleMapType = std::map<std::string, std::unique_ptr<const Rule>>;
- const RuleMapType& GetRules() const { return rules_; }
-
- 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).
- std::string LookupWithFallback(const std::string& var, const EvalString* eval,
- Env* env);
-
-private:
- std::map<std::string, std::string> bindings_;
- RuleMapType rules_;
- BindingEnv* parent_;
-};
-
-#endif // NINJA_EVAL_ENV_H_
diff --git a/src/exit_status.h b/src/exit_status.h
deleted file mode 100644
index dd45efc..0000000
--- a/src/exit_status.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.
-
-#ifndef NINJA_EXIT_STATUS_H_
-#define NINJA_EXIT_STATUS_H_
-
-enum ExitStatus {
- ExitSuccess,
- ExitFailure,
- ExitInterrupted,
- ExitTimeout,
-};
-
-#endif // NINJA_EXIT_STATUS_H_
diff --git a/src/gen_doxygen_mainpage.sh b/src/gen_doxygen_mainpage.sh
deleted file mode 100755
index d159947..0000000
--- a/src/gen_doxygen_mainpage.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/sh
-
-# 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.
-
-set -o errexit
-set -o nounset
-
-STATUS=0
-
-# Print each of its arguments on stderr (one per line) prefixed by the
-# basename of this script.
-stderr()
-{
- local me=$(basename "$0")
- local i
- for i
- do
- echo >&2 "$me: $i"
- done
-}
-
-# Print each of its arguments on stderr (one per line) prefixed by the
-# basename of this script and 'error'.
-error()
-{
- local i
- for i
- do
- stderr "error: $i"
- done
- STATUS=1
-}
-
-generate_header()
-{
- cat <<EOF
-/**
- * \\mainpage
-EOF
-}
-
-generate_footer()
-{
- cat <<EOF
- */
-EOF
-}
-
-include_file()
-{
- local file="$1"
- if ! [ -r "$file" ]
- then
- error "'$file' is not readable."
- return
- fi
- cat <<EOF
- * \\section $file
- * \\verbatim
-EOF
- cat < "$file"
- cat <<EOF
- \\endverbatim
-EOF
-}
-
-if [ $# -eq 0 ]
-then
- echo >&2 "usage: $0 inputs..."
- exit 1
-fi
-
-generate_header
-for i in "$@"
-do
- include_file "$i"
-done
-generate_footer
-
-exit $STATUS
diff --git a/src/getopt.c b/src/getopt.c
deleted file mode 100644
index 861f07f..0000000
--- a/src/getopt.c
+++ /dev/null
@@ -1,410 +0,0 @@
-/****************************************************************************
-
-getopt.c - Read command line options
-
-AUTHOR: Gregory Pietsch
-CREATED Fri Jan 10 21:13:05 1997
-
-DESCRIPTION:
-
-The getopt() function parses the command line arguments. Its arguments argc
-and argv are the argument count and array as passed to the main() function
-on program invocation. The argument optstring is a list of available option
-characters. If such a character is followed by a colon (`:'), the option
-takes an argument, which is placed in optarg. If such a character is
-followed by two colons, the option takes an optional argument, which is
-placed in optarg. If the option does not take an argument, optarg is NULL.
-
-The external variable optind is the index of the next array element of argv
-to be processed; it communicates from one call to the next which element to
-process.
-
-The getopt_long() function works like getopt() except that it also accepts
-long options started by two dashes `--'. If these take values, it is either
-in the form
-
---arg=value
-
- or
-
---arg value
-
-It takes the additional arguments longopts which is a pointer to the first
-element of an array of type GETOPT_LONG_OPTION_T. The last element of the
-array has to be filled with NULL for the name field.
-
-The longind pointer points to the index of the current long option relative
-to longopts if it is non-NULL.
-
-The getopt() function returns the option character if the option was found
-successfully, `:' if there was a missing parameter for one of the options,
-`?' for an unknown option character, and EOF for the end of the option list.
-
-The getopt_long() function's return value is described in the header file.
-
-The function getopt_long_only() is identical to getopt_long(), except that a
-plus sign `+' can introduce long options as well as `--'.
-
-The following describes how to deal with options that follow non-option
-argv-elements.
-
-If the caller did not specify anything, the default is REQUIRE_ORDER if the
-environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise.
-
-REQUIRE_ORDER means don't recognize them as options; stop option processing
-when the first non-option is seen. This is what Unix does. This mode of
-operation is selected by either setting the environment variable
-POSIXLY_CORRECT, or using `+' as the first character of the optstring
-parameter.
-
-PERMUTE is the default. We permute the contents of ARGV as we scan, so that
-eventually all the non-options are at the end. This allows options to be
-given in any order, even with programs that were not written to expect this.
-
-RETURN_IN_ORDER is an option available to programs that were written to
-expect options and other argv-elements in any order and that care about the
-ordering of the two. We describe each non-option argv-element as if it were
-the argument of an option with character code 1. Using `-' as the first
-character of the optstring parameter selects this mode of operation.
-
-The special argument `--' forces an end of option-scanning regardless of the
-value of ordering. In the case of RETURN_IN_ORDER, only `--' can cause
-getopt() and friends to return EOF with optind != argc.
-
-COPYRIGHT NOTICE AND DISCLAIMER:
-
-Copyright (C) 1997 Gregory Pietsch
-
-This file and the accompanying getopt.h header file are hereby placed in the
-public domain without restrictions. Just give the author credit, don't
-claim you wrote it or prevent anyone else from using it.
-
-Gregory Pietsch's current e-mail address:
-gpietsch@comcast.net
-****************************************************************************/
-
-/* include files */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#ifndef GETOPT_H
-#include "getopt.h"
-#endif
-
-/* macros */
-
-/* types */
-typedef enum GETOPT_ORDERING_T
-{
- PERMUTE,
- RETURN_IN_ORDER,
- REQUIRE_ORDER
-} GETOPT_ORDERING_T;
-
-/* globally-defined variables */
-char *optarg = NULL;
-int optind = 0;
-int opterr = 1;
-int optopt = '?';
-
-/* functions */
-
-/* reverse_argv_elements: reverses num elements starting at argv */
-static void
-reverse_argv_elements (char **argv, int num)
-{
- int i;
- char *tmp;
-
- for (i = 0; i < (num >> 1); i++)
- {
- tmp = argv[i];
- argv[i] = argv[num - i - 1];
- argv[num - i - 1] = tmp;
- }
-}
-
-/* permute: swap two blocks of argv-elements given their lengths */
-static void
-permute (char **argv, int len1, int len2)
-{
- reverse_argv_elements (argv, len1);
- reverse_argv_elements (argv, len1 + len2);
- reverse_argv_elements (argv, len2);
-}
-
-/* is_option: is this argv-element an option or the end of the option list? */
-static int
-is_option (char *argv_element, int only)
-{
- return ((argv_element == NULL)
- || (argv_element[0] == '-') || (only && argv_element[0] == '+'));
-}
-
-/* getopt_internal: the function that does all the dirty work */
-static int
-getopt_internal (int argc, char **argv, char *shortopts,
- GETOPT_LONG_OPTION_T * longopts, int *longind, int only)
-{
- GETOPT_ORDERING_T ordering = PERMUTE;
- static size_t optwhere = 0;
- size_t permute_from = 0;
- int num_nonopts = 0;
- int optindex = 0;
- size_t match_chars = 0;
- char *possible_arg = NULL;
- int longopt_match = -1;
- int has_arg = -1;
- char *cp = NULL;
- int arg_next = 0;
-
- /* first, deal with silly parameters and easy stuff */
- if (argc == 0 || argv == NULL || (shortopts == NULL && longopts == NULL))
- return (optopt = '?');
- if (optind >= argc || argv[optind] == NULL)
- return EOF;
- if (strcmp (argv[optind], "--") == 0)
- {
- optind++;
- return EOF;
- }
- /* if this is our first time through */
- if (optind == 0)
- optind = optwhere = 1;
-
- /* define ordering */
- if (shortopts != NULL && (*shortopts == '-' || *shortopts == '+'))
- {
- ordering = (*shortopts == '-') ? RETURN_IN_ORDER : REQUIRE_ORDER;
- shortopts++;
- }
- else
- ordering = (getenv ("POSIXLY_CORRECT") != NULL) ? REQUIRE_ORDER : PERMUTE;
-
- /*
- * based on ordering, find our next option, if we're at the beginning of
- * one
- */
- if (optwhere == 1)
- {
- switch (ordering)
- {
- case PERMUTE:
- permute_from = optind;
- num_nonopts = 0;
- while (!is_option (argv[optind], only))
- {
- optind++;
- num_nonopts++;
- }
- if (argv[optind] == NULL)
- {
- /* no more options */
- optind = permute_from;
- return EOF;
- }
- else if (strcmp (argv[optind], "--") == 0)
- {
- /* no more options, but have to get `--' out of the way */
- permute (argv + permute_from, num_nonopts, 1);
- optind = permute_from + 1;
- return EOF;
- }
- break;
- case RETURN_IN_ORDER:
- if (!is_option (argv[optind], only))
- {
- optarg = argv[optind++];
- return (optopt = 1);
- }
- break;
- case REQUIRE_ORDER:
- if (!is_option (argv[optind], only))
- return EOF;
- break;
- }
- }
- /* we've got an option, so parse it */
-
- /* first, is it a long option? */
- if (longopts != NULL
- && (memcmp (argv[optind], "--", 2) == 0
- || (only && argv[optind][0] == '+')) && optwhere == 1)
- {
- /* handle long options */
- if (memcmp (argv[optind], "--", 2) == 0)
- optwhere = 2;
- longopt_match = -1;
- possible_arg = strchr (argv[optind] + optwhere, '=');
- if (possible_arg == NULL)
- {
- /* no =, so next argv might be arg */
- match_chars = strlen (argv[optind]);
- possible_arg = argv[optind] + match_chars;
- match_chars = match_chars - optwhere;
- }
- else
- match_chars = (possible_arg - argv[optind]) - optwhere;
- for (optindex = 0; longopts[optindex].name != NULL; optindex++)
- {
- if (memcmp (argv[optind] + optwhere,
- longopts[optindex].name, match_chars) == 0)
- {
- /* do we have an exact match? */
- if (match_chars == strlen (longopts[optindex].name))
- {
- longopt_match = optindex;
- break;
- }
- /* do any characters match? */
- else
- {
- if (longopt_match < 0)
- longopt_match = optindex;
- else
- {
- /* we have ambiguous options */
- if (opterr)
- fprintf (stderr, "%s: option `%s' is ambiguous "
- "(could be `--%s' or `--%s')\n",
- argv[0],
- argv[optind],
- longopts[longopt_match].name,
- longopts[optindex].name);
- return (optopt = '?');
- }
- }
- }
- }
- if (longopt_match >= 0)
- has_arg = longopts[longopt_match].has_arg;
- }
- /* if we didn't find a long option, is it a short option? */
- if (longopt_match < 0 && shortopts != NULL)
- {
- cp = strchr (shortopts, argv[optind][optwhere]);
- if (cp == NULL)
- {
- /* couldn't find option in shortopts */
- if (opterr)
- fprintf (stderr,
- "%s: invalid option -- `-%c'\n",
- argv[0], argv[optind][optwhere]);
- optwhere++;
- if (argv[optind][optwhere] == '\0')
- {
- optind++;
- optwhere = 1;
- }
- return (optopt = '?');
- }
- has_arg = ((cp[1] == ':')
- ? ((cp[2] == ':') ? OPTIONAL_ARG : required_argument) : no_argument);
- possible_arg = argv[optind] + optwhere + 1;
- optopt = *cp;
- }
- /* get argument and reset optwhere */
- arg_next = 0;
- switch (has_arg)
- {
- case OPTIONAL_ARG:
- if (*possible_arg == '=')
- possible_arg++;
- if (*possible_arg != '\0')
- {
- optarg = possible_arg;
- optwhere = 1;
- }
- else
- optarg = NULL;
- break;
- case required_argument:
- if (*possible_arg == '=')
- possible_arg++;
- if (*possible_arg != '\0')
- {
- optarg = possible_arg;
- optwhere = 1;
- }
- else if (optind + 1 >= argc)
- {
- if (opterr)
- {
- fprintf (stderr, "%s: argument required for option `", argv[0]);
- if (longopt_match >= 0)
- fprintf (stderr, "--%s'\n", longopts[longopt_match].name);
- else
- fprintf (stderr, "-%c'\n", *cp);
- }
- optind++;
- return (optopt = ':');
- }
- else
- {
- optarg = argv[optind + 1];
- arg_next = 1;
- optwhere = 1;
- }
- break;
- case no_argument:
- if (longopt_match < 0)
- {
- optwhere++;
- if (argv[optind][optwhere] == '\0')
- optwhere = 1;
- }
- else
- optwhere = 1;
- optarg = NULL;
- break;
- }
-
- /* do we have to permute or otherwise modify optind? */
- if (ordering == PERMUTE && optwhere == 1 && num_nonopts != 0)
- {
- permute (argv + permute_from, num_nonopts, 1 + arg_next);
- optind = permute_from + 1 + arg_next;
- }
- else if (optwhere == 1)
- optind = optind + 1 + arg_next;
-
- /* finally return */
- if (longopt_match >= 0)
- {
- if (longind != NULL)
- *longind = longopt_match;
- if (longopts[longopt_match].flag != NULL)
- {
- *(longopts[longopt_match].flag) = longopts[longopt_match].val;
- return 0;
- }
- else
- return longopts[longopt_match].val;
- }
- else
- return optopt;
-}
-
-#ifndef _AIX
-int
-getopt (int argc, char **argv, char *optstring)
-{
- return getopt_internal (argc, argv, optstring, NULL, NULL, 0);
-}
-#endif
-
-int
-getopt_long (int argc, char **argv, const char *shortopts,
- const GETOPT_LONG_OPTION_T * longopts, int *longind)
-{
- return getopt_internal (argc, argv, (char*)shortopts, (GETOPT_LONG_OPTION_T*)longopts, longind, 0);
-}
-
-int
-getopt_long_only (int argc, char **argv, const char *shortopts,
- const GETOPT_LONG_OPTION_T * longopts, int *longind)
-{
- return getopt_internal (argc, argv, (char*)shortopts, (GETOPT_LONG_OPTION_T*)longopts, longind, 1);
-}
-
-/* end of file GETOPT.C */
diff --git a/src/getopt.h b/src/getopt.h
deleted file mode 100644
index 965dc29..0000000
--- a/src/getopt.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef GETOPT_H
-#define GETOPT_H
-
-/* include files needed by this include file */
-
-/* macros defined by this include file */
-#define no_argument 0
-#define required_argument 1
-#define OPTIONAL_ARG 2
-
-/* types defined by this include file */
-
-/* GETOPT_LONG_OPTION_T: The type of long option */
-typedef struct GETOPT_LONG_OPTION_T
-{
- const char *name; /* the name of the long option */
- int has_arg; /* one of the above macros */
- int *flag; /* determines if getopt_long() returns a
- * value for a long option; if it is
- * non-NULL, 0 is returned as a function
- * value and the value of val is stored in
- * the area pointed to by flag. Otherwise,
- * val is returned. */
- int val; /* determines the value to return if flag is
- * NULL. */
-} GETOPT_LONG_OPTION_T;
-
-typedef GETOPT_LONG_OPTION_T option;
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
- /* externally-defined variables */
- extern char *optarg;
- extern int optind;
- extern int opterr;
- extern int optopt;
-
- /* function prototypes */
-#ifndef _AIX
- int getopt (int argc, char **argv, char *optstring);
-#endif
- int getopt_long (int argc, char **argv, const char *shortopts,
- const GETOPT_LONG_OPTION_T * longopts, int *longind);
- int getopt_long_only (int argc, char **argv, const char *shortopts,
- const GETOPT_LONG_OPTION_T * longopts, int *longind);
-
-#ifdef __cplusplus
-};
-
-#endif
-
-#endif /* GETOPT_H */
-
-/* END OF FILE getopt.h */
diff --git a/src/graph.cc b/src/graph.cc
deleted file mode 100644
index af639df..0000000
--- a/src/graph.cc
+++ /dev/null
@@ -1,900 +0,0 @@
-// 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.
-
-#include "graph.h"
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <algorithm>
-#include <deque>
-#include <set>
-
-#include "build_log.h"
-#include "debug_flags.h"
-#include "depfile_parser.h"
-#include "deps_log.h"
-#include "disk_interface.h"
-#include "manifest_parser.h"
-#include "metrics.h"
-#include "state.h"
-#include "util.h"
-
-using namespace std;
-
-bool Node::Stat(DiskInterface* disk_interface, string* err) {
- mtime_ = disk_interface->Stat(path_, err);
- if (mtime_ == -1) {
- return false;
- }
- exists_ = (mtime_ != 0) ? ExistenceStatusExists : ExistenceStatusMissing;
- return true;
-}
-
-void Node::UpdatePhonyMtime(TimeStamp mtime) {
- if (!exists()) {
- mtime_ = std::max(mtime_, mtime);
- }
-}
-
-bool DependencyScan::RecomputeDirty(Node* initial_node,
- std::vector<Node*>* validation_nodes,
- string* err) {
- std::vector<Node*> stack;
- std::vector<Node*> new_validation_nodes;
-
- std::deque<Node*> nodes(1, initial_node);
-
- // RecomputeNodeDirty might return new validation nodes that need to be
- // checked for dirty state, keep a queue of nodes to visit.
- while (!nodes.empty()) {
- Node* node = nodes.front();
- nodes.pop_front();
-
- stack.clear();
- new_validation_nodes.clear();
-
- if (!RecomputeNodeDirty(node, &stack, &new_validation_nodes, err))
- return false;
- nodes.insert(nodes.end(), new_validation_nodes.begin(),
- new_validation_nodes.end());
- if (!new_validation_nodes.empty()) {
- assert(validation_nodes &&
- "validations require RecomputeDirty to be called with validation_nodes");
- validation_nodes->insert(validation_nodes->end(),
- new_validation_nodes.begin(),
- new_validation_nodes.end());
- }
- }
-
- return true;
-}
-
-bool DependencyScan::RecomputeNodeDirty(Node* node, std::vector<Node*>* stack,
- std::vector<Node*>* validation_nodes,
- string* err) {
- Edge* edge = node->in_edge();
- if (!edge) {
- // If we already visited this leaf node then we are done.
- if (node->status_known())
- return true;
- // This node has no in-edge; it is dirty if it is missing.
- if (!node->StatIfNecessary(disk_interface_, err))
- return false;
- if (!node->exists())
- EXPLAIN("%s has no in-edge and is missing", node->path().c_str());
- node->set_dirty(!node->exists());
- return true;
- }
-
- // If we already finished this edge then we are done.
- if (edge->mark_ == Edge::VisitDone)
- return true;
-
- // If we encountered this edge earlier in the call stack we have a cycle.
- if (!VerifyDAG(node, stack, err))
- return false;
-
- // Mark the edge temporarily while in the call stack.
- edge->mark_ = Edge::VisitInStack;
- stack->push_back(node);
-
- bool dirty = false;
- edge->outputs_ready_ = true;
- edge->deps_missing_ = false;
-
- if (!edge->deps_loaded_) {
- // This is our first encounter with this edge.
- // If there is a pending dyndep file, visit it now:
- // * If the dyndep file is ready then load it now to get any
- // additional inputs and outputs for this and other edges.
- // Once the dyndep file is loaded it will no longer be pending
- // if any other edges encounter it, but they will already have
- // been updated.
- // * If the dyndep file is not ready then since is known to be an
- // input to this edge, the edge will not be considered ready below.
- // Later during the build the dyndep file will become ready and be
- // loaded to update this edge before it can possibly be scheduled.
- if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
- if (!RecomputeNodeDirty(edge->dyndep_, stack, validation_nodes, err))
- return false;
-
- if (!edge->dyndep_->in_edge() ||
- edge->dyndep_->in_edge()->outputs_ready()) {
- // The dyndep file is ready, so load it now.
- if (!LoadDyndeps(edge->dyndep_, err))
- return false;
- }
- }
- }
-
- // Load output mtimes so we can compare them to the most recent input below.
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (!(*o)->StatIfNecessary(disk_interface_, err))
- return false;
- }
-
- if (!edge->deps_loaded_) {
- // This is our first encounter with this edge. Load discovered deps.
- edge->deps_loaded_ = true;
- if (!dep_loader_.LoadDeps(edge, err)) {
- if (!err->empty())
- return false;
- // Failed to load dependency info: rebuild to regenerate it.
- // LoadDeps() did EXPLAIN() already, no need to do it here.
- dirty = edge->deps_missing_ = true;
- }
- }
-
- // Store any validation nodes from the edge for adding to the initial
- // nodes. Don't recurse into them, that would trigger the dependency
- // cycle detector if the validation node depends on this node.
- // RecomputeDirty will add the validation nodes to the initial nodes
- // and recurse into them.
- validation_nodes->insert(validation_nodes->end(),
- edge->validations_.begin(), edge->validations_.end());
-
- // Visit all inputs; we're dirty if any of the inputs are dirty.
- Node* most_recent_input = NULL;
- for (vector<Node*>::iterator i = edge->inputs_.begin();
- i != edge->inputs_.end(); ++i) {
- // Visit this input.
- if (!RecomputeNodeDirty(*i, stack, validation_nodes, err))
- return false;
-
- // If an input is not ready, neither are our outputs.
- if (Edge* in_edge = (*i)->in_edge()) {
- if (!in_edge->outputs_ready_)
- edge->outputs_ready_ = false;
- }
-
- if (!edge->is_order_only(i - edge->inputs_.begin())) {
- // If a regular input is dirty (or missing), we're dirty.
- // Otherwise consider mtime.
- if ((*i)->dirty()) {
- EXPLAIN("%s is dirty", (*i)->path().c_str());
- dirty = true;
- } else {
- if (!most_recent_input || (*i)->mtime() > most_recent_input->mtime()) {
- most_recent_input = *i;
- }
- }
- }
- }
-
- // We may also be dirty due to output state: missing outputs, out of
- // date outputs, etc. Visit all outputs and determine whether they're dirty.
- if (!dirty)
- if (!RecomputeOutputsDirty(edge, most_recent_input, &dirty, err))
- return false;
-
- // Finally, visit each output and update their dirty state if necessary.
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (dirty)
- (*o)->MarkDirty();
- }
-
- // If an edge is dirty, its outputs are normally not ready. (It's
- // possible to be clean but still not be ready in the presence of
- // order-only inputs.)
- // But phony edges with no inputs have nothing to do, so are always
- // ready.
- if (dirty && !(edge->is_phony() && edge->inputs_.empty()))
- edge->outputs_ready_ = false;
-
- // Mark the edge as finished during this walk now that it will no longer
- // be in the call stack.
- edge->mark_ = Edge::VisitDone;
- assert(stack->back() == node);
- stack->pop_back();
-
- return true;
-}
-
-bool DependencyScan::VerifyDAG(Node* node, vector<Node*>* stack, string* err) {
- Edge* edge = node->in_edge();
- assert(edge != NULL);
-
- // If we have no temporary mark on the edge then we do not yet have a cycle.
- if (edge->mark_ != Edge::VisitInStack)
- return true;
-
- // We have this edge earlier in the call stack. Find it.
- vector<Node*>::iterator start = stack->begin();
- while (start != stack->end() && (*start)->in_edge() != edge)
- ++start;
- assert(start != stack->end());
-
- // Make the cycle clear by reporting its start as the node at its end
- // instead of some other output of the starting edge. For example,
- // running 'ninja b' on
- // build a b: cat c
- // build c: cat a
- // should report a -> c -> a instead of b -> c -> a.
- *start = node;
-
- // Construct the error message rejecting the cycle.
- *err = "dependency cycle: ";
- for (vector<Node*>::const_iterator i = start; i != stack->end(); ++i) {
- err->append((*i)->path());
- err->append(" -> ");
- }
- err->append((*start)->path());
-
- if ((start + 1) == stack->end() && edge->maybe_phonycycle_diagnostic()) {
- // The manifest parser would have filtered out the self-referencing
- // input if it were not configured to allow the error.
- err->append(" [-w phonycycle=err]");
- }
-
- return false;
-}
-
-bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input,
- bool* outputs_dirty, string* err) {
- for (vector<Node*>::iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o) {
- if (RecomputeOutputDirty(edge, most_recent_input, *o)) {
- *outputs_dirty = true;
- return true;
- }
- }
- return true;
-}
-
-bool DependencyScan::RecomputeOutputDirty(const Edge* edge,
- const Node* most_recent_input,
- Node* output) {
- if (edge->is_phony()) {
- // Phony edges don't write any output. Outputs are only dirty if
- // there are no inputs and we're missing the output.
- if (edge->inputs_.empty() && !output->exists()) {
- EXPLAIN("output %s of phony edge with no inputs doesn't exist",
- output->path().c_str());
- return true;
- }
-
- // Update the mtime with the newest input. Dependents can thus call mtime()
- // on the fake node and get the latest mtime of the dependencies
- if (most_recent_input) {
- output->UpdatePhonyMtime(most_recent_input->mtime());
- }
-
- // Phony edges are clean, nothing to do
- return false;
- }
-
- // Dirty if we're missing the output.
- if (!output->exists()) {
- EXPLAIN("output %s doesn't exist", output->path().c_str());
- return true;
- }
-
- BuildLog::LogEntry* entry = 0;
-
- // If this is a restat rule, we may have cleaned the output in a
- // previous run and stored the command start time in the build log.
- // We don't want to consider a restat rule's outputs as dirty unless
- // an input changed since the last run, so we'll skip checking the
- // output file's actual mtime and simply check the recorded mtime from
- // the log against the most recent input's mtime (see below)
- bool used_restat = false;
- if (edge->has_restat() && build_log() &&
- (entry = build_log()->LookupByOutput(output->path()))) {
- used_restat = true;
- }
-
- // Dirty if the output is older than the input.
- if (!used_restat && most_recent_input && output->mtime() < most_recent_input->mtime()) {
- EXPLAIN("output %s older than most recent input %s "
- "(%" PRId64 " vs %" PRId64 ")",
- output->path().c_str(),
- most_recent_input->path().c_str(),
- output->mtime(), most_recent_input->mtime());
- return true;
- }
-
- if (build_log()) {
- bool generator = edge->is_generator();
- if (entry || (entry = build_log()->LookupByOutput(output->path()))) {
- if (!generator && edge->GetCommandHash() != entry->command_hash) {
- // May also be dirty due to the command changing since the last build.
- // But if this is a generator rule, the command changing does not make us
- // dirty.
- EXPLAIN("command line changed for %s", output->path().c_str());
- return true;
- }
- if (most_recent_input && entry->mtime < most_recent_input->mtime()) {
- // May also be dirty due to the mtime in the log being older than the
- // mtime of the most recent input. This can occur even when the mtime
- // on disk is newer if a previous run wrote to the output file but
- // exited with an error or was interrupted. If this was a restat rule,
- // then we only check the recorded mtime against the most recent input
- // mtime and ignore the actual output's mtime above.
- EXPLAIN("recorded mtime of %s older than most recent input %s (%" PRId64 " vs %" PRId64 ")",
- output->path().c_str(), most_recent_input->path().c_str(),
- entry->mtime, most_recent_input->mtime());
- return true;
- }
- }
- if (!entry && !generator) {
- EXPLAIN("command line not found in log for %s", output->path().c_str());
- return true;
- }
- }
-
- return false;
-}
-
-bool DependencyScan::LoadDyndeps(Node* node, string* err) const {
- return dyndep_loader_.LoadDyndeps(node, err);
-}
-
-bool DependencyScan::LoadDyndeps(Node* node, DyndepFile* ddf,
- string* err) const {
- return dyndep_loader_.LoadDyndeps(node, ddf, err);
-}
-
-void Edge::SetRestat() {
- env_->AddBinding("restat", "1");
- has_restat_ = 1;
-}
-
-bool Edge::has_restat() const {
- if (has_restat_ < 0)
- has_restat_ = GetBindingBool("restat") ? 1 : 0;
- return has_restat_ != 0;
-}
-
-bool Edge::is_generator() const {
- if (is_generator_ < 0)
- is_generator_ = GetBindingBool("generator") ? 1 : 0;
- return is_generator_ != 0;
-}
-
-bool Edge::AllInputsReady() const {
- for (vector<Node*>::const_iterator i = inputs_.begin();
- i != inputs_.end(); ++i) {
- if ((*i)->in_edge() && !(*i)->in_edge()->outputs_ready())
- return false;
- }
- return true;
-}
-
-/// An Env for an Edge, providing $in and $out.
-struct EdgeEnv : public Env {
- enum EscapeKind { kShellEscape, kDoNotEscape };
-
- EdgeEnv(const Edge* const edge, const EscapeKind escape)
- : edge_(edge), escape_in_out_(escape), recursive_(false) {}
- virtual string LookupVariable(const string& var);
-
- /// Given a span of Nodes, construct a list of paths suitable for a command
- /// line.
- std::string MakePathList(const Node* const* span, size_t size, char sep) const;
-
- private:
- vector<string> lookups_;
- const Edge* const edge_;
- EscapeKind escape_in_out_;
- bool recursive_;
-};
-
-string EdgeEnv::LookupVariable(const string& var) {
- if (var == "in" || var == "in_newline") {
- int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ -
- edge_->order_only_deps_;
- return MakePathList(edge_->inputs_.data(), explicit_deps_count,
- var == "in" ? ' ' : '\n');
- } else if (var == "out") {
- int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_;
- return MakePathList(&edge_->outputs_[0], explicit_outs_count, ' ');
- }
-
- if (recursive_) {
- vector<string>::const_iterator it;
- if ((it = find(lookups_.begin(), lookups_.end(), var)) != lookups_.end()) {
- string cycle;
- for (; it != lookups_.end(); ++it)
- cycle.append(*it + " -> ");
- cycle.append(var);
- Fatal(("cycle in rule variables: " + cycle).c_str());
- }
- }
-
- // See notes on BindingEnv::LookupWithFallback.
- const EvalString* eval = edge_->rule_->GetBinding(var);
- bool record_varname = recursive_ && eval;
- if (record_varname)
- lookups_.push_back(var);
-
- // In practice, variables defined on rules never use another rule variable.
- // For performance, only start checking for cycles after the first lookup.
- recursive_ = true;
- std::string result = edge_->env_->LookupWithFallback(var, eval, this);
- if (record_varname)
- lookups_.pop_back();
- return result;
-}
-
-std::string EdgeEnv::MakePathList(const Node* const* const span,
- const size_t size, const char sep) const {
- string result;
- for (const Node* const* i = span; i != span + size; ++i) {
- if (!result.empty())
- result.push_back(sep);
- const string& path = (*i)->PathDecanonicalized();
- if (escape_in_out_ == kShellEscape) {
-#ifdef _WIN32
- GetWin32EscapedString(path, &result);
-#else
- GetShellEscapedString(path, &result);
-#endif
- } else {
- result.append(path);
- }
- }
- return result;
-}
-
-void Edge::CollectInputs(bool shell_escape,
- std::vector<std::string>* out) const {
- for (std::vector<Node*>::const_iterator it = inputs_.begin();
- it != inputs_.end(); ++it) {
- std::string path = (*it)->PathDecanonicalized();
- if (shell_escape) {
- std::string unescaped;
- unescaped.swap(path);
-#ifdef _WIN32
- GetWin32EscapedString(unescaped, &path);
-#else
- GetShellEscapedString(unescaped, &path);
-#endif
- }
-#if __cplusplus >= 201103L
- out->push_back(std::move(path));
-#else
- out->push_back(path);
-#endif
- }
-}
-
-std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
- string command = GetBinding("command");
- if (incl_rsp_file) {
- string rspfile_content = GetBinding("rspfile_content");
- if (!rspfile_content.empty())
- command += ";rspfile=" + rspfile_content;
- }
- return command;
-}
-
-std::string Edge::GetBinding(const std::string& key) const {
- EdgeEnv env(this, EdgeEnv::kShellEscape);
- return env.LookupVariable(key);
-}
-
-bool Edge::GetBindingBool(const string& key) const {
- return !GetBinding(key).empty();
-}
-
-uint64_t Edge::GetCommandHash() const {
- if (!has_command_hash_) {
- std::string command = EvaluateCommand(/* incl_rsp_file */ true);
- command_hash_ = BuildLog::LogEntry::HashCommand(command);
- has_command_hash_ = true;
- }
- return command_hash_;
-}
-
-string Edge::GetUnescapedDepfile() const {
- EdgeEnv env(this, EdgeEnv::kDoNotEscape);
- return env.LookupVariable("depfile");
-}
-
-string Edge::GetUnescapedDyndep() const {
- EdgeEnv env(this, EdgeEnv::kDoNotEscape);
- return env.LookupVariable("dyndep");
-}
-
-std::string Edge::GetUnescapedRspfile() const {
- EdgeEnv env(this, EdgeEnv::kDoNotEscape);
- return env.LookupVariable("rspfile");
-}
-
-void Edge::Dump(const char* prefix) const {
- printf("%s[ ", prefix);
- for (vector<Node*>::const_iterator i = inputs_.begin();
- i != inputs_.end() && *i != NULL; ++i) {
- printf("%s ", (*i)->path().c_str());
- }
- printf("--%s-> ", rule_->name().c_str());
- for (vector<Node*>::const_iterator i = outputs_.begin();
- i != outputs_.end() && *i != NULL; ++i) {
- printf("%s ", (*i)->path().c_str());
- }
- if (!validations_.empty()) {
- printf(" validations ");
- for (std::vector<Node*>::const_iterator i = validations_.begin();
- i != validations_.end() && *i != NULL; ++i) {
- printf("%s ", (*i)->path().c_str());
- }
- }
- if (pool_) {
- if (!pool_->name().empty()) {
- printf("(in pool '%s')", pool_->name().c_str());
- }
- } else {
- printf("(null pool?)");
- }
- printf("] 0x%p\n", this);
-}
-
-void Edge::UpdateDynamicImplicitDeps(size_t new_count, Node* const* new_deps) {
- size_t cur_count =
- static_cast<size_t>(implicit_deps_ - static_implicit_deps_);
- auto cur_deps = inputs_.end() - order_only_deps_ - cur_count;
-
- // Most of the time, the content of depfiles will not change between
- // command invocations, and thus |new_deps| will already be recorded
- // in this edge. Detect when this is the case and exit immediately.
- if (cur_count == new_count &&
- !memcmp(&(*cur_deps), new_deps, cur_count * sizeof(Node*))) {
- return;
- }
-
- // If there are no recorded deps, insert the new ones directly.
- // This happens the first time this function is called for a given
- // Edge instance.
- if (cur_count == 0) {
- auto it = inputs_.insert(cur_deps, new_count, nullptr);
- implicit_deps_ += new_count;
- for (size_t n = 0; n < new_count; ++n) {
- Node* node = new_deps[n];
- *it++ = node;
- node->AddOutEdge(this);
- }
- return;
- }
-
- // This is the general case where the content of a depfile changed since
- // the last build invocation. This is rare but can happen, for example
- // when depfile inputs have content-hash based names that change with
- // the content of another input.
- //
- // Because benchmarking shows that modifying the build graph is slow for
- // very large build plans, try to minimize changes by detecting the set of
- // nodes to remove from the current edge, then the set of new ones to add.
- std::set<Node*> cur_set(cur_deps, cur_deps + cur_count);
- std::set<Node*> new_set(new_deps, new_deps + new_count);
-
- // Remove all nodes that are no longer in |new_deps|.
- {
- auto it = cur_deps;
- for (size_t n = 0; n < cur_count;) {
- Node* node = *it;
- if (!new_set.count(node)) {
- node->RemoveOutEdge(this);
- it = inputs_.erase(it);
- implicit_deps_ -= 1;
- cur_count -= 1;
- } else {
- ++it;
- ++n;
- }
- }
- cur_deps = inputs_.end() - order_only_deps_ - cur_count;
- }
-
- // Add any edge in |new_deps| that is not in the current set.
- {
- auto it = cur_deps + cur_count;
- for (size_t n = 0; n < new_count; ++n) {
- Node* node = new_deps[n];
- if (cur_set.count(node))
- continue;
- implicit_deps_ += 1;
- it = inputs_.insert(it, node) + 1;
- node->AddOutEdge(this);
- }
- }
-}
-
-bool Edge::UpdateDynamicImplicitOutputs(size_t new_count, Node* const* new_outs,
- std::string* err) {
- size_t cur_count =
- static_cast<size_t>(implicit_outs_ - static_implicit_outs_);
- auto cur_outs = outputs_.end() - cur_count;
-
- // Most of the time, the content of dyndep files will not change.
- if (cur_count == new_count &&
- !memcmp(&(*cur_outs), new_outs, cur_count * sizeof(Node*))) {
- return true;
- }
-
- // If there are no recorded outputs, insert the new ones directly.
- if (cur_count == 0) {
- auto it = outputs_.insert(cur_outs, new_count, nullptr);
- implicit_outs_ += new_count;
- for (size_t n = 0; n < new_count; ++n) {
- Node* node = new_outs[n];
- if (node->in_edge()) {
- // This node already has an edge producing it.
- *err = "multiple rules generate " + node->path();
- return false;
- }
- *it++ = node;
- node->set_in_edge(this);
- }
- return true;
- }
-
- // This is the general case where the content of a dyndep file changed since
- // the last build invocation.
- std::set<Node*> cur_set(cur_outs, cur_outs + cur_count);
- std::set<Node*> new_set(new_outs, new_outs + new_count);
-
- // Remove all nodes that are no longer in |new_outs|.
- {
- auto it = cur_outs;
- for (size_t n = 0; n < cur_count;) {
- Node* node = *it;
- assert(node->in_edge() == this);
- if (!new_set.count(node)) {
- node->set_in_edge(nullptr);
- it = outputs_.erase(it);
- implicit_outs_ -= 1;
- cur_count -= 1;
- } else {
- ++it;
- ++n;
- }
- }
- cur_outs = outputs_.end() - cur_count;
- }
-
- // Add any edge in |new_outs| that is not in the current set.
- {
- auto it = cur_outs + cur_count;
- for (size_t n = 0; n < new_count; ++n) {
- Node* node = new_outs[n];
- if (cur_set.count(node))
- continue;
- if (node->in_edge()) {
- // This node already has an edge producing it.
- *err = "multiple rules generate " + node->path();
- return false;
- }
- node->set_in_edge(this);
- implicit_outs_ += 1;
- it = outputs_.insert(it, node) + 1;
- }
- }
- return true;
-}
-
-bool Edge::is_phony() const {
- return rule_->is_phony();
-}
-
-bool Edge::use_console() const {
- return pool()->is_console();
-}
-
-bool Edge::maybe_phonycycle_diagnostic() const {
- // CMake 2.8.12.x and 3.0.x produced self-referencing phony rules
- // of the form "build a: phony ... a ...". Restrict our
- // "phonycycle" diagnostic option to the form it used.
- return is_phony() && outputs_.size() == 1 && implicit_outs_ == 0 &&
- implicit_deps_ == 0;
-}
-
-// static
-string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
- string result = path;
-#ifdef _WIN32
- uint64_t mask = 1;
- for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) {
- if (slash_bits & mask)
- *c = '\\';
- c++;
- mask <<= 1;
- }
-#endif
- return result;
-}
-
-void Node::RemoveOutEdge(Edge* edge) {
- auto it = std::find(out_edges_.begin(), out_edges_.end(), edge);
- assert(it != out_edges_.end());
- // order is not important here, avoid O(n) deletion cost.
- *it = out_edges_.back();
- out_edges_.pop_back();
-}
-
-void Node::Dump(const char* prefix) const {
- printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ",
- prefix, path().c_str(), this,
- mtime(), exists() ? "" : " (:missing)",
- dirty() ? " dirty" : " clean");
- if (in_edge()) {
- in_edge()->Dump("in-edge: ");
- } else {
- printf("no in-edge\n");
- }
- printf(" out edges:\n");
- for (vector<Edge*>::const_iterator e = out_edges().begin();
- e != out_edges().end() && *e != NULL; ++e) {
- (*e)->Dump(" +- ");
- }
- if (!validation_out_edges().empty()) {
- printf(" validation out edges:\n");
- for (std::vector<Edge*>::const_iterator e = validation_out_edges().begin();
- e != validation_out_edges().end() && *e != NULL; ++e) {
- (*e)->Dump(" +- ");
- }
- }
-}
-
-bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) {
- string deps_type = edge->GetBinding("deps");
- if (!deps_type.empty())
- return LoadDepsFromLog(edge, err);
-
- string depfile = edge->GetUnescapedDepfile();
- if (!depfile.empty())
- return LoadDepFile(edge, depfile, err);
-
- // No deps to load.
- 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");
- // Read depfile content. Treat a missing depfile as empty.
- string content;
- switch (disk_interface_->ReadFile(path, &content, err)) {
- case DiskInterface::Okay:
- break;
- case DiskInterface::NotFound:
- err->clear();
- break;
- case DiskInterface::OtherError:
- *err = "loading '" + path + "': " + *err;
- return false;
- }
- // On a missing depfile: return false and empty *err.
- if (content.empty()) {
- EXPLAIN("depfile '%s' is missing", path.c_str());
- return false;
- }
-
- DepfileParser depfile(depfile_parser_options_
- ? *depfile_parser_options_
- : DepfileParserOptions());
- string depfile_err;
- if (!depfile.Parse(&content, &depfile_err)) {
- *err = path + ": " + depfile_err;
- return false;
- }
-
- if (depfile.outs_.empty()) {
- *err = path + ": no outputs declared";
- return false;
- }
-
- uint64_t unused;
- std::vector<StringPiece>::iterator primary_out = depfile.outs_.begin();
- CanonicalizePath(const_cast<char*>(primary_out->str_), &primary_out->len_,
- &unused);
-
- // Check that this depfile matches the edge's output, if not return false to
- // mark the edge as dirty.
- Node* first_output = edge->outputs_[0];
- StringPiece opath = StringPiece(first_output->path());
- if (opath != *primary_out) {
- EXPLAIN("expected depfile '%s' to mention '%s', got '%s'", path.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;
- }
- }
-
- return ProcessDepfileDeps(edge, &depfile.ins_, err);
-}
-
-bool ImplicitDepLoader::ProcessDepfileDeps(
- Edge* edge, std::vector<StringPiece>* depfile_ins, std::string* err) {
- std::vector<Node*> new_deps;
- new_deps.reserve(depfile_ins->size());
- for (StringPiece dep : *depfile_ins) {
- uint64_t slash_bits;
- CanonicalizePath(const_cast<char*>(dep.str_), &dep.len_, &slash_bits);
- new_deps.push_back(state_->GetNode(dep, slash_bits));
- }
- edge->UpdateDynamicImplicitDeps(new_deps.size(), new_deps.data());
- return true;
-}
-
-bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) {
- // NOTE: deps are only supported for single-target edges.
- Node* output = edge->outputs_[0];
- DepsLog::Deps* deps = deps_log_ ? deps_log_->GetDeps(output) : NULL;
- if (!deps) {
- EXPLAIN("deps for '%s' are missing", output->path().c_str());
- return false;
- }
-
- // Deps are invalid if the output is newer than the deps.
- if (output->mtime() > deps->mtime) {
- EXPLAIN("stored deps info out of date for '%s' (%" PRId64 " vs %" PRId64 ")",
- output->path().c_str(), deps->mtime, output->mtime());
- return false;
- }
-
- edge->UpdateDynamicImplicitDeps(static_cast<size_t>(deps->node_count),
- deps->nodes);
- return true;
-}
-
-vector<Node*>::iterator ImplicitDepLoader::PreallocateSpace(Edge* edge,
- int count) {
- edge->inputs_.insert(edge->inputs_.end() - edge->order_only_deps_,
- (size_t)count, 0);
- edge->implicit_deps_ += count;
- return edge->inputs_.end() - edge->order_only_deps_ - count;
-}
diff --git a/src/graph.h b/src/graph.h
deleted file mode 100644
index 2cda42b..0000000
--- a/src/graph.h
+++ /dev/null
@@ -1,450 +0,0 @@
-// 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.
-
-#ifndef NINJA_GRAPH_H_
-#define NINJA_GRAPH_H_
-
-#include <algorithm>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "dyndep.h"
-#include "eval_env.h"
-#include "timestamp.h"
-#include "util.h"
-
-struct BuildLog;
-struct DepfileParserOptions;
-struct DiskInterface;
-struct DepsLog;
-struct Edge;
-struct Node;
-struct Pool;
-struct State;
-
-/// Information about a node in the dependency graph: the file, whether
-/// it's dirty, mtime, etc.
-struct Node {
- Node(const std::string& path, uint64_t slash_bits)
- : path_(path),
- slash_bits_(slash_bits),
- mtime_(-1),
- exists_(ExistenceStatusUnknown),
- dirty_(false),
- dyndep_pending_(false),
- in_edge_(NULL),
- id_(-1) {}
-
- /// Return false on error.
- bool Stat(DiskInterface* disk_interface, std::string* err);
-
- /// If the file doesn't exist, set the mtime_ from its dependencies
- void UpdatePhonyMtime(TimeStamp mtime);
-
- /// Return false on error.
- bool StatIfNecessary(DiskInterface* disk_interface, std::string* err) {
- if (status_known())
- return true;
- return Stat(disk_interface, err);
- }
-
- /// Mark as not-yet-stat()ed and not dirty.
- void ResetState() {
- mtime_ = -1;
- exists_ = ExistenceStatusUnknown;
- dirty_ = false;
- dyndep_pending_ = false;
- }
-
- /// Mark the Node as already-stat()ed and missing.
- void MarkMissing() {
- if (mtime_ == -1) {
- mtime_ = 0;
- }
- exists_ = ExistenceStatusMissing;
- }
-
- bool exists() const {
- return exists_ == ExistenceStatusExists;
- }
-
- bool status_known() const {
- return exists_ != ExistenceStatusUnknown;
- }
-
- const std::string& path() const { return path_; }
- /// Get |path()| but use slash_bits to convert back to original slash styles.
- std::string PathDecanonicalized() const {
- return PathDecanonicalized(path_, 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_; }
-
- bool dirty() const { return dirty_; }
- void set_dirty(bool dirty) { dirty_ = dirty; }
- void MarkDirty() { dirty_ = true; }
-
- bool dyndep_pending() const { return dyndep_pending_; }
- void set_dyndep_pending(bool pending) { dyndep_pending_ = pending; }
-
- Edge* in_edge() const { return in_edge_; }
- void set_in_edge(Edge* edge) { in_edge_ = edge; }
-
- /// Indicates whether this node was generated from a depfile or dyndep file,
- /// instead of being a regular input or output from the Ninja manifest.
- bool generated_by_dep_loader() const { return generated_by_dep_loader_; }
-
- void set_generated_by_dep_loader(bool value) {
- generated_by_dep_loader_ = value;
- }
-
- int id() const { return id_; }
- void set_id(int id) { id_ = id; }
-
- const std::vector<Edge*>& out_edges() const { return out_edges_; }
- const std::vector<Edge*>& validation_out_edges() const { return validation_out_edges_; }
- void AddOutEdge(Edge* edge) { out_edges_.push_back(edge); }
- void RemoveOutEdge(Edge* edge);
- void AddValidationOutEdge(Edge* edge) { validation_out_edges_.push_back(edge); }
-
- void Dump(const char* prefix="") const;
-
-private:
- std::string path_;
-
- /// Set bits starting from lowest for backslashes that were normalized to
- /// forward slashes by CanonicalizePath. See |PathDecanonicalized|.
- uint64_t slash_bits_;
-
- /// Possible values of mtime_:
- /// -1: file hasn't been examined
- /// 0: we looked, and file doesn't exist
- /// >0: actual file's mtime, or the latest mtime of its dependencies if it doesn't exist
- TimeStamp mtime_;
-
- enum ExistenceStatus {
- /// The file hasn't been examined.
- ExistenceStatusUnknown,
- /// The file doesn't exist. mtime_ will be the latest mtime of its dependencies.
- ExistenceStatusMissing,
- /// The path is an actual file. mtime_ will be the file's mtime.
- ExistenceStatusExists
- };
- ExistenceStatus exists_;
-
- /// Dirty is true when the underlying file is out-of-date.
- /// But note that Edge::outputs_ready_ is also used in judging which
- /// edges to build.
- bool dirty_;
-
- /// Store whether dyndep information is expected from this node but
- /// has not yet been loaded.
- bool dyndep_pending_;
-
- /// Set to true when this node comes from a depfile, a dyndep file or the
- /// deps log. If it does not have a producing edge, the build should not
- /// abort if it is missing (as for regular source inputs). By default
- /// all nodes have this flag set to true, since the deps and build logs
- /// can be loaded before the manifest.
- bool generated_by_dep_loader_ = true;
-
- /// The Edge that produces this Node, or NULL when there is no
- /// known edge to produce it.
- Edge* in_edge_;
-
- /// All Edges that use this Node as an input.
- std::vector<Edge*> out_edges_;
-
- /// All Edges that use this Node as a validation.
- std::vector<Edge*> validation_out_edges_;
-
- /// A dense integer id for the node, assigned and used by DepsLog.
- int id_;
-};
-
-/// An edge in the dependency graph; links between Nodes using Rules.
-struct Edge {
- enum VisitMark {
- VisitNone,
- VisitInStack,
- VisitDone
- };
-
- Edge()
- : rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL), mark_(VisitNone),
- id_(0), outputs_ready_(false), deps_loaded_(false),
- deps_missing_(false), has_restat_(-1), is_generator_(-1),
- command_start_time_(0), static_implicit_deps_(0), implicit_deps_(0),
- order_only_deps_(0), static_implicit_outs_(0), implicit_outs_(0) {}
-
- /// Return true if all inputs' in-edges are ready.
- bool AllInputsReady() const;
-
- /// Expand all variables in a command and return it as a string.
- /// If incl_rsp_file is enabled, the string will also contain the
- /// full contents of a response file (if applicable)
- std::string EvaluateCommand(bool incl_rsp_file = false) const;
-
- /// Returns the shell-escaped value of |key|.
- std::string GetBinding(const std::string& key) const;
- bool GetBindingBool(const std::string& key) const;
-
- /// Like GetBinding("depfile"), but without shell escaping.
- std::string GetUnescapedDepfile() const;
- /// Like GetBinding("dyndep"), but without shell escaping.
- std::string GetUnescapedDyndep() const;
- /// Like GetBinding("rspfile"), but without shell escaping.
- std::string GetUnescapedRspfile() const;
-
- void Dump(const char* prefix="") const;
-
- /// Append all edge explicit inputs to |*out|. Possibly with shell escaping.
- void CollectInputs(bool shell_escape, std::vector<std::string>* out) const;
-
- /// Reset state of edge for next build / dependency scan.
- void ResetState() {
- outputs_ready_ = false;
- deps_missing_ = false;
- mark_ = VisitNone;
- }
-
- const Rule* rule_;
- Pool* pool_;
- std::vector<Node*> inputs_;
- std::vector<Node*> outputs_;
- std::vector<Node*> validations_;
- Node* dyndep_;
- BindingEnv* env_;
- VisitMark mark_;
- size_t id_;
- bool outputs_ready_;
-
- /// Set to true to indicate that this edge contains extra dependencies that
- /// were loaded from depfiles, the deps log, or dyndep files.
- bool deps_loaded_;
-
- bool deps_missing_;
-
- /// True for special phony edges that are created when loading extra
- /// dependencies from depfiles or the deps log that do not have a
- /// generating edge.
- bool generated_by_dep_loader_;
-
- /// Return the command hash for this edge.
- uint64_t GetCommandHash() const;
-
- /// True if command_hash_ is valid. Modified by const method.
- mutable bool has_command_hash_ = false;
- mutable uint64_t command_hash_ = 0;
-
- TimeStamp command_start_time_;
-
- const Rule& rule() const { return *rule_; }
- Pool* pool() const { return pool_; }
- int weight() const { return 1; }
- bool outputs_ready() const { return outputs_ready_; }
-
- /// Return true if "restat" is set for this edge.
- bool has_restat() const;
- mutable int has_restat_ = -1; // computed on-demand by const method.
-
- /// Return true if "generator" is set for this edge.
- bool is_generator() const;
- mutable int is_generator_ = -1; // computed on-demand by const method.
-
- void SetRestat();
-
- // There are four types of inputs.
- // 1) explicit deps, which show up as $in on the command line;
- // 2) static implicit deps, which the target depends on implicitly (e.g. C
- // headers), as they appear in the build plan, and changes in them cause
- // the target to be rebuild.
- // 3) dynamic implicit deps, which come from depfiles, the deps log or
- // dyndep files. They never appear in the build plan, and are inserted
- // into the build plan during incremental builds. They are otherwise
- // considered implicit dependencies.
- // 4) order-only deps, which are needed before the target builds but which
- // don't cause the target to rebuild.
- // These are stored in inputs_ in that order, and we keep counts of
- // #2, #3 and #4 when we need to access the various subsets.
- //
- // static_implicit_deps_
- // |
- // inputs_ [...|<----*----->| |<--order_only_deps_-->]
- // |<------implicit_deps_--->|
- //
- int static_implicit_deps_;
- int implicit_deps_;
- int order_only_deps_;
- bool is_implicit(size_t index) {
- return index >= inputs_.size() - order_only_deps_ - implicit_deps_ &&
- !is_order_only(index);
- }
- bool is_order_only(size_t index) {
- return index >= inputs_.size() - order_only_deps_;
- }
-
- // There are three types of outputs.
- // 1) explicit outs, which show up as $out on the command line;
- // 2) static implicit outs, that the target generates in the build plan, and
- // which are not listed as part of $out.
- // 3) dynamic implicit outs. which do not appear in the build plan, but
- // inserted into the build graph through dyndep files only.
- // These are stored in outputs_ in that order, and we keep a count of
- // #2 and #3 to use when we need to access the various subsets.
- //
- // static_implicit_outs_
- // |
- // outputs_ [.....|<-----*------>| ]
- // |<--------implicit_outs_-------->|
- //
- int static_implicit_outs_;
- int implicit_outs_;
- bool is_implicit_out(size_t index) const {
- return index >= outputs_.size() - implicit_outs_;
- }
-
- /// Update the set of dynamic implicit inputs for this edge. These can
- /// come from the deps log, a depfile, or a dyndep file. This method tries
- /// to minimize the changes to the build graph during incremental builds.
- void UpdateDynamicImplicitDeps(size_t new_count, Node* const* new_deps);
-
- /// Update the set of dynamic implicit outputs for this edge. These only
- /// come from dyndep files. On success, return true. On failure, which means
- /// that one of the new outputs already has a producing edge, set |*err|
- /// then return false.
- bool UpdateDynamicImplicitOutputs(size_t new_count, Node* const* new_outs,
- std::string* err);
-
- bool is_phony() const;
- bool use_console() const;
- 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.
-struct ImplicitDepLoader {
- ImplicitDepLoader(State* state, DepsLog* deps_log,
- DiskInterface* disk_interface,
- DepfileParserOptions const* depfile_parser_options)
- : state_(state), disk_interface_(disk_interface), deps_log_(deps_log),
- depfile_parser_options_(depfile_parser_options) {}
-
- /// 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, std::string* err);
-
- DepsLog* deps_log() const {
- return deps_log_;
- }
-
- protected:
- /// Process loaded implicit dependencies for \a edge and update the graph
- /// @return false on error (without filling \a err if info is just missing)
- virtual bool ProcessDepfileDeps(Edge* edge,
- std::vector<StringPiece>* depfile_ins,
- std::string* err);
-
- /// 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 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, std::string* err);
-
- /// Preallocate \a count spaces in the input array on \a edge, returning
- /// an iterator pointing at the first new space.
- std::vector<Node*>::iterator PreallocateSpace(Edge* edge, int count);
-
- State* state_;
- DiskInterface* disk_interface_;
- DepsLog* deps_log_;
- DepfileParserOptions const* depfile_parser_options_;
-};
-
-
-/// DependencyScan manages the process of scanning the files in a graph
-/// and updating the dirty/outputs_ready state of all the nodes and edges.
-struct DependencyScan {
- DependencyScan(State* state, BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface,
- DepfileParserOptions const* depfile_parser_options)
- : build_log_(build_log),
- disk_interface_(disk_interface),
- dep_loader_(state, deps_log, disk_interface, depfile_parser_options),
- dyndep_loader_(state, disk_interface) {}
-
- /// Update the |dirty_| state of the given nodes by transitively inspecting
- /// their input edges.
- /// Examine inputs, outputs, and command lines to judge whether an edge
- /// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_|
- /// state accordingly.
- /// Appends any validation nodes found to the nodes parameter.
- /// Returns false on failure.
- bool RecomputeDirty(Node* node, std::vector<Node*>* validation_nodes, 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, std::string* err);
-
- BuildLog* build_log() const {
- return build_log_;
- }
- void set_build_log(BuildLog* log) {
- build_log_ = log;
- }
-
- DepsLog* deps_log() const {
- return dep_loader_.deps_log();
- }
-
- /// Load a dyndep file from the given node's path and update the
- /// 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, std::string* err) const;
- bool LoadDyndeps(Node* node, DyndepFile* ddf, std::string* err) const;
-
- private:
- bool RecomputeNodeDirty(Node* node, std::vector<Node*>* stack,
- std::vector<Node*>* validation_nodes, 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(const Edge* edge, const Node* most_recent_input,
- Node* output);
-
- BuildLog* build_log_;
- DiskInterface* disk_interface_;
- ImplicitDepLoader dep_loader_;
- DyndepLoader dyndep_loader_;
-};
-
-#endif // NINJA_GRAPH_H_
diff --git a/src/graph_test.cc b/src/graph_test.cc
deleted file mode 100644
index 8a738ec..0000000
--- a/src/graph_test.cc
+++ /dev/null
@@ -1,1166 +0,0 @@
-// 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.
-
-#include "graph.h"
-#include "build.h"
-
-#include "test.h"
-
-using namespace std;
-
-struct GraphTest : public StateTestWithBuiltinRules {
- GraphTest() : scan_(&state_, NULL, NULL, &fs_, NULL) {}
-
- VirtualFileSystem fs_;
- DependencyScan scan_;
-};
-
-TEST_F(GraphTest, MissingImplicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in | implicit\n"));
- fs_.Create("in", "");
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- // A missing implicit dep *should* make the output dirty.
- // (In fact, a build will fail.)
- // This is a change from prior semantics of ninja.
- EXPECT_TRUE(GetNode("out")->dirty());
-}
-
-TEST_F(GraphTest, ModifiedImplicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in | implicit\n"));
- fs_.Create("in", "");
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("implicit", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- // A modified implicit dep should make the output dirty.
- EXPECT_TRUE(GetNode("out")->dirty());
-}
-
-TEST_F(GraphTest, FunkyMakefilePath) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule catdep\n"
-" depfile = $out.d\n"
-" command = cat $in > $out\n"
-"build out.o: catdep foo.cc\n"));
- fs_.Create("foo.cc", "");
- fs_.Create("out.o.d", "out.o: ./foo/../implicit.h\n");
- fs_.Create("out.o", "");
- fs_.Tick();
- fs_.Create("implicit.h", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
-
- // implicit.h has changed, though our depfile refers to it with a
- // non-canonical path; we should still find it.
- EXPECT_TRUE(GetNode("out.o")->dirty());
-}
-
-TEST_F(GraphTest, ExplicitImplicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule catdep\n"
-" depfile = $out.d\n"
-" command = cat $in > $out\n"
-"build implicit.h: cat data\n"
-"build out.o: catdep foo.cc || implicit.h\n"));
- fs_.Create("implicit.h", "");
- fs_.Create("foo.cc", "");
- fs_.Create("out.o.d", "out.o: implicit.h\n");
- fs_.Create("out.o", "");
- fs_.Tick();
- fs_.Create("data", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
-
- // We have both an implicit and an explicit dep on implicit.h.
- // The implicit dep should "win" (in the sense that it should cause
- // the output to be dirty).
- EXPECT_TRUE(GetNode("out.o")->dirty());
-}
-
-TEST_F(GraphTest, ImplicitOutputParse) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out | out.imp: cat in\n"));
-
- Edge* edge = GetNode("out")->in_edge();
- EXPECT_EQ(2, edge->outputs_.size());
- EXPECT_EQ("out", edge->outputs_[0]->path());
- EXPECT_EQ("out.imp", edge->outputs_[1]->path());
- EXPECT_EQ(1, edge->implicit_outs_);
- EXPECT_EQ(edge, GetNode("out.imp")->in_edge());
-}
-
-TEST_F(GraphTest, ImplicitOutputMissing) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out | out.imp: cat in\n"));
- fs_.Create("in", "");
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("out")->dirty());
- EXPECT_TRUE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(GraphTest, ImplicitOutputOutOfDate) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out | out.imp: cat in\n"));
- fs_.Create("out.imp", "");
- fs_.Tick();
- fs_.Create("in", "");
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("out")->dirty());
- EXPECT_TRUE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(GraphTest, ImplicitOutputOnlyParse) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build | out.imp: cat in\n"));
-
- Edge* edge = GetNode("out.imp")->in_edge();
- EXPECT_EQ(1, edge->outputs_.size());
- EXPECT_EQ("out.imp", edge->outputs_[0]->path());
- EXPECT_EQ(1, edge->implicit_outs_);
- EXPECT_EQ(edge, GetNode("out.imp")->in_edge());
-}
-
-TEST_F(GraphTest, ImplicitOutputOnlyMissing) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build | out.imp: cat in\n"));
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(GraphTest, ImplicitOutputOnlyOutOfDate) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build | out.imp: cat in\n"));
- fs_.Create("out.imp", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.imp"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("out.imp")->dirty());
-}
-
-TEST_F(GraphTest, PathWithCurrentDirectory) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule catdep\n"
-" depfile = $out.d\n"
-" command = cat $in > $out\n"
-"build ./out.o: catdep ./foo.cc\n"));
- fs_.Create("foo.cc", "");
- fs_.Create("out.o.d", "out.o: foo.cc\n");
- fs_.Create("out.o", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(GetNode("out.o")->dirty());
-}
-
-TEST_F(GraphTest, RootNodes) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out1: cat in1\n"
-"build mid1: cat in1\n"
-"build out2: cat mid1\n"
-"build out3 out4: cat mid1\n"));
-
- string err;
- vector<Node*> root_nodes = state_.RootNodes(&err);
- EXPECT_EQ(4u, root_nodes.size());
- for (size_t i = 0; i < root_nodes.size(); ++i) {
- string name = root_nodes[i]->path();
- EXPECT_EQ("out", name.substr(0, 3));
- }
-}
-
-TEST_F(GraphTest, CollectInputs) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
- &state_,
- "build out$ 1: cat in1 in2 in$ with$ space | implicit || order_only\n"));
-
- std::vector<std::string> inputs;
- Edge* edge = GetNode("out 1")->in_edge();
-
- // Test without shell escaping.
- inputs.clear();
- edge->CollectInputs(false, &inputs);
- EXPECT_EQ(5u, inputs.size());
- EXPECT_EQ("in1", inputs[0]);
- EXPECT_EQ("in2", inputs[1]);
- EXPECT_EQ("in with space", inputs[2]);
- EXPECT_EQ("implicit", inputs[3]);
- EXPECT_EQ("order_only", inputs[4]);
-
- // Test with shell escaping.
- inputs.clear();
- edge->CollectInputs(true, &inputs);
- EXPECT_EQ(5u, inputs.size());
- EXPECT_EQ("in1", inputs[0]);
- EXPECT_EQ("in2", inputs[1]);
-#ifdef _WIN32
- EXPECT_EQ("\"in with space\"", inputs[2]);
-#else
- EXPECT_EQ("'in with space'", inputs[2]);
-#endif
- EXPECT_EQ("implicit", inputs[3]);
- EXPECT_EQ("order_only", inputs[4]);
-}
-
-TEST_F(GraphTest, VarInOutPathEscaping) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build a$ b: cat no'space with$ space$$ no\"space2\n"));
-
- Edge* edge = GetNode("a b")->in_edge();
-#ifdef _WIN32
- EXPECT_EQ("cat no'space \"with space$\" \"no\\\"space2\" > \"a b\"",
- edge->EvaluateCommand());
-#else
- EXPECT_EQ("cat 'no'\\''space' 'with space$' 'no\"space2' > 'a b'",
- edge->EvaluateCommand());
-#endif
-}
-
-// Regression test for https://github.com/ninja-build/ninja/issues/380
-TEST_F(GraphTest, DepfileWithCanonicalizablePath) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule catdep\n"
-" depfile = $out.d\n"
-" command = cat $in > $out\n"
-"build ./out.o: catdep ./foo.cc\n"));
- fs_.Create("foo.cc", "");
- fs_.Create("out.o.d", "out.o: bar/../foo.cc\n");
- fs_.Create("out.o", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(GetNode("out.o")->dirty());
-}
-
-// Regression test for https://github.com/ninja-build/ninja/issues/404
-TEST_F(GraphTest, DepfileRemoved) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule catdep\n"
-" depfile = $out.d\n"
-" command = cat $in > $out\n"
-"build ./out.o: catdep ./foo.cc\n"));
- fs_.Create("foo.h", "");
- fs_.Create("foo.cc", "");
- fs_.Tick();
- fs_.Create("out.o.d", "out.o: foo.h\n");
- fs_.Create("out.o", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(GetNode("out.o")->dirty());
-
- fs_.RemoveFile("out.o.d");
-
- // Note that State::Reset() does not remove the recorded deps from the edge
- // so a new dependency scan will ingore the depfile being removed.
- state_.Reset();
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(GetNode("out.o")->dirty());
-
- // Set the edge's |deps_are_invalid_| flag to ensure the next dependency
- // scan will try to reload the depfile.
- state_.Reset();
- GetNode("out.o")->in_edge()->deps_loaded_ = false;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out.o"), NULL, &err));
- ASSERT_EQ("", err);
- EXPECT_TRUE(GetNode("out.o")->dirty());
-}
-
-// Regression test for https://fxbug.dev/135792
-TEST_F(GraphTest, DepfileOutputChanged) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
- "rule catdep\n"
- " depfile = $out.d\n"
- " command = echo OUT > $out\n"
- "build ./out: catdep\n"));
- // Create three files as if 'out' had been built by a command that generated
- // a depfile pointing to blobs/1, all three files have the same timestamp
- // so the corresponding Node should not be dirty.
- fs_.Create("out.d", "out: blobs/1\n");
- fs_.Create("out", "");
- fs_.Create("blobs/1", "1");
-
- Node* out_node = GetNode("out");
- Edge* out_edge = out_node->in_edge();
- EXPECT_TRUE(out_edge->inputs_.empty());
-
- // Perform a dependency scan, then verify that the node is not dirty, and
- // that blobs/1 was injected into the edge's inputs.
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(out_node->dirty());
- ASSERT_EQ(1u, out_edge->inputs_.size());
- EXPECT_EQ(out_edge->inputs_[0]->path(), "blobs/1");
-
- // Simulate a command that builds out while generating a depfile
- // pointing to a different file. All three files have the same timestamp.
- fs_.Tick();
- fs_.Create("blobs/2", "2");
- fs_.WriteFile("out.d", "out: blobs/2");
- fs_.WriteFile("out", "");
-
- state_.Reset();
- out_edge->deps_loaded_ = false;
-
- GetNode("out")->in_edge()->deps_loaded_ = false;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
- EXPECT_FALSE(out_node->dirty());
-
- // Verify that blobs/1 was removed from the list of inputs.
- EXPECT_EQ(1u, out_edge->inputs_.size());
- EXPECT_EQ(out_edge->inputs_[0]->path(), "blobs/2");
-}
-
-// Check that rule-level variables are in scope for eval.
-TEST_F(GraphTest, RuleVariablesInScope) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule r\n"
-" depfile = x\n"
-" command = depfile is $depfile\n"
-"build out: r in\n"));
- Edge* edge = GetNode("out")->in_edge();
- EXPECT_EQ("depfile is x", edge->EvaluateCommand());
-}
-
-// Check that build statements can override rule builtins like depfile.
-TEST_F(GraphTest, DepfileOverride) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule r\n"
-" depfile = x\n"
-" command = unused\n"
-"build out: r in\n"
-" depfile = y\n"));
- Edge* edge = GetNode("out")->in_edge();
- EXPECT_EQ("y", edge->GetBinding("depfile"));
-}
-
-// Check that overridden values show up in expansion of rule-level bindings.
-TEST_F(GraphTest, DepfileOverrideParent) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule r\n"
-" depfile = x\n"
-" command = depfile is $depfile\n"
-"build out: r in\n"
-" depfile = y\n"));
- Edge* edge = GetNode("out")->in_edge();
- EXPECT_EQ("depfile is y", edge->GetBinding("command"));
-}
-
-// Verify that building a nested phony rule prints "no work to do"
-TEST_F(GraphTest, NestedPhonyPrintsDone) {
- AssertParse(&state_,
-"build n1: phony \n"
-"build n2: phony n1\n"
- );
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("n2"), NULL, &err));
- ASSERT_EQ("", err);
-
- Plan plan_;
- EXPECT_TRUE(plan_.AddTarget(GetNode("n2"), &err));
- ASSERT_EQ("", err);
-
- EXPECT_EQ(0, plan_.command_edge_count());
- ASSERT_FALSE(plan_.more_to_do());
-}
-
-TEST_F(GraphTest, PhonySelfReferenceError) {
- ManifestParserOptions parser_opts;
- parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
- AssertParse(&state_,
-"build a: phony a\n",
- parser_opts);
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
- ASSERT_EQ("dependency cycle: a -> a [-w phonycycle=err]", err);
-}
-
-TEST_F(GraphTest, DependencyCycle) {
- AssertParse(&state_,
-"build out: cat mid\n"
-"build mid: cat in\n"
-"build in: cat pre\n"
-"build pre: cat out\n");
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
-}
-
-TEST_F(GraphTest, CycleInEdgesButNotInNodes1) {
- string err;
- AssertParse(&state_,
-"build a b: cat a\n");
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
- ASSERT_EQ("dependency cycle: a -> a", err);
-}
-
-TEST_F(GraphTest, CycleInEdgesButNotInNodes2) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build b a: cat a\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
- ASSERT_EQ("dependency cycle: a -> a", err);
-}
-
-TEST_F(GraphTest, CycleInEdgesButNotInNodes3) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build a b: cat c\n"
-"build c: cat a\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("b"), NULL, &err));
- ASSERT_EQ("dependency cycle: a -> c -> a", err);
-}
-
-TEST_F(GraphTest, CycleInEdgesButNotInNodes4) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build d: cat c\n"
-"build c: cat b\n"
-"build b: cat a\n"
-"build a e: cat d\n"
-"build f: cat e\n"));
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("f"), NULL, &err));
- ASSERT_EQ("dependency cycle: a -> d -> c -> b -> a", err);
-}
-
-// Verify that cycles in graphs with multiple outputs are handled correctly
-// in RecomputeDirty() and don't cause deps to be loaded multiple times.
-TEST_F(GraphTest, CycleWithLengthZeroFromDepfile) {
- AssertParse(&state_,
-"rule deprule\n"
-" depfile = dep.d\n"
-" command = unused\n"
-"build a b: deprule\n"
- );
- fs_.Create("dep.d", "a: b\n");
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
- ASSERT_EQ("dependency cycle: b -> b", err);
-
- // Despite the depfile causing edge to be a cycle (it has outputs a and b,
- // but the depfile also adds b as an input), the deps should have been loaded
- // only once:
- Edge* edge = GetNode("a")->in_edge();
- EXPECT_EQ(1, edge->inputs_.size());
- EXPECT_EQ("b", edge->inputs_[0]->path());
-}
-
-// Like CycleWithLengthZeroFromDepfile but with a higher cycle length.
-TEST_F(GraphTest, CycleWithLengthOneFromDepfile) {
- AssertParse(&state_,
-"rule deprule\n"
-" depfile = dep.d\n"
-" command = unused\n"
-"rule r\n"
-" command = unused\n"
-"build a b: deprule\n"
-"build c: r b\n"
- );
- fs_.Create("dep.d", "a: c\n");
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("a"), NULL, &err));
- ASSERT_EQ("dependency cycle: b -> c -> b", err);
-
- // Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
- // but c's in_edge has b as input but the depfile also adds |edge| as
- // output)), the deps should have been loaded only once:
- Edge* edge = GetNode("a")->in_edge();
- EXPECT_EQ(1, edge->inputs_.size());
- EXPECT_EQ("c", edge->inputs_[0]->path());
-}
-
-// Like CycleWithLengthOneFromDepfile but building a node one hop away from
-// the cycle.
-TEST_F(GraphTest, CycleWithLengthOneFromDepfileOneHopAway) {
- AssertParse(&state_,
-"rule deprule\n"
-" depfile = dep.d\n"
-" command = unused\n"
-"rule r\n"
-" command = unused\n"
-"build a b: deprule\n"
-"build c: r b\n"
-"build d: r a\n"
- );
- fs_.Create("dep.d", "a: c\n");
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("d"), NULL, &err));
- ASSERT_EQ("dependency cycle: b -> c -> b", err);
-
- // Despite the depfile causing edge to be a cycle (|edge| has outputs a and b,
- // but c's in_edge has b as input but the depfile also adds |edge| as
- // output)), the deps should have been loaded only once:
- Edge* edge = GetNode("a")->in_edge();
- EXPECT_EQ(1, edge->inputs_.size());
- EXPECT_EQ("c", edge->inputs_[0]->path());
-}
-
-#ifdef _WIN32
-TEST_F(GraphTest, Decanonicalize) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out\\out1: cat src\\in1\n"
-"build out\\out2/out3\\out4: cat mid1\n"
-"build out3 out4\\foo: cat mid1\n"));
-
- string err;
- vector<Node*> root_nodes = state_.RootNodes(&err);
- EXPECT_EQ(4u, root_nodes.size());
- EXPECT_EQ(root_nodes[0]->path(), "out/out1");
- EXPECT_EQ(root_nodes[1]->path(), "out/out2/out3/out4");
- EXPECT_EQ(root_nodes[2]->path(), "out3");
- EXPECT_EQ(root_nodes[3]->path(), "out4/foo");
- EXPECT_EQ(root_nodes[0]->PathDecanonicalized(), "out\\out1");
- EXPECT_EQ(root_nodes[1]->PathDecanonicalized(), "out\\out2/out3\\out4");
- EXPECT_EQ(root_nodes[2]->PathDecanonicalized(), "out3");
- EXPECT_EQ(root_nodes[3]->PathDecanonicalized(), "out4\\foo");
-}
-#endif
-
-TEST_F(GraphTest, DyndepLoadTrivial) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("", err);
- EXPECT_FALSE(GetNode("dd")->dyndep_pending());
-
- Edge* edge = GetNode("out")->in_edge();
- ASSERT_EQ(1u, edge->outputs_.size());
- EXPECT_EQ("out", edge->outputs_[0]->path());
- ASSERT_EQ(2u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("dd", edge->inputs_[1]->path());
- EXPECT_EQ(0u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-}
-
-TEST_F(GraphTest, DyndepLoadImplicit) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out1: r in || dd\n"
-" dyndep = dd\n"
-"build out2: r in\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out1: dyndep | out2\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("", err);
- EXPECT_FALSE(GetNode("dd")->dyndep_pending());
-
- Edge* edge = GetNode("out1")->in_edge();
- ASSERT_EQ(1u, edge->outputs_.size());
- EXPECT_EQ("out1", edge->outputs_[0]->path());
- ASSERT_EQ(3u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("out2", edge->inputs_[1]->path());
- EXPECT_EQ("dd", edge->inputs_[2]->path());
- EXPECT_EQ(1u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-}
-
-TEST_F(GraphTest, DyndepLoadMissingFile) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" dyndep = dd\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("loading 'dd': No such file or directory", err);
-}
-
-TEST_F(GraphTest, DyndepLoadMissingEntry) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("'out' not mentioned in its dyndep file 'dd'", err);
-}
-
-TEST_F(GraphTest, DyndepLoadExtraEntry) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" dyndep = dd\n"
-"build out2: r in || dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep\n"
-"build out2: dyndep\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("dyndep file 'dd' mentions output 'out2' whose build statement "
- "does not have a dyndep binding for the file", err);
-}
-
-TEST_F(GraphTest, DyndepLoadOutputWithMultipleRules1) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out1 | out-twice.imp: r in1\n"
-"build out2: r in2 || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out2 | out-twice.imp: dyndep\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("multiple rules generate out-twice.imp", err);
-}
-
-TEST_F(GraphTest, DyndepLoadOutputWithMultipleRules2) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out1: r in1 || dd1\n"
-" dyndep = dd1\n"
-"build out2: r in2 || dd2\n"
-" dyndep = dd2\n"
- );
- fs_.Create("dd1",
-"ninja_dyndep_version = 1\n"
-"build out1 | out-twice.imp: dyndep\n"
- );
- fs_.Create("dd2",
-"ninja_dyndep_version = 1\n"
-"build out2 | out-twice.imp: dyndep\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd1")->dyndep_pending());
- EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd1"), &err));
- EXPECT_EQ("", err);
- ASSERT_TRUE(GetNode("dd2")->dyndep_pending());
- EXPECT_FALSE(scan_.LoadDyndeps(GetNode("dd2"), &err));
- EXPECT_EQ("multiple rules generate out-twice.imp", err);
-}
-
-TEST_F(GraphTest, DyndepLoadMultiple) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out1: r in1 || dd\n"
-" dyndep = dd\n"
-"build out2: r in2 || dd\n"
-" dyndep = dd\n"
-"build outNot: r in3 || dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out1 | out1imp: dyndep | in1imp\n"
-"build out2: dyndep | in2imp\n"
-" restat = 1\n"
- );
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_TRUE(scan_.LoadDyndeps(GetNode("dd"), &err));
- EXPECT_EQ("", err);
- EXPECT_FALSE(GetNode("dd")->dyndep_pending());
-
- Edge* edge1 = GetNode("out1")->in_edge();
- ASSERT_EQ(2u, edge1->outputs_.size());
- EXPECT_EQ("out1", edge1->outputs_[0]->path());
- EXPECT_EQ("out1imp", edge1->outputs_[1]->path());
- EXPECT_EQ(1u, edge1->implicit_outs_);
- ASSERT_EQ(3u, edge1->inputs_.size());
- EXPECT_EQ("in1", edge1->inputs_[0]->path());
- EXPECT_EQ("in1imp", edge1->inputs_[1]->path());
- EXPECT_EQ("dd", edge1->inputs_[2]->path());
- EXPECT_EQ(1u, edge1->implicit_deps_);
- EXPECT_EQ(1u, edge1->order_only_deps_);
- EXPECT_FALSE(edge1->GetBindingBool("restat"));
- EXPECT_EQ(edge1, GetNode("out1imp")->in_edge());
- Node* in1imp = GetNode("in1imp");
- ASSERT_EQ(1u, in1imp->out_edges().size());
- EXPECT_EQ(edge1, in1imp->out_edges()[0]);
-
- Edge* edge2 = GetNode("out2")->in_edge();
- ASSERT_EQ(1u, edge2->outputs_.size());
- EXPECT_EQ("out2", edge2->outputs_[0]->path());
- EXPECT_EQ(0u, edge2->implicit_outs_);
- ASSERT_EQ(3u, edge2->inputs_.size());
- EXPECT_EQ("in2", edge2->inputs_[0]->path());
- EXPECT_EQ("in2imp", edge2->inputs_[1]->path());
- EXPECT_EQ("dd", edge2->inputs_[2]->path());
- EXPECT_EQ(1u, edge2->implicit_deps_);
- EXPECT_EQ(1u, edge2->order_only_deps_);
- EXPECT_TRUE(edge2->GetBindingBool("restat"));
- Node* in2imp = GetNode("in2imp");
- ASSERT_EQ(1u, in2imp->out_edges().size());
- EXPECT_EQ(edge2, in2imp->out_edges()[0]);
-}
-
-TEST_F(GraphTest, DyndepFileMissing) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("loading 'dd': No such file or directory", err);
-}
-
-TEST_F(GraphTest, DyndepFileError) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
- );
-
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("'out' not mentioned in its dyndep file 'dd'", err);
-}
-
-TEST_F(GraphTest, DyndepImplicitInputNewer) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | in\n"
- );
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(GetNode("in")->dirty());
- EXPECT_FALSE(GetNode("dd")->dirty());
-
- // "out" is dirty due to dyndep-specified implicit input
- EXPECT_TRUE(GetNode("out")->dirty());
-}
-
-TEST_F(GraphTest, DyndepFileReady) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build dd: r dd-in\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd-in", "");
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out: dyndep | in\n"
- );
- fs_.Create("out", "");
- fs_.Tick();
- fs_.Create("in", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(GetNode("in")->dirty());
- EXPECT_FALSE(GetNode("dd")->dirty());
- EXPECT_TRUE(GetNode("dd")->in_edge()->outputs_ready());
-
- // "out" is dirty due to dyndep-specified implicit input
- EXPECT_TRUE(GetNode("out")->dirty());
-}
-
-TEST_F(GraphTest, DyndepFileNotClean) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build dd: r dd-in\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd", "this-should-not-be-loaded");
- fs_.Tick();
- fs_.Create("dd-in", "");
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("dd")->dirty());
- EXPECT_FALSE(GetNode("dd")->in_edge()->outputs_ready());
-
- // "out" is clean but not ready since "dd" is not ready
- EXPECT_FALSE(GetNode("out")->dirty());
- EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
-}
-
-TEST_F(GraphTest, DyndepFileNotReady) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build tmp: r\n"
-"build dd: r dd-in || tmp\n"
-"build out: r || dd\n"
-" dyndep = dd\n"
- );
- fs_.Create("dd", "this-should-not-be-loaded");
- fs_.Create("dd-in", "");
- fs_.Tick();
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_FALSE(GetNode("dd")->dirty());
- EXPECT_FALSE(GetNode("dd")->in_edge()->outputs_ready());
- EXPECT_FALSE(GetNode("out")->dirty());
- EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
-}
-
-TEST_F(GraphTest, DyndepFileSecondNotReady) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build dd1: r dd1-in\n"
-"build dd2-in: r || dd1\n"
-" dyndep = dd1\n"
-"build dd2: r dd2-in\n"
-"build out: r || dd2\n"
-" dyndep = dd2\n"
- );
- fs_.Create("dd1", "");
- fs_.Create("dd2", "");
- fs_.Create("dd2-in", "");
- fs_.Tick();
- fs_.Create("dd1-in", "");
- fs_.Create("out", "");
-
- string err;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- ASSERT_EQ("", err);
-
- EXPECT_TRUE(GetNode("dd1")->dirty());
- EXPECT_FALSE(GetNode("dd1")->in_edge()->outputs_ready());
- EXPECT_FALSE(GetNode("dd2")->dirty());
- EXPECT_FALSE(GetNode("dd2")->in_edge()->outputs_ready());
- EXPECT_FALSE(GetNode("out")->dirty());
- EXPECT_FALSE(GetNode("out")->in_edge()->outputs_ready());
-}
-
-TEST_F(GraphTest, DyndepFileCircular) {
- AssertParse(&state_,
-"rule r\n"
-" command = unused\n"
-"build out: r in || dd\n"
-" depfile = out.d\n"
-" dyndep = dd\n"
-"build in: r circ\n"
- );
- fs_.Create("out.d", "out: inimp\n");
- fs_.Create("dd",
-"ninja_dyndep_version = 1\n"
-"build out | circ: dyndep\n"
- );
- fs_.Create("out", "");
-
- Edge* edge = GetNode("out")->in_edge();
- string err;
- EXPECT_FALSE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
- EXPECT_EQ("dependency cycle: circ -> in -> circ", err);
-
- // Verify that "out.d" was loaded exactly once despite
- // circular reference discovered from dyndep file.
- ASSERT_EQ(3u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("inimp", edge->inputs_[1]->path());
- EXPECT_EQ("dd", edge->inputs_[2]->path());
- EXPECT_EQ(1u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
-}
-
-TEST_F(GraphTest, DyndepLoadIncremental) {
- AssertParse(&state_,
- "rule r\n"
- " command = unused\n"
- "build out: r in || dd\n"
- " dyndep = dd\n");
-
- // First version of the dyndep file adds one implicit output, and
- // one implicit input.
- fs_.Create("dd",
- "ninja_dyndep_version = 1\n"
- "build out | implicit_out: dyndep | implicit1\n");
-
- string err;
- ASSERT_TRUE(GetNode("dd")->dyndep_pending());
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), NULL, &err));
-
- EXPECT_EQ("", err);
- Node* out_node = GetNode("out");
- Edge* edge = out_node->in_edge();
-
- // Check that implicit_out was inserted as a dynamic implicit output.
- ASSERT_EQ(2u, edge->outputs_.size());
- EXPECT_EQ(0, edge->static_implicit_outs_);
- EXPECT_EQ(1, edge->implicit_outs_);
- EXPECT_EQ("out", edge->outputs_[0]->path());
- EXPECT_EQ("implicit_out", edge->outputs_[1]->path());
-
- // Check that implicit1 was inserted as a dynamic implicit input,
- // i.e. before order-only deps in the inputs_ array.
- ASSERT_EQ(3u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("implicit1", edge->inputs_[1]->path());
- EXPECT_EQ("dd", edge->inputs_[2]->path());
- EXPECT_EQ(0u, edge->static_implicit_deps_);
- EXPECT_EQ(1u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-
- // Perform a second scan, forcing a reload of the dyndep information.
- // and verify that nothing changed.
- state_.Reset();
- edge->deps_loaded_ = false;
- edge->dyndep_->set_dyndep_pending(true);
- EXPECT_TRUE(scan_.RecomputeDirty(out_node, NULL, &err));
-
- EXPECT_EQ("", err);
- ASSERT_EQ(2u, edge->outputs_.size());
- EXPECT_EQ(0, edge->static_implicit_outs_);
- EXPECT_EQ(1, edge->implicit_outs_);
- EXPECT_EQ("out", edge->outputs_[0]->path());
- EXPECT_EQ("implicit_out", edge->outputs_[1]->path());
-
- ASSERT_EQ(3u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("implicit1", edge->inputs_[1]->path());
- EXPECT_EQ("dd", edge->inputs_[2]->path());
- EXPECT_EQ(0u, edge->static_implicit_deps_);
- EXPECT_EQ(1u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-
- // Now modify the dyndep file content to add a new implicit output,
- // and two new implicit inputs. Note that some of the new files are listed
- // _before_ the current ones.
- fs_.Tick();
- fs_.WriteFile("dd",
- "ninja_dyndep_version = 1\n"
- "build out | implicit_out2 implicit_out: dyndep | implicit2 "
- "implicit1 implicit3\n");
-
- state_.Reset();
- edge->deps_loaded_ = false;
- edge->dyndep_->set_dyndep_pending(true);
- EXPECT_TRUE(scan_.RecomputeDirty(out_node, NULL, &err));
-
- EXPECT_EQ("", err);
- // Verify that the new implicit output was inserted _after_ the first one
- // even though it appeared before it in the dyndep file.
- ASSERT_EQ(3u, edge->outputs_.size());
- EXPECT_EQ(0, edge->static_implicit_outs_);
- EXPECT_EQ(2, edge->implicit_outs_);
- EXPECT_EQ("out", edge->outputs_[0]->path());
- EXPECT_EQ("implicit_out", edge->outputs_[1]->path());
- EXPECT_EQ("implicit_out2", edge->outputs_[2]->path());
-
- // Verify that the new implicits input were inserted _after_ the first one
- // even though some appeared before it in the dyndep file.
- ASSERT_EQ(5u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("implicit1", edge->inputs_[1]->path());
- EXPECT_EQ("implicit2", edge->inputs_[2]->path());
- EXPECT_EQ("implicit3", edge->inputs_[3]->path());
- EXPECT_EQ("dd", edge->inputs_[4]->path());
- EXPECT_EQ(0u, edge->static_implicit_deps_);
- EXPECT_EQ(3u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-
- // Modify the dyndep file again to remove all implicit outputs, and the
- // two implicit inputs.
- fs_.Tick();
- fs_.WriteFile("dd",
- "ninja_dyndep_version = 1\n"
- "build out: dyndep | implicit2\n");
-
- state_.Reset();
- edge->deps_loaded_ = false;
- edge->dyndep_->set_dyndep_pending(true);
- EXPECT_TRUE(scan_.RecomputeDirty(out_node, NULL, &err));
-
- EXPECT_EQ("", err);
- // Verify that all dynamic implicit outputs were removed.
- ASSERT_EQ(1u, edge->outputs_.size());
- EXPECT_EQ(0, edge->static_implicit_outs_);
- EXPECT_EQ(0, edge->implicit_outs_);
- EXPECT_EQ("out", edge->outputs_[0]->path());
-
- // Verify that only one dynamic implicit input remains.
- ASSERT_EQ(3u, edge->inputs_.size());
- EXPECT_EQ("in", edge->inputs_[0]->path());
- EXPECT_EQ("implicit2", edge->inputs_[1]->path());
- EXPECT_EQ("dd", edge->inputs_[2]->path());
- EXPECT_EQ(0u, edge->static_implicit_deps_);
- EXPECT_EQ(1u, edge->implicit_deps_);
- EXPECT_EQ(1u, edge->order_only_deps_);
- EXPECT_FALSE(edge->GetBindingBool("restat"));
-}
-
-TEST_F(GraphTest, Validation) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"build out: cat in |@ validate\n"
-"build validate: cat in\n"));
-
- fs_.Create("in", "");
- string err;
- std::vector<Node*> validation_nodes;
- EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &validation_nodes, &err));
- ASSERT_EQ("", err);
-
- ASSERT_EQ(validation_nodes.size(), 1);
- EXPECT_EQ(validation_nodes[0]->path(), "validate");
-
- EXPECT_TRUE(GetNode("out")->dirty());
- EXPECT_TRUE(GetNode("validate")->dirty());
-}
-
-// Check that phony's dependencies' mtimes are propagated.
-TEST_F(GraphTest, PhonyDepsMtimes) {
- string err;
- ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
-"rule touch\n"
-" command = touch $out\n"
-"build in_ph: phony in1\n"
-"build out1: touch in_ph\n"
-));
- fs_.Create("in1", "");
- fs_.Create("out1", "");
- Node* out1 = GetNode("out1");
- Node* in1 = GetNode("in1");
-
- EXPECT_TRUE(scan_.RecomputeDirty(out1, NULL, &err));
- EXPECT_TRUE(!out1->dirty());
-
- // Get the mtime of out1
- ASSERT_TRUE(in1->Stat(&fs_, &err));
- ASSERT_TRUE(out1->Stat(&fs_, &err));
- TimeStamp out1Mtime1 = out1->mtime();
- TimeStamp in1Mtime1 = in1->mtime();
-
- // Touch in1. This should cause out1 to be dirty
- state_.Reset();
- fs_.Tick();
- fs_.Create("in1", "");
-
- ASSERT_TRUE(in1->Stat(&fs_, &err));
- EXPECT_GT(in1->mtime(), in1Mtime1);
-
- EXPECT_TRUE(scan_.RecomputeDirty(out1, NULL, &err));
- EXPECT_GT(in1->mtime(), in1Mtime1);
- EXPECT_EQ(out1->mtime(), out1Mtime1);
- EXPECT_TRUE(out1->dirty());
-}
diff --git a/src/graphviz.cc b/src/graphviz.cc
deleted file mode 100644
index 37b7108..0000000
--- a/src/graphviz.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// 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.
-
-#include "graphviz.h"
-
-#include <stdio.h>
-#include <algorithm>
-
-#include "dyndep.h"
-#include "graph.h"
-
-using namespace std;
-
-void GraphViz::AddTarget(Node* node) {
- if (visited_nodes_.find(node) != visited_nodes_.end())
- return;
-
- string pathstr = node->path();
- replace(pathstr.begin(), pathstr.end(), '\\', '/');
- printf("\"%p\" [label=\"%s\"]\n", node, pathstr.c_str());
- visited_nodes_.insert(node);
-
- Edge* edge = node->in_edge();
-
- if (!edge) {
- // Leaf node.
- // Draw as a rect?
- return;
- }
-
- if (visited_edges_.find(edge) != visited_edges_.end())
- return;
- visited_edges_.insert(edge);
-
- if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
- std::string err;
- if (!dyndep_loader_.LoadDyndeps(edge->dyndep_, &err)) {
- Warning("%s\n", err.c_str());
- }
- }
-
- if (edge->inputs_.size() == 1 && edge->outputs_.size() == 1) {
- // Can draw simply.
- // Note extra space before label text -- this is cosmetic and feels
- // like a graphviz bug.
- printf("\"%p\" -> \"%p\" [label=\" %s\"]\n",
- edge->inputs_[0], edge->outputs_[0], edge->rule_->name().c_str());
- } else {
- printf("\"%p\" [label=\"%s\", shape=ellipse]\n",
- edge, edge->rule_->name().c_str());
- for (vector<Node*>::iterator out = edge->outputs_.begin();
- out != edge->outputs_.end(); ++out) {
- printf("\"%p\" -> \"%p\"\n", edge, *out);
- }
- for (vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in) {
- const char* order_only = "";
- if (edge->is_order_only(in - edge->inputs_.begin()))
- order_only = " style=dotted";
- printf("\"%p\" -> \"%p\" [arrowhead=none%s]\n", (*in), edge, order_only);
- }
- }
-
- for (vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in) {
- AddTarget(*in);
- }
-}
-
-void GraphViz::Start() {
- printf("digraph ninja {\n");
- printf("rankdir=\"LR\"\n");
- printf("node [fontsize=10, shape=box, height=0.25]\n");
- printf("edge [fontsize=10]\n");
-}
-
-void GraphViz::Finish() {
- printf("}\n");
-}
diff --git a/src/graphviz.h b/src/graphviz.h
deleted file mode 100644
index 3a3282e..0000000
--- a/src/graphviz.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.
-
-#ifndef NINJA_GRAPHVIZ_H_
-#define NINJA_GRAPHVIZ_H_
-
-#include <set>
-
-#include "dyndep.h"
-#include "graph.h"
-
-struct DiskInterface;
-struct Node;
-struct Edge;
-struct State;
-
-/// Runs the process of creating GraphViz .dot file output.
-struct GraphViz {
- GraphViz(State* state, DiskInterface* disk_interface)
- : dyndep_loader_(state, disk_interface) {}
- void Start();
- void AddTarget(Node* node);
- void Finish();
-
- DyndepLoader dyndep_loader_;
- std::set<Node*> visited_nodes_;
- EdgeSet visited_edges_;
-};
-
-#endif // NINJA_GRAPHVIZ_H_
diff --git a/src/hash_collision_bench.cc b/src/hash_collision_bench.cc
deleted file mode 100644
index 8f37ed0..0000000
--- a/src/hash_collision_bench.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2012 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 "build_log.h"
-
-#include <algorithm>
-
-#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+1];
- for (int i = 0; i < len; ++i)
- (*s)[i] = (char)random(32, 127);
- (*s)[len] = '\0';
-}
-
-int main() {
- const int N = 20 * 1000 * 1000;
-
- // Leak these, else 10% of the runtime is spent destroying strings.
- char** commands = new char*[N];
- pair<uint64_t, int>* hashes = new pair<uint64_t, int>[N];
-
- srand((int)time(NULL));
-
- for (int i = 0; i < N; ++i) {
- RandomCommand(&commands[i]);
- hashes[i] = make_pair(BuildLog::LogEntry::HashCommand(commands[i]), i);
- }
-
- sort(hashes, hashes + N);
-
- int collision_count = 0;
- for (int i = 1; i < N; ++i) {
- if (hashes[i - 1].first == hashes[i].first) {
- if (strcmp(commands[hashes[i - 1].second],
- commands[hashes[i].second]) != 0) {
- printf("collision!\n string 1: '%s'\n string 2: '%s'\n",
- commands[hashes[i - 1].second],
- commands[hashes[i].second]);
- collision_count++;
- }
- }
- }
- printf("\n\n%d collisions after %d runs\n", collision_count, N);
-}
diff --git a/src/hash_map.h b/src/hash_map.h
deleted file mode 100644
index 4353609..0000000
--- a/src/hash_map.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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.
-
-#ifndef NINJA_MAP_H_
-#define NINJA_MAP_H_
-
-#include <algorithm>
-#include <string.h>
-#include "string_piece.h"
-#include "util.h"
-
-// MurmurHash2, by Austin Appleby
-static inline
-unsigned int MurmurHash2(const void* key, size_t len) {
- static const unsigned int seed = 0xDECAFBAD;
- const unsigned int m = 0x5bd1e995;
- const int r = 24;
- unsigned int h = seed ^ len;
- const unsigned char* data = (const unsigned char*)key;
- while (len >= 4) {
- unsigned int k;
- memcpy(&k, data, sizeof k);
- k *= m;
- k ^= k >> r;
- k *= m;
- h *= m;
- h ^= k;
- data += 4;
- len -= 4;
- }
- switch (len) {
- case 3: h ^= data[2] << 16;
- NINJA_FALLTHROUGH;
- case 2: h ^= data[1] << 8;
- NINJA_FALLTHROUGH;
- case 1: h ^= data[0];
- h *= m;
- };
- h ^= h >> 13;
- h *= m;
- h ^= h >> 15;
- return h;
-}
-
-#include <unordered_map>
-
-namespace std {
-template<>
-struct hash<StringPiece> {
- typedef StringPiece argument_type;
- typedef size_t result_type;
-
- size_t operator()(StringPiece key) const {
- return MurmurHash2(key.str_, key.len_);
- }
-};
-}
-
-/// A template for hash_maps keyed by a StringPiece whose string is
-/// owned externally (typically by the values). Use like:
-/// ExternalStringHash<Foo*>::Type foos; to make foos into a hash
-/// mapping StringPiece => Foo*.
-template<typename V>
-struct ExternalStringHashMap {
- typedef std::unordered_map<StringPiece, V> Type;
-};
-
-#endif // NINJA_MAP_H_
diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc
deleted file mode 100644
index 081e364..0000000
--- a/src/includes_normalize-win32.cc
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2012 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 "includes_normalize.h"
-
-#include "string_piece.h"
-#include "string_piece_util.h"
-#include "util.h"
-
-#include <algorithm>
-#include <iterator>
-#include <sstream>
-
-#include <windows.h>
-
-using namespace std;
-
-namespace {
-
-bool InternalGetFullPathName(const StringPiece& file_name, char* buffer,
- size_t buffer_length, string *err) {
- DWORD result_size = GetFullPathNameA(file_name.AsString().c_str(),
- buffer_length, buffer, NULL);
- if (result_size == 0) {
- *err = "GetFullPathNameA(" + file_name.AsString() + "): " +
- GetLastErrorString();
- return false;
- } else if (result_size > buffer_length) {
- *err = "path too long";
- return false;
- }
- return true;
-}
-
-bool IsPathSeparator(char c) {
- return c == '/' || c == '\\';
-}
-
-// Return true if paths a and b are on the same windows drive.
-// Return false if this function cannot check
-// whether or not on the same windows drive.
-bool SameDriveFast(StringPiece a, StringPiece b) {
- if (a.size() < 3 || b.size() < 3) {
- return false;
- }
-
- if (!islatinalpha(a[0]) || !islatinalpha(b[0])) {
- return false;
- }
-
- if (ToLowerASCII(a[0]) != ToLowerASCII(b[0])) {
- return false;
- }
-
- if (a[1] != ':' || b[1] != ':') {
- return false;
- }
-
- return IsPathSeparator(a[2]) && IsPathSeparator(b[2]);
-}
-
-// Return true if paths a and b are on the same Windows drive.
-bool SameDrive(StringPiece a, StringPiece b, string* err) {
- if (SameDriveFast(a, b)) {
- return true;
- }
-
- char a_absolute[_MAX_PATH];
- char b_absolute[_MAX_PATH];
- if (!InternalGetFullPathName(a, a_absolute, sizeof(a_absolute), err)) {
- return false;
- }
- if (!InternalGetFullPathName(b, b_absolute, sizeof(b_absolute), err)) {
- return false;
- }
- char a_drive[_MAX_DIR];
- char b_drive[_MAX_DIR];
- _splitpath(a_absolute, a_drive, NULL, NULL, NULL);
- _splitpath(b_absolute, b_drive, NULL, NULL, NULL);
- return _stricmp(a_drive, b_drive) == 0;
-}
-
-// Check path |s| is FullPath style returned by GetFullPathName.
-// This ignores difference of path separator.
-// This is used not to call very slow GetFullPathName API.
-bool IsFullPathName(StringPiece s) {
- if (s.size() < 3 ||
- !islatinalpha(s[0]) ||
- s[1] != ':' ||
- !IsPathSeparator(s[2])) {
- return false;
- }
-
- // Check "." or ".." is contained in path.
- for (size_t i = 2; i < s.size(); ++i) {
- if (!IsPathSeparator(s[i])) {
- continue;
- }
-
- // Check ".".
- if (i + 1 < s.size() && s[i+1] == '.' &&
- (i + 2 >= s.size() || IsPathSeparator(s[i+2]))) {
- return false;
- }
-
- // Check "..".
- if (i + 2 < s.size() && s[i+1] == '.' && s[i+2] == '.' &&
- (i + 3 >= s.size() || IsPathSeparator(s[i+3]))) {
- return false;
- }
- }
-
- return true;
-}
-
-} // anonymous namespace
-
-IncludesNormalize::IncludesNormalize(const string& relative_to) {
- string err;
- relative_to_ = AbsPath(relative_to, &err);
- if (!err.empty()) {
- Fatal("Initializing IncludesNormalize(): %s", err.c_str());
- }
- split_relative_to_ = SplitStringPiece(relative_to_, '/');
-}
-
-string IncludesNormalize::AbsPath(StringPiece s, string* err) {
- if (IsFullPathName(s)) {
- string result = s.AsString();
- for (size_t i = 0; i < result.size(); ++i) {
- if (result[i] == '\\') {
- result[i] = '/';
- }
- }
- return result;
- }
-
- char result[_MAX_PATH];
- if (!InternalGetFullPathName(s, result, sizeof(result), err)) {
- return "";
- }
- for (char* c = result; *c; ++c)
- if (*c == '\\')
- *c = '/';
- return result;
-}
-
-string IncludesNormalize::Relativize(
- StringPiece path, const vector<StringPiece>& start_list, string* err) {
- string abs_path = AbsPath(path, err);
- if (!err->empty())
- return "";
- vector<StringPiece> path_list = SplitStringPiece(abs_path, '/');
- int i;
- for (i = 0; i < static_cast<int>(min(start_list.size(), path_list.size()));
- ++i) {
- if (!EqualsCaseInsensitiveASCII(start_list[i], path_list[i])) {
- break;
- }
- }
-
- vector<StringPiece> rel_list;
- rel_list.reserve(start_list.size() - i + path_list.size() - i);
- for (int j = 0; j < static_cast<int>(start_list.size() - i); ++j)
- rel_list.push_back("..");
- for (int j = i; j < static_cast<int>(path_list.size()); ++j)
- rel_list.push_back(path_list[j]);
- if (rel_list.size() == 0)
- return ".";
- return JoinStringPiece(rel_list, '/');
-}
-
-bool IncludesNormalize::Normalize(const string& input,
- string* result, string* err) const {
- char copy[_MAX_PATH + 1];
- size_t len = input.size();
- if (len > _MAX_PATH) {
- *err = "path too long";
- return false;
- }
- strncpy(copy, input.c_str(), input.size() + 1);
- uint64_t slash_bits;
- CanonicalizePath(copy, &len, &slash_bits);
- StringPiece partially_fixed(copy, len);
- string abs_input = AbsPath(partially_fixed, err);
- if (!err->empty())
- return false;
-
- if (!SameDrive(abs_input, relative_to_, err)) {
- if (!err->empty())
- return false;
- *result = partially_fixed.AsString();
- return true;
- }
- *result = Relativize(abs_input, split_relative_to_, err);
- if (!err->empty())
- return false;
- return true;
-}
diff --git a/src/includes_normalize.h b/src/includes_normalize.h
deleted file mode 100644
index 7d50556..0000000
--- a/src/includes_normalize.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2012 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 <string>
-#include <vector>
-
-struct StringPiece;
-
-/// Utility functions for normalizing include paths on Windows.
-/// TODO: this likely duplicates functionality of CanonicalizePath; refactor.
-struct IncludesNormalize {
- /// Normalize path relative to |relative_to|.
- IncludesNormalize(const std::string& relative_to);
-
- // Internal utilities made available for testing, maybe useful otherwise.
- 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 std::string& input, std::string* result,
- std::string* err) const;
-
- private:
- std::string relative_to_;
- std::vector<StringPiece> split_relative_to_;
-};
diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc
deleted file mode 100644
index 5d99396..0000000
--- a/src/includes_normalize_test.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2012 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 "includes_normalize.h"
-
-#include <algorithm>
-
-#include <direct.h>
-
-#include "string_piece_util.h"
-#include "test.h"
-#include "util.h"
-
-using namespace std;
-
-namespace {
-
-string GetCurDir() {
- char buf[_MAX_PATH];
- _getcwd(buf, sizeof(buf));
- vector<StringPiece> parts = SplitStringPiece(buf, '\\');
- return parts[parts.size() - 1].AsString();
-}
-
-string NormalizeAndCheckNoError(const string& input) {
- string result, err;
- IncludesNormalize normalizer(".");
- EXPECT_TRUE(normalizer.Normalize(input, &result, &err));
- EXPECT_EQ("", err);
- return result;
-}
-
-string NormalizeRelativeAndCheckNoError(const string& input,
- const string& relative_to) {
- string result, err;
- IncludesNormalize normalizer(relative_to);
- EXPECT_TRUE(normalizer.Normalize(input, &result, &err));
- EXPECT_EQ("", err);
- return result;
-}
-
-} // namespace
-
-TEST(IncludesNormalize, Simple) {
- EXPECT_EQ("b", NormalizeAndCheckNoError("a\\..\\b"));
- EXPECT_EQ("b", NormalizeAndCheckNoError("a\\../b"));
- EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\.\\b"));
- EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b"));
-}
-
-TEST(IncludesNormalize, WithRelative) {
- string err;
- string currentdir = GetCurDir();
- EXPECT_EQ("c", NormalizeRelativeAndCheckNoError("a/b/c", "a/b"));
- EXPECT_EQ("a",
- NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a", &err)));
- EXPECT_EQ("", err);
- EXPECT_EQ(string("../") + currentdir + string("/a"),
- NormalizeRelativeAndCheckNoError("a", "../b"));
- EXPECT_EQ(string("../") + currentdir + string("/a/b"),
- NormalizeRelativeAndCheckNoError("a/b", "../c"));
- EXPECT_EQ("../../a", NormalizeRelativeAndCheckNoError("a", "b/c"));
- EXPECT_EQ(".", NormalizeRelativeAndCheckNoError("a", "a"));
-}
-
-TEST(IncludesNormalize, Case) {
- EXPECT_EQ("b", NormalizeAndCheckNoError("Abc\\..\\b"));
- EXPECT_EQ("BdEf", NormalizeAndCheckNoError("Abc\\..\\BdEf"));
- EXPECT_EQ("A/b", NormalizeAndCheckNoError("A\\.\\b"));
- EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b"));
- EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\.\\B"));
- EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\./B"));
-}
-
-TEST(IncludesNormalize, DifferentDrive) {
- EXPECT_EQ("stuff.h",
- NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "p:\\vs08"));
- EXPECT_EQ("stuff.h",
- NormalizeRelativeAndCheckNoError("P:\\Vs08\\stuff.h", "p:\\vs08"));
- EXPECT_EQ("p:/vs08/stuff.h",
- NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "c:\\vs08"));
- EXPECT_EQ("P:/vs08/stufF.h", NormalizeRelativeAndCheckNoError(
- "P:\\vs08\\stufF.h", "D:\\stuff/things"));
- EXPECT_EQ("P:/vs08/stuff.h", NormalizeRelativeAndCheckNoError(
- "P:/vs08\\stuff.h", "D:\\stuff/things"));
- EXPECT_EQ("P:/wee/stuff.h",
- NormalizeRelativeAndCheckNoError("P:/vs08\\../wee\\stuff.h",
- "D:\\stuff/things"));
-}
-
-TEST(IncludesNormalize, LongInvalidPath) {
- const char kLongInputString[] =
- "C:\\Program Files (x86)\\Microsoft Visual Studio "
- "12.0\\VC\\INCLUDEwarning #31001: The dll for reading and writing the "
- "pdb (for example, mspdb110.dll) could not be found on your path. This "
- "is usually a configuration error. Compilation will continue using /Z7 "
- "instead of /Zi, but expect a similar error when you link your program.";
- // Too long, won't be canonicalized. Ensure doesn't crash.
- string result, err;
- IncludesNormalize normalizer(".");
- EXPECT_FALSE(
- normalizer.Normalize(kLongInputString, &result, &err));
- EXPECT_EQ("path too long", err);
-
-
- // Construct max size path having cwd prefix.
- // kExactlyMaxPath = "$cwd\\a\\aaaa...aaaa\0";
- char kExactlyMaxPath[_MAX_PATH + 1];
- ASSERT_NOT_NULL(_getcwd(kExactlyMaxPath, sizeof kExactlyMaxPath));
-
- int cwd_len = strlen(kExactlyMaxPath);
- ASSERT_LE(cwd_len + 3 + 1, _MAX_PATH)
- kExactlyMaxPath[cwd_len] = '\\';
- kExactlyMaxPath[cwd_len + 1] = 'a';
- kExactlyMaxPath[cwd_len + 2] = '\\';
-
- kExactlyMaxPath[cwd_len + 3] = 'a';
-
- for (int i = cwd_len + 4; i < _MAX_PATH; ++i) {
- if (i > cwd_len + 4 && i < _MAX_PATH - 1 && i % 10 == 0)
- kExactlyMaxPath[i] = '\\';
- else
- kExactlyMaxPath[i] = 'a';
- }
-
- kExactlyMaxPath[_MAX_PATH] = '\0';
- EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH);
-
- string forward_slashes(kExactlyMaxPath);
- replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/');
- // Make sure a path that's exactly _MAX_PATH long is canonicalized.
- EXPECT_EQ(forward_slashes.substr(cwd_len + 1),
- NormalizeAndCheckNoError(kExactlyMaxPath));
-}
-
-TEST(IncludesNormalize, ShortRelativeButTooLongAbsolutePath) {
- string result, err;
- IncludesNormalize normalizer(".");
- // A short path should work
- EXPECT_TRUE(normalizer.Normalize("a", &result, &err));
- EXPECT_EQ("", err);
-
- // Construct max size path having cwd prefix.
- // kExactlyMaxPath = "aaaa\\aaaa...aaaa\0";
- char kExactlyMaxPath[_MAX_PATH + 1];
- for (int i = 0; i < _MAX_PATH; ++i) {
- if (i < _MAX_PATH - 1 && i % 10 == 4)
- kExactlyMaxPath[i] = '\\';
- else
- kExactlyMaxPath[i] = 'a';
- }
- kExactlyMaxPath[_MAX_PATH] = '\0';
- EXPECT_EQ(strlen(kExactlyMaxPath), _MAX_PATH);
-
- // Make sure a path that's exactly _MAX_PATH long fails with a proper error.
- EXPECT_FALSE(normalizer.Normalize(kExactlyMaxPath, &result, &err));
- EXPECT_TRUE(err.find("GetFullPathName") != string::npos);
-}
diff --git a/src/inline.sh b/src/inline.sh
deleted file mode 100755
index 5092fa2..0000000
--- a/src/inline.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-#
-# Copyright 2001 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.
-
-# This quick script converts a text file into an #include-able header.
-# It expects the name of the variable as its first argument, and reads
-# stdin and writes stdout.
-
-varname="$1"
-
-# '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/interrupt_handling-posix.cc b/src/interrupt_handling-posix.cc
deleted file mode 100644
index fa47131..0000000
--- a/src/interrupt_handling-posix.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2023 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 <stdio.h>
-#include <unistd.h>
-
-#include "interrupt_handling.h"
-#include "util.h"
-
-// Set to 1 to print debug messages to stderr during development
-#define DEBUG 0
-
-namespace {
-
-// Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM
-sigset_t GetInterruptSignalMask() {
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGINT);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGTERM);
- return mask;
-}
-
-// Set the signal action for a given |signum|, returning the previous one
-// in |*old_action| is |old_action != nullptr|.
-void SetSignalAction(int signum, const struct sigaction* action,
- struct sigaction* old_action) {
- if (sigaction(signum, action, old_action) < 0)
- ErrnoFatal("sigaction");
-}
-
-} // namespace
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptBlocker
-///
-
-InterruptBlocker::InterruptBlocker() {
- sigset_t block_interrupts = GetInterruptSignalMask();
- if (sigprocmask(SIG_BLOCK, &block_interrupts, &prev_signal_mask_) < 0)
- ErrnoFatal("sigprocmask");
-}
-
-InterruptBlocker::~InterruptBlocker() {
- if (sigprocmask(SIG_SETMASK, &prev_signal_mask_, nullptr) < 0)
- ErrnoFatal("sigprocmask");
-}
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptHandlerBase
-///
-
-InterruptHandlerBase::InterruptHandlerBase(const struct sigaction& action) {
- // Block the signals before changing the handlers.
- sigset_t mask = GetInterruptSignalMask();
- sigprocmask(SIG_BLOCK, &mask, &old_mask_);
-
- SetSignalAction(SIGINT, &action, &old_int_action_);
- SetSignalAction(SIGHUP, &action, &old_hup_action_);
- SetSignalAction(SIGTERM, &action, &old_term_action_);
-
- // Unblock the signals now.
- sigprocmask(SIG_UNBLOCK, &mask, nullptr);
-}
-
-InterruptHandlerBase::~InterruptHandlerBase() {
- // Block the signal before changing the action handlers.
- sigset_t mask = GetInterruptSignalMask();
- sigprocmask(SIG_BLOCK, &mask, nullptr);
-
- SetSignalAction(SIGINT, &old_int_action_, nullptr);
- SetSignalAction(SIGHUP, &old_hup_action_, nullptr);
- SetSignalAction(SIGTERM, &old_term_action_, nullptr);
-
- // Restore the original signal mask.
- sigprocmask(SIG_SETMASK, &old_mask_, nullptr);
-}
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptCatcher
-///
-
-InterruptCatcher::InterruptCatcher() : InterruptHandlerBase(MakeAction()) {
- s_interrupted_ = 0;
- HandlePendingInterrupt();
-}
-
-InterruptCatcher::~InterruptCatcher() = default;
-
-#if DEBUG
-#define WRITE(msg) ::write(2, msg, sizeof(msg) - 1)
-#else
-#define WRITE(msg) (void)(msg)
-#endif
-
-// static
-struct sigaction InterruptCatcher::MakeAction() {
- struct sigaction result = {};
- result.sa_handler = [](int signum) {
- s_interrupted_ = signum;
- if (signum == SIGINT)
- WRITE("\nSIGINT SIGNALED\n");
- else if (signum == SIGHUP)
- WRITE("\nSIGHUP SIGNALED\n");
- else if (signum == SIGTERM)
- WRITE("\nSIGTERM SIGNALED\n");
- };
- return result;
-}
-
-// static
-void InterruptCatcher::HandlePendingInterrupt() {
- sigset_t pending;
- sigemptyset(&pending);
- if (sigpending(&pending) == -1) {
- perror("ninja: sigpending");
- return;
- }
- if (sigismember(&pending, SIGINT)) {
- WRITE("\nSIGINT PENDING\n");
- s_interrupted_ = SIGINT;
- } else if (sigismember(&pending, SIGTERM)) {
- WRITE("\nSIGTERM PENDING\n");
- s_interrupted_ = SIGTERM;
- } else if (sigismember(&pending, SIGHUP)) {
- WRITE("\nSIGHUP PENDING\n");
- s_interrupted_ = SIGHUP;
- }
-}
-
-// static
-volatile sig_atomic_t InterruptCatcher::s_interrupted_ = 0;
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptForwarder
-///
-
-#include <unistd.h>
-
-InterruptForwarder::InterruptForwarder(pid_t process_group)
- : InterruptHandlerBase(MakeAction()), old_process_group_(s_process_group_) {
- s_process_group_ = process_group;
-}
-
-InterruptForwarder::~InterruptForwarder() {
- s_process_group_ = old_process_group_;
-}
-
-// static
-struct sigaction InterruptForwarder::MakeAction() {
- struct sigaction result = {};
- result.sa_handler = [](int signum) {
- // Send the interrupt to the server's process group
- kill(-s_process_group_, signum);
- WRITE("\nINTERRUPT FORWARDED\n");
- };
- return result;
-}
-
-// static
-pid_t InterruptForwarder::s_process_group_ = 0;
diff --git a/src/interrupt_handling-win32.cc b/src/interrupt_handling-win32.cc
deleted file mode 100644
index a0b866c..0000000
--- a/src/interrupt_handling-win32.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2023 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 "interrupt_handling.h"
-#include "util.h"
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptCompletionPortHandler
-///
-
-InterruptCompletionPortHandler::InterruptCompletionPortHandler(
- HANDLE ioport, ULONG_PTR completion_key)
- : old_ioport_(s_ioport_), old_completion_key_(s_completion_key_) {
- s_ioport_ = ioport;
- s_completion_key_ = completion_key;
-
- if (!SetConsoleCtrlHandler(HandlerRoutine, TRUE))
- Win32Fatal("SetConsoleCtrlHandler");
-}
-
-InterruptCompletionPortHandler::~InterruptCompletionPortHandler() {
- s_ioport_ = old_ioport_;
- s_completion_key_ = old_completion_key_;
-
- if (!SetConsoleCtrlHandler(HandlerRoutine, FALSE))
- Win32Fatal("SetConsoleCtrlHandler");
-}
-
-// static
-BOOL InterruptCompletionPortHandler::HandlerRoutine(DWORD dwCtrlType) {
- if (dwCtrlType != CTRL_C_EVENT && dwCtrlType != CTRL_BREAK_EVENT)
- return FALSE;
-
- if (!PostQueuedCompletionStatus(s_ioport_, 0, s_completion_key_, nullptr))
- Win32Fatal("PostQueuedCompletionStatus");
-
- return TRUE;
-}
-
-// static
-HANDLE InterruptCompletionPortHandler::s_ioport_ = INVALID_HANDLE_VALUE;
-
-// static
-ULONG_PTR InterruptCompletionPortHandler::s_completion_key_ = 0;
-
-//////////////////////////////////////////////////////////////////////////
-///
-/// InterruptForwarder
-///
-
-InterruptForwarder::InterruptForwarder(DWORD pgid)
- : old_process_group_id_(s_process_group_id_) {
- s_process_group_id_ = pgid;
- if (!SetConsoleCtrlHandler(InterruptForwarder::HandlerRoutine, TRUE))
- Win32Fatal("SetConsoleCtrlHandler");
-}
-
-InterruptForwarder::~InterruptForwarder() {
- s_process_group_id_ = old_process_group_id_;
- if (!SetConsoleCtrlHandler(InterruptForwarder::HandlerRoutine, FALSE))
- Win32Fatal("SetConsoleCtrlHandler");
-}
-
-// static
-BOOL InterruptForwarder::HandlerRoutine(DWORD dwCtrlType) {
- if (dwCtrlType != CTRL_C_EVENT && dwCtrlType != CTRL_BREAK_EVENT)
- return FALSE;
-
- if (!GenerateConsoleCtrlEvent(dwCtrlType, s_process_group_id_))
- Win32Fatal("GenerateConsoleCtrlEvent");
-
- return TRUE;
-}
-
-// static
-DWORD InterruptForwarder::s_process_group_id_ = 0;
diff --git a/src/interrupt_handling.h b/src/interrupt_handling.h
deleted file mode 100644
index 6dc562c..0000000
--- a/src/interrupt_handling.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2023 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_INTERRUPT_HANDLING_H_
-#define NINJA_INTERRUPT_HANDLING_H_
-
-/// Convenience classes used to control how user interruption
-/// is handled by Ninja at various times. This means Ctrl+C and Ctrl+Break
-/// events on Win32, and SIGINT/SIGHUP/SIGTERM ones on Posix.
-
-#ifdef _WIN32
-
-#include <windows.h>
-
-/// On Ctrl-C or Ctrl-Break, post en empty i/o completion packet
-/// to a given i/o completion queue. Useful to catch signals when
-/// waiting for overlapped i/o on Win32.
-struct InterruptCompletionPortHandler {
- InterruptCompletionPortHandler(HANDLE ioport, ULONG_PTR completion_key = 0);
- ~InterruptCompletionPortHandler();
-
- private:
- static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType);
- HANDLE old_ioport_;
- ULONG_PTR old_completion_key_;
- static HANDLE s_ioport_;
- static ULONG_PTR s_completion_key_;
-};
-
-/// Forward all Ctrl-C and Ctrl-Break signals to a different
-/// process group.
-struct InterruptForwarder {
- InterruptForwarder(DWORD process_group_id);
- ~InterruptForwarder();
-
- private:
- static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType);
- DWORD old_process_group_id_;
- static DWORD s_process_group_id_;
-};
-
-#else // !_WIN32
-
-#include <signal.h>
-
-/// A class used to block all SIGINT/SIGHUP/SIGTERM signals
-/// in the current process. Restores the previous signal
-/// mask in the destructor.
-struct InterruptBlocker {
- InterruptBlocker();
- ~InterruptBlocker();
-
- const sigset_t& old_mask() const { return prev_signal_mask_; }
-
- private:
- sigset_t prev_signal_mask_;
-};
-
-/// Base class for all interrupt handlers.
-struct InterruptHandlerBase {
- /// Constructor sets a new signal handler for SIGINT/SIGHUP/SIGTERM
- /// and unblocks these signals!
- InterruptHandlerBase(const struct sigaction& action);
-
- /// Destructor restores previous interrupt handlers and signal mask.
- ~InterruptHandlerBase();
-
- /// Return the signal mask before construction. This will be restored
- /// on destruction as well.
- sigset_t old_mask() const { return old_mask_; }
-
- private:
- struct sigaction old_int_action_;
- struct sigaction old_hup_action_;
- struct sigaction old_term_action_;
- sigset_t old_mask_;
-};
-
-/// Catch all SIGINT/SIGHUP/SIGTERM signals and stores the
-/// corresponding signal number into a global interrupted()
-/// variable. This can also detect pending signals.
-struct InterruptCatcher : public InterruptHandlerBase {
- InterruptCatcher();
- ~InterruptCatcher();
-
- /// Return interrupt signal number, or 0 if there were none.
- int interrupted() const { return s_interrupted_; }
-
- /// Clear the interrupted signal number.
- void Clear() { s_interrupted_ = 0; }
-
- /// Handle any pending interruption signal. This updates
- /// the interrupted() value but does not cancel the signals.
- static void HandlePendingInterrupt();
-
- private:
- static struct sigaction MakeAction();
- static volatile sig_atomic_t s_interrupted_;
-};
-
-/// Forward all SIGINT/SIGHUP/SIGTERM signal to a different
-/// process group
-struct InterruptForwarder : public InterruptHandlerBase {
- explicit InterruptForwarder(pid_t process_group);
- ~InterruptForwarder();
-
- private:
- static struct sigaction MakeAction();
- pid_t old_process_group_ = -1;
- static pid_t s_process_group_;
-};
-
-#endif // !_WIN32
-
-#endif // NINJA_INTERRUPT_HANDLING_H_
diff --git a/src/interrupt_handling_test.cc b/src/interrupt_handling_test.cc
deleted file mode 100644
index 2df18db..0000000
--- a/src/interrupt_handling_test.cc
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright 2023 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 "interrupt_handling.h"
-
-#include "test.h"
-#include "util.h"
-
-#ifdef _WIN32
-
-// TODO(digit): Write real test for _WIN32
-char dummy_;
-
-#else // !_WIN32
-
-#include <sys/signal.h>
-#include <unistd.h>
-
-namespace {
-
-// Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM
-sigset_t GetInterruptSignalMask() {
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGINT);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGTERM);
- return mask;
-}
-
-// Set the signal action for a given |signum|, returning the previous one
-// in |*old_action| is |old_action != nullptr|.
-void SetSignalAction(int signum, const struct sigaction* action,
- struct sigaction* old_action) {
- if (sigaction(signum, action, old_action) < 0)
- ErrnoFatal("sigaction");
-}
-
-// Base class for all tests, used to set the signal mask and actions for
-// interrupts to the same base state.
-class InterruptHandlingTest : public ::testing::Test {
- public:
- InterruptHandlingTest() {
- // Block signals before changing the action handler + save previous mask.
- sigset_t mask = GetInterruptSignalMask();
- sigprocmask(SIG_BLOCK, &mask, &prev_mask_);
-
- // Change signal handlers.
- struct sigaction sigint_action = {};
- sigint_action.sa_handler = [](int) { s_got_sigint = 1; };
- SetSignalAction(SIGINT, &sigint_action, &prev_sigint_action_);
-
- struct sigaction sighup_action = {};
- sighup_action.sa_handler = [](int) { s_got_sighup = 1; };
- SetSignalAction(SIGHUP, &sighup_action, &prev_sighup_action_);
-
- struct sigaction sigterm_action = {};
- sigterm_action.sa_handler = [](int) { s_got_sigterm = 1; };
- SetSignalAction(SIGTERM, &sigterm_action, &prev_sigterm_action_);
-
- // Unblock signals.
- sigprocmask(SIG_UNBLOCK, &mask, nullptr);
- }
-
- ~InterruptHandlingTest() {
- // Block signals before changing handlers.
- sigset_t mask = GetInterruptSignalMask();
- sigprocmask(SIG_BLOCK, &mask, nullptr);
-
- // Restore previous handlers.
- SetSignalAction(SIGINT, &prev_sigint_action_, nullptr);
- SetSignalAction(SIGHUP, &prev_sighup_action_, nullptr);
- SetSignalAction(SIGTERM, &prev_sigterm_action_, nullptr);
-
- // Clear flags for next test.
- Clear();
-
- // Restore previous signal mask.
- sigprocmask(SIG_SETMASK, &prev_mask_, nullptr);
- }
-
- void Clear() {
- s_got_sigint = 0;
- s_got_sighup = 0;
- s_got_sigterm = 0;
- }
-
- void SendSelfSignal(int signum) { ::kill(getpid(), signum); }
-
- sigset_t prev_mask_;
- struct sigaction prev_sigint_action_;
- struct sigaction prev_sighup_action_;
- struct sigaction prev_sigterm_action_;
-
- static volatile sig_atomic_t s_got_sigint;
- static volatile sig_atomic_t s_got_sighup;
- static volatile sig_atomic_t s_got_sigterm;
-};
-
-volatile sig_atomic_t InterruptHandlingTest::s_got_sigint = 0;
-volatile sig_atomic_t InterruptHandlingTest::s_got_sighup = 0;
-volatile sig_atomic_t InterruptHandlingTest::s_got_sigterm = 0;
-
-} // namespace
-
-TEST_F(InterruptHandlingTest, SendSelfSignals) {
- // Verify that the interrupt signals are not blocked
- sigset_t empty_mask;
- sigemptyset(&empty_mask);
- sigset_t cur_mask;
- sigprocmask(SIG_BLOCK, &empty_mask, &cur_mask);
- ASSERT_FALSE(sigismember(&cur_mask, SIGINT));
- ASSERT_FALSE(sigismember(&cur_mask, SIGHUP));
- ASSERT_FALSE(sigismember(&cur_mask, SIGTERM));
-
- SendSelfSignal(SIGINT);
- ASSERT_TRUE(s_got_sigint);
-
- SendSelfSignal(SIGHUP);
- ASSERT_TRUE(s_got_sighup);
-
- SendSelfSignal(SIGTERM);
- ASSERT_TRUE(s_got_sigterm);
-
- Clear();
- ASSERT_FALSE(s_got_sigint);
- ASSERT_FALSE(s_got_sighup);
- ASSERT_FALSE(s_got_sigterm);
-}
-
-TEST_F(InterruptHandlingTest, InterruptBlocker) {
- {
- InterruptBlocker blocker;
-
- SendSelfSignal(SIGINT);
- ASSERT_FALSE(s_got_sigint);
-
- SendSelfSignal(SIGHUP);
- ASSERT_FALSE(s_got_sighup);
-
- SendSelfSignal(SIGTERM);
- ASSERT_FALSE(s_got_sigterm);
- }
- ASSERT_TRUE(s_got_sigint);
- ASSERT_TRUE(s_got_sighup);
- ASSERT_TRUE(s_got_sigterm);
-}
-
-TEST_F(InterruptHandlingTest, InterruptCatcher) {
- {
- InterruptCatcher catcher;
-
- SendSelfSignal(SIGINT);
- ASSERT_FALSE(s_got_sigint);
- ASSERT_EQ(SIGINT, catcher.interrupted());
-
- SendSelfSignal(SIGHUP);
- ASSERT_FALSE(s_got_sighup);
- ASSERT_EQ(SIGHUP, catcher.interrupted());
-
- SendSelfSignal(SIGTERM);
- ASSERT_FALSE(s_got_sigterm);
- ASSERT_EQ(SIGTERM, catcher.interrupted());
- }
-
- // Verify that a second instance does not keep the old interrupted()
- // value from a stale global variable.
- {
- InterruptCatcher catcher;
- ASSERT_EQ(0, catcher.interrupted());
- }
-}
-
-TEST_F(InterruptHandlingTest, InterruptForwarder) {
- ASSERT_FALSE(s_got_sigint);
-
- // Create child process with fork.
- pid_t child_pid = fork();
- ASSERT_GE(child_pid, 0);
-
- if (child_pid == 0) {
- // In the child process, do nothing, just wait for an interrupt.
- sleep(10);
- exit(0);
- }
-
- InterruptForwarder forwarder(child_pid);
-
- SendSelfSignal(SIGINT);
- ASSERT_FALSE(s_got_sigint);
-}
-
-#endif // !_WIN32
diff --git a/src/ipc_handle-posix.cc b/src/ipc_handle-posix.cc
deleted file mode 100644
index 09d3c78..0000000
--- a/src/ipc_handle-posix.cc
+++ /dev/null
@@ -1,757 +0,0 @@
-// Copyright 2023 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 <errno.h>
-#include <fcntl.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include "ipc_handle.h"
-#include "util.h" // For StringFormat() and GetLastErrorString()
-
-namespace {
-
-// helper macro to loop on EINTR during syscalls.
-// Important: Do not use it for close(), use CloseFd() instead.
-#define HANDLE_EINTR(x) \
- ({ \
- decltype(x) eintr_wrapper_result; \
- do { \
- eintr_wrapper_result = (x); \
- } while (eintr_wrapper_result == -1 && errno == EINTR); \
- eintr_wrapper_result; \
- })
-
-// Close file descriptor if needed, preserving errno
-// since EINTR can happen during a close(), but there
-// is nothing that can be done when it does (since
-// it is impossible to tell whether the descriptor
-// was already closed or not, the result being kernel
-// and system specific).
-void CloseFd(int& fd) {
- if (fd >= 0) {
- int save_errno = errno;
- ::close(fd);
- fd = -1;
- errno = save_errno;
- }
-}
-
-// Convenience function to write the errno message to a string
-// and return false.
-bool SetErrnoMessage(std::string* error_message) {
- *error_message = strerror(errno);
- return false;
-}
-
-// Return true if |fd| is in non-blocking mode.
-bool IsNonBlockingFd(int fd) {
- int flags = fcntl(fd, F_GETFL);
- return (flags >= 0) && (flags & O_NONBLOCK) != 0;
-}
-
-// Set the non-blocking flags of |fd| to |enabled|
-void SetNonBlockingFd(int fd, bool enabled) {
- int flags = fcntl(fd, F_GETFL);
- if (flags < 0)
- return;
-
- int new_flags = enabled ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
- if (new_flags != flags)
- fcntl(fd, F_SETFL, new_flags);
-}
-
-bool IsClosedOnExec(int fd) {
- int flags = fcntl(fd, F_GETFD);
- return (flags >= 0) && (flags & FD_CLOEXEC) != 0;
-}
-
-void SetClosedOnExecFlag(int fd, bool enabled) {
- if (fd >= 0) {
- int flags = fcntl(fd, F_GETFD);
- int new_flags = enabled ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC);
- if (flags != new_flags)
- fcntl(fd, F_SETFD, new_flags);
- }
-}
-
-// Create a new unix-domain socket, potentially in non-blocking mode,
-// always with CLOEXEC.
-int CreateUnixSocket(bool non_blocking) {
-#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
- int flags = SOCK_STREAM | SOCK_CLOEXEC | (non_blocking ? SOCK_NONBLOCK : 0);
- return socket(AF_UNIX, flags, 0);
-#else // !SOCK_NONBLOCK || !SOCK_CLOEXEC
- int sock = socket(AF_UNIX, SOCK_STREAM, 0);
- if (sock < 0)
- return -1;
-
- int flags = fcntl(sock, F_GETFD);
- fcntl(sock, F_SETFD, flags | FD_CLOEXEC);
-
- if (non_blocking)
- SetNonBlockingFd(sock, true);
-
- return sock;
-#endif // !SOCK_NONBLOCK || !SOCK_CLOEXEC
-}
-
-// Convenience class to wait for a single file descriptor to become
-// either readable or writable. Usage is:
-//
-// - Create instance.
-// - Call Wait() passing a timeout.
-//
-struct PosixIoConditionWaiter {
- // Constructor. Set |writable| to true to wait for write events,
- // otherwise for |read| events.
- PosixIoConditionWaiter(int fd, bool writable) : fd_(fd), writable_(writable) {
- FD_ZERO(&fds_);
- }
-
- // Wait for the specific condition. On success return true.
- // On failure, set |*error| then return false. In all cases
- // |*did_timeout| will be set to true only in case of timeout.
- bool Wait(int64_t timeout_ms, bool* did_timeout, std::string* error) {
- // Reset fds_ in case Wait() is called multiple times.
- FD_SET(fd_, &fds_);
-
- struct timespec* ts = nullptr;
- struct timespec timeout_ts;
- if (timeout_ms >= 0) {
- timeout_ts.tv_sec = static_cast<time_t>(timeout_ms / 1000);
- timeout_ts.tv_nsec =
- static_cast<int32_t>((timeout_ms % 1000) * 1000000LL);
- ts = &timeout_ts;
- }
-
- *did_timeout = false;
-
- fd_set* readable = writable_ ? nullptr : &fds_;
- fd_set* writable = writable_ ? &fds_ : nullptr;
- int ret = pselect(fd_ + 1, readable, writable, nullptr, ts, nullptr);
- if (ret < 0)
- return SetErrnoMessage(error);
-
- if (!FD_ISSET(fd_, &fds_)) {
- *did_timeout = true;
- *error = "timed out";
- return false;
- }
- return true;
- }
-
- const int fd_;
- const bool writable_;
- fd_set fds_;
-};
-
-// Retrieve non-blocking socket connection status.
-int GetSocketConnectStatus(int fd) {
- int so_error = 0;
- socklen_t so_error_len = sizeof(so_error);
- int ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len);
- if (ret < 0)
- return ret;
-
- return so_error;
-}
-
-// Set USE_LINUX_NAMESPACE to 1 to use Linux abstract
-// unix namespace, which do not require a filesystem
-// entry point.
-#ifdef __linux__
-#define USE_LINUX_NAMESPACE 1
-#endif
-
-#if !USE_LINUX_NAMESPACE
-// Return runtime directory where to create a Unix socket.
-// Only used for non-Linux systems. Callers can assume
-// that the directory already exists (otherwise, the system
-// is not configured properly, and an error on bind or
-// connect operation is expected).
-std::string GetRuntimeDirectory() {
- std::string result;
- // XDG_RUNTIME_DIR might be defined on BSDs and other operating
- // systems.
- const char* xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
- if (xdg_runtime_dir) {
- result = xdg_runtime_dir;
- }
- if (result.empty()) {
- const char* tmp = getenv("TMPDIR");
- if (!tmp || !tmp[0])
- tmp = "/tmp";
- result = tmp;
- }
- return result;
-}
-#endif // !USE_LINUX_NAMESPACE
-
-// Return the Unix socket path to be used for |service_name|.
-std::string CreateUnixSocketPath(StringPiece service_name) {
- // On Linux, use the abstract namespace by creating a string with
- // a NUL byte at the front. On other platform, use the runtime
- // directory instead.
-#if USE_LINUX_NAMESPACE
- std::string result(1, '\0');
-#else
- std::string result = GetRuntimeDirectory() + "/";
-#endif
- result += "basic_ipc-";
- const char* user = getenv("USER");
- if (!user || !user[0])
- user = "unknown_user";
- result += user;
- result += "-";
- result += service_name.AsString();
- return result;
-}
-
-// Convenience class to model a Unix socket address.
-// Usage is:
-// 1) Create instance, passing service name.
-// 2) Call valid() to check the instance. If false the
-// service name was too long.
-// 3) Use address() and size() to pass to sendmsg().
-class LocalAddress {
- public:
- LocalAddress(StringPiece service_name) {
- local_ = {};
- local_.sun_family = AF_UNIX;
- std::string path = CreateUnixSocketPath(service_name);
- if (path.size() >= sizeof(local_.sun_path))
- return; // Service name is too long.
-
- memcpy(local_.sun_path, path.data(), path.size());
- local_.sun_path[path.size()] = '\0';
- size_ = offsetof(sockaddr_un, sun_path) + path.size() + 1;
- }
-
- bool valid() const { return size_ > 0; }
- sockaddr* address() const { return const_cast<sockaddr*>(&generic_); }
- size_t size() const { return size_; }
- const char* path() const { return local_.sun_path; }
- std::string pid_path() const {
- return StringFormat("%s.pid", local_.sun_path);
- }
-
- private:
- size_t size_ = 0;
- union {
- sockaddr_un local_;
- sockaddr generic_;
- };
-};
-
-} // namespace
-
-// static
-constexpr int IpcHandle::kInvalid;
-
-// static
-IpcHandle::HandleType IpcHandle::CloneNativeHandle(HandleType handle,
- bool inherited) {
- int fd = ::dup(handle);
- SetClosedOnExecFlag(fd, inherited);
- return fd;
-}
-
-void IpcHandle::Close() {
- CloseFd(handle_);
-}
-
-int IpcHandle::ReleaseNativeHandle() {
- int result = handle_;
- handle_ = -1;
- return result;
-}
-
-ssize_t IpcHandle::Read(void* buff, size_t buffer_size,
- std::string* error_message) const {
- auto* buffer = static_cast<char*>(buff);
- ssize_t result = 0;
- while (buffer_size > 0) {
- ssize_t count = read(handle_, buffer, buffer_size);
- if (count < 0) {
- if (errno == EINTR)
- continue;
- if (result > 0) {
- // Ignore this error to return the current read result.
- // This assumes the error will repeat on the next call.
- break;
- }
- *error_message = strerror(errno);
- return -1;
- } else if (count == 0) {
- break;
- }
- buffer += count;
- buffer_size -= static_cast<size_t>(count);
- result += count;
- }
- return result;
-}
-
-ssize_t IpcHandle::Write(const void* buff, size_t buffer_size,
- std::string* error_message) const {
- auto* buffer = static_cast<const char*>(buff);
- ssize_t result = 0;
- while (buffer_size > 0) {
- ssize_t count = write(handle_, buffer, buffer_size);
- if (count < 0) {
- if (errno == EINTR)
- continue;
- if (result > 0) {
- break;
- }
- *error_message = strerror(errno);
- return -1;
- } else if (count == 0) {
- break;
- }
- buffer += count;
- buffer_size -= static_cast<size_t>(count);
- result += count;
- }
- return result;
-}
-
-bool IpcHandle::SendNativeHandle(HandleType native,
- std::string* error_message) const {
- char ch = 'x';
- iovec iov = { &ch, 1 };
- union {
- char buf[CMSG_SPACE(sizeof(int))];
- cmsghdr align;
- } control;
- memset(control.buf, 0, sizeof(control.buf));
-
- msghdr header = {};
- header.msg_iov = &iov;
- header.msg_iovlen = 1;
- header.msg_control = control.buf;
- header.msg_controllen = sizeof(control.buf);
-
- cmsghdr* control_header = CMSG_FIRSTHDR(&header);
- control_header->cmsg_len = CMSG_LEN(sizeof(int));
- control_header->cmsg_level = SOL_SOCKET;
- control_header->cmsg_type = SCM_RIGHTS;
- reinterpret_cast<int*>(CMSG_DATA(control_header))[0] = native;
-
- ssize_t ret = HANDLE_EINTR(sendmsg(handle_, &header, 0));
- if (ret == -1)
- return SetErrnoMessage(error_message);
-
- return true;
-}
-
-bool IpcHandle::ReceiveNativeHandle(IpcHandle* native,
- std::string* error_message) const {
- char ch = '\0';
- iovec iov = { &ch, 1 };
- union {
- char buf[CMSG_SPACE(sizeof(int))];
- cmsghdr align;
- } control;
- memset(control.buf, 0, sizeof(control.buf));
-
- msghdr header = {};
- header.msg_iov = &iov;
- header.msg_iovlen = 1;
- header.msg_control = control.buf;
- header.msg_controllen = sizeof(control.buf);
-
- ssize_t ret = HANDLE_EINTR(recvmsg(handle_, &header, 0));
- if (ret == -1)
- return SetErrnoMessage(error_message);
-
- cmsghdr* control_header = CMSG_FIRSTHDR(&header);
- if (!control_header || control_header->cmsg_len != CMSG_LEN(sizeof(int)) ||
- control_header->cmsg_level != SOL_SOCKET ||
- control_header->cmsg_type != SCM_RIGHTS) {
- *error_message =
- std::string("Invalid data when receiving file descriptor!");
- return false;
- }
- *native = IpcHandle(reinterpret_cast<int*>(CMSG_DATA(control_header))[0]);
- return true;
-}
-
-bool IpcHandle::IsInheritable() const {
- return IsClosedOnExec(handle_);
-}
-
-void IpcHandle::SetInheritable(bool enabled) {
- SetClosedOnExecFlag(handle_, enabled);
-}
-
-bool IpcHandle::IsNonBlocking() const {
- return IsNonBlockingFd(handle_);
-}
-
-void IpcHandle::SetNonBlocking(bool enable) {
- SetNonBlockingFd(handle_, enable);
-}
-
-// static
-IpcHandle::HandleType IpcHandle::NativeForStdio(FILE* file) {
- return fileno(file);
-}
-
-// static
-IpcHandle IpcHandle::CloneFromStdio(FILE* file) {
- fflush(file);
- return { CloneNativeHandle(fileno(file)) };
-}
-
-bool IpcHandle::CloneIntoStdio(FILE* file) {
- if (file != stdout && file != stderr && file != stdin) {
- errno = EINVAL;
- return false;
- }
- if (handle_ < 0) {
- errno = EINVAL;
- return false;
- }
-
- fflush(file);
- int ret = ::dup2(handle_, fileno(file));
- if (ret < 0)
- return false;
-
- return true;
-}
-
-void IpcServiceHandle::Close() {
- this->IpcHandle::Close();
- if (!socket_path_.empty() && socket_path_[0] != '\0') {
- // Remove socket and pid file.
- unlink(socket_path_.c_str());
-
- std::string pid_path = socket_path_;
- pid_path += ".pid";
- unlink(pid_path.c_str());
-
- socket_path_.clear();
- }
-}
-
-IpcServiceHandle::~IpcServiceHandle() {
- Close();
-}
-
-#if !USE_LINUX_NAMESPACE
-// Try to read the pidfile for |address| if it exists, and return the server
-// process id it contains. Return 0 if there is no pid file or if it is
-// malformed. Return -1 and set |*err| if the pid file could not be read
-// (likely a permission issue).
-int ReadPidFile(const std::string& pid_path, std::string* err) {
- // Try to open the pid file.
- FILE* pid_file = fopen(pid_path.c_str(), "r");
- if (!pid_file) {
- if (errno != ENOENT) {
- *err = StringFormat("Cannot open pid file: %s", strerror(errno));
- return -1;
- }
- // There is no pid file.
- return 0;
- }
-
- int server_pid = -1;
- int ret = fscanf(pid_file, "%d", &server_pid);
- (void)fclose(pid_file);
-
- if (ret != 1 || server_pid <= 0) {
- // A malformed pid file, consider server not running and
- // do not report error.
- return 0;
- }
-
- return server_pid;
-}
-
-#endif // !USE_LINUX_NAMESPACE
-
-// static
-IpcServiceHandle IpcServiceHandle::BindTo(StringPiece service_name,
- std::string* error_message) {
- LocalAddress address(service_name);
- if (!address.valid()) {
- *error_message = std::string("Service name too long");
- return {};
- }
-#if !USE_LINUX_NAMESPACE
- // Try to see if another server is already running. Use a .pid file
- // that contains the server's process PID to do that, and check whether
- // it is still alive.
- std::string pid_path = address.pid_path();
- int server_pid = ReadPidFile(pid_path, error_message);
- if (server_pid < 0)
- return {};
-
- if (server_pid > 0 && kill(server_pid, 0) == 0) {
- // The server process is still running.
- *error_message = "already in use";
- return {};
- }
-
- // Create new temporary pid file before doing an atomic filesystem rename.
- int cur_pid = getpid();
- std::string temp_pid_path =
- StringFormat("%s.temp.%d", pid_path.c_str(), cur_pid);
- {
- bool pid_file_error = false;
- FILE* pid_file = fopen(temp_pid_path.c_str(), "w");
- if (!pid_file) {
- pid_file_error = true;
- } else {
- if (fprintf(pid_file, "%d", cur_pid) <= 0)
- pid_file_error = true;
- fclose(pid_file);
- }
- if (pid_file_error) {
- *error_message = "Cannot create temporary pid file: ";
- *error_message += strerror(errno);
- return {};
- }
- }
- // atomically rename the temporary file.
- // Note that EINTR can happen in practice in rename() :-(
- if (HANDLE_EINTR(rename(temp_pid_path.c_str(), pid_path.c_str())) < 0) {
- *error_message = "Cannot rename pid file: ";
- *error_message += strerror(errno);
- return {};
- }
-
- // Remove stale socket if any.
- if (server_pid > 0)
- (void)unlink(address.path());
-
-#endif // !USE_LINUX_NAMESPACE
-
- int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (server_fd == -1) {
- SetErrnoMessage(error_message);
- return {};
- }
- if (bind(server_fd, address.address(), address.size()) < 0 ||
- listen(server_fd, 1) < 0) {
- CloseFd(server_fd);
- SetErrnoMessage(error_message);
- return {};
- }
- IpcServiceHandle result(server_fd, address.path());
- return result;
-}
-
-IpcHandle IpcServiceHandle::AcceptClient(std::string* error_message) const {
- int client = HANDLE_EINTR(accept(handle_, nullptr, nullptr));
- if (client < 0) {
- SetErrnoMessage(error_message);
- return {};
- }
- return { client };
-}
-
-IpcHandle IpcServiceHandle::AcceptClient(int64_t timeout_ms, bool* did_timeout,
- std::string* error_message) const {
- PosixIoConditionWaiter waiter(handle_, false);
- if (!waiter.Wait(timeout_ms, did_timeout, error_message))
- return {};
-
- int client = HANDLE_EINTR(accept(handle_, nullptr, nullptr));
- if (client < 0) {
- SetErrnoMessage(error_message);
- return {};
- }
- return { client };
-}
-
-// static
-bool IpcServiceHandle::IsBound(StringPiece service_name) {
- LocalAddress address(service_name);
-#if !USE_LINUX_NAMESPACE
- std::string pid_path = address.pid_path();
- std::string error;
- int server_pid = ReadPidFile(pid_path, &error);
- if (server_pid <= 0) {
- // No pid file means there is no server running.
- return false;
- } else if (kill(server_pid, 0) == 0) {
- // Server is still running.
- return true;
- }
-#endif // !USE_LINUX_NAMESPACE
- int server_fd = CreateUnixSocket(false);
- if (server_fd == -1)
- return false;
-
- bool result;
- if (bind(server_fd, address.address(), address.size()) == 0) {
- // fprintf(stderr, "IsBound(): bind() succeeded!\n");
- result = false;
- } else if (errno == EADDRINUSE) {
- // fprintf(stderr, "IsBound(): address in use!\n");
- result = true;
- } else {
- // fprintf(stderr, "IsBound(): bind() returned error: %s\n",
- // strerror(errno));
- result = false;
- }
- CloseFd(server_fd);
- return result;
-}
-
-// static
-IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name,
- std::string* error_message) {
- LocalAddress address(service_name);
- if (!address.valid()) {
- *error_message = std::string("Service name too long");
- return {};
- }
- int client_fd = CreateUnixSocket(false);
- if (client_fd == -1) {
- SetErrnoMessage(error_message);
- return {};
- }
- if (HANDLE_EINTR(connect(client_fd, address.address(), address.size())) < 0) {
- SetErrnoMessage(error_message);
- CloseFd(client_fd);
- return {};
- }
- return { client_fd };
-}
-
-// static
-IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name,
- int64_t timeout_ms, bool* did_timeout,
- std::string* error_message) {
- bool did_connect = false;
- *did_timeout = false;
- IpcHandle client = AsyncConnectTo(service_name, &did_connect, error_message);
- if (!client)
- return {};
-
- if (did_connect)
- return client;
-
- PosixIoConditionWaiter waiter(client.native_handle(), false);
- if (!waiter.Wait(timeout_ms, did_timeout, error_message)) {
- return false;
- }
-
- int so_error = GetSocketConnectStatus(client.native_handle());
- if (so_error != 0) {
- SetErrnoMessage(error_message);
- return {};
- }
-
- client.SetNonBlocking(false);
- return client;
-}
-
-// static
-IpcHandle IpcServiceHandle::AsyncConnectTo(StringPiece service_name,
- bool* did_connect,
- std::string* error_message) {
- LocalAddress address(service_name);
- if (!address.valid()) {
- *error_message = std::string("Service name too long");
- return {};
- }
- int client_fd = CreateUnixSocket(true);
- if (client_fd == -1) {
- SetErrnoMessage(error_message);
- return {};
- }
-
- if (!HANDLE_EINTR(connect(client_fd, address.address(), address.size()))) {
- // Connection completed immediately!
- *did_connect = true;
- return { client_fd };
- }
-
- if (errno == EINPROGRESS) {
- // Connection could not be completed immediately.
- *did_connect = false;
- return { client_fd };
- }
-
- SetErrnoMessage(error_message);
- CloseFd(client_fd);
- return {};
-}
-
-// static
-int IpcHandle::GetNativeAsyncConnectStatus(int fd) {
- return GetSocketConnectStatus(fd);
-}
-
-// static
-bool IpcHandle::CreatePipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message) {
- int fds[2] = { -1, -1 };
- if (pipe(fds) != 0)
- return SetErrnoMessage(error_message);
-
- *read = fds[0];
- *write = fds[1];
- return true;
-}
-
-bool IpcHandle::CreateAsyncPipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message) {
- if (!CreatePipe(read, write, error_message))
- return false;
-
- read->SetNonBlocking(true);
- write->SetNonBlocking(true);
- return true;
-}
-
-bool IpcHandle::ReadFull(void* buffer, size_t buffer_size,
- std::string* error_message) const {
- ssize_t count = Read(buffer, buffer_size, error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(buffer_size)) {
- *error_message =
- StringFormat("Received %zu bytes, expected %zu", count, buffer_size);
- return false;
- }
- return true;
-}
-
-bool IpcHandle::WriteFull(const void* buffer, size_t buffer_size,
- std::string* error_message) const {
- ssize_t count = Write(buffer, buffer_size, error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(buffer_size)) {
- *error_message =
- StringFormat("Sent %zu bytes, expected %zu", count, buffer_size);
- return false;
- }
- return true;
-}
-
-std::string IpcHandle::display() const {
- return StringFormat("fd=%d", handle_);
-}
diff --git a/src/ipc_handle-win32.cc b/src/ipc_handle-win32.cc
deleted file mode 100644
index a28f4a6..0000000
--- a/src/ipc_handle-win32.cc
+++ /dev/null
@@ -1,498 +0,0 @@
-// Copyright 2023 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.
-
-// Must appear before <stdio.h> which includes <fileapi.h> which will complain
-// of "No Target Architecture" for some unknown reason (!?)
-#include <fcntl.h>
-#include <stdio.h>
-#include <windows.h>
-
-// Must appear after <windows.h>
-#include <io.h>
-#include <lmcons.h> // required for UNLEN
-
-#include "ipc_handle.h"
-#include "util.h" // For StringFormat() and GetLastErrorString()
-
-namespace {
-
-std::string Win32ErrorMessage(const char* prefix, LONG error) {
- return StringFormat("%s: %s", prefix, GetLastErrorString(error).c_str());
-}
-
-std::string Win32ErrorMessage(const char* prefix) {
- return Win32ErrorMessage(prefix, GetLastError());
-}
-
-std::wstring CurrentUserName() {
- wchar_t user[UNLEN + 1];
- DWORD count = UNLEN + 1;
- if (!GetUserNameW(user, &count) || count < 2) {
- return std::wstring(L"unknown_user");
- }
- // count includes the terminating zero.
- return std::wstring(user, count - 1);
-}
-
-std::wstring GetNamedPipePath(StringPiece service_name) {
- std::wstring result = L"\\\\.\\pipe\\basic_ipc-";
- result += CurrentUserName();
- result += L'-';
- result += ConvertToUnicodeString(service_name.str_, service_name.len_);
- return result;
-}
-
-HANDLE CreateNamedPipeHandle(const std::wstring& pipe_path, bool async,
- std::string* error_message) {
- HANDLE handle = CreateNamedPipeW(
- pipe_path.data(),
- PIPE_ACCESS_DUPLEX | (async ? FILE_FLAG_OVERLAPPED : 0), // bidirectional
- PIPE_TYPE_BYTE | // write bytes, not messages
- PIPE_READMODE_BYTE | // read bytes, not messages
- PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // local only
- 1, // max. instances
- 4096, // out buffer size
- 4096, // in buffer size
- 0, // default time out
- nullptr); // default security attribute
- if (handle == INVALID_HANDLE_VALUE)
- *error_message = Win32ErrorMessage("Could not create named pipe");
-
- return handle;
-}
-
-HANDLE ConnectToNamedPipe(const std::wstring& pipe_path, bool async,
- std::string* error_message) {
- HANDLE handle = CreateFileW(pipe_path.data(), GENERIC_READ | GENERIC_WRITE,
- 0, // no sharing
- nullptr, // default security attributes
- CREATE_NEW, // fail if it already exists
- async ? FILE_FLAG_OVERLAPPED : 0,
- nullptr); // no template file
- if (handle == INVALID_HANDLE_VALUE)
- *error_message = Win32ErrorMessage("Coult not connect pipe");
-
- return handle;
-}
-
-std::wstring GetUniqueNamedPipePath() {
- // Do not use CreatePipe because these are documented as only
- // unidirectional and synchronous only. Instead create a
- // named pipe with a unique name. This matches the current
- // implementation in Windows, though it may change in future
- // releases of the platform, see:
- // https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe/51448441#51448441
- static LONG serial_number = 1;
- wchar_t pipe_path[64];
- swprintf_s(pipe_path, 64, L"\\\\.\\pipe\\IpcHandle.%08x.%08x",
- GetCurrentProcessId(), InterlockedIncrement(&serial_number));
- return std::wstring(pipe_path);
-}
-
-} // namespace
-
-// INVALID_HANDLE_VALUE expands to an expression that includes
-// a reinterpret_cast<>, which is why it cannot be used to
-// define a constexpr static member.
-
-// static
-const HANDLE IpcHandle::kInvalid = INVALID_HANDLE_VALUE;
-
-// static
-void IpcHandle::Close() {
- if (handle_ != kInvalid) {
- CloseHandle(handle_);
- handle_ = kInvalid;
- }
-}
-
-HANDLE IpcHandle::ReleaseNativeHandle() {
- HANDLE result = handle_;
- handle_ = INVALID_HANDLE_VALUE;
- return result;
-}
-
-bool IpcHandle::IsInheritable() const {
- DWORD flags;
- if (!GetHandleInformation(handle_, &flags))
- return false;
- return (flags & HANDLE_FLAG_INHERIT) != 0;
-}
-
-void IpcHandle::SetInheritable(bool enabled) {
- if (!SetHandleInformation(handle_, HANDLE_FLAG_INHERIT,
- enabled ? HANDLE_FLAG_INHERIT : 0)) {
- Win32Fatal("SetHandleInformation");
- }
-}
-
-// static
-IpcHandle::HandleType IpcHandle::CloneNativeHandle(HandleType handle,
- bool inherited) {
- HANDLE process = GetCurrentProcess();
- HANDLE peer = INVALID_HANDLE_VALUE;
- if (!DuplicateHandle(process, handle, process, &peer, 0, inherited,
- DUPLICATE_SAME_ACCESS)) {
- return INVALID_HANDLE_VALUE;
- }
- return peer;
-}
-
-// static
-IpcHandle::HandleType IpcHandle::NativeForStdio(FILE* file) {
- int fd = _fileno(file);
- intptr_t int_handle = _get_osfhandle(fd);
- if (int_handle < 0)
- return INVALID_HANDLE_VALUE;
- return reinterpret_cast<HANDLE>(int_handle);
-}
-
-// static
-IpcHandle IpcHandle::CloneFromStdio(FILE* file) {
- return { CloneNativeHandle(NativeForStdio(file)) };
-}
-
-bool IpcHandle::CloneIntoStdio(FILE* file) {
- HANDLE new_handle = CloneNativeHandle(handle_);
- // Ensure low-level Win32 operations will write to the new handle.
- // by calling SetStdHandle.
- DWORD std_index = 0;
- int std_fd;
- int open_flags;
- if (file == stdout) {
- std_index = STD_OUTPUT_HANDLE;
- std_fd = 1;
- open_flags = _O_WRONLY | _fmode;
- } else if (file == stderr) {
- std_index = STD_ERROR_HANDLE;
- std_fd = 2;
- open_flags = _O_WRONLY | _fmode;
- } else if (file == stdin) {
- std_index = STD_INPUT_HANDLE;
- std_fd = 0;
- open_flags = _O_RDONLY | _fmode;
- } else {
- errno = EINVAL;
- return false;
- }
- SetStdHandle(std_index, new_handle);
- int new_fd =
- ::_open_osfhandle(reinterpret_cast<intptr_t>(new_handle), open_flags);
- ::_dup2(new_fd, std_fd);
- ::close(new_fd);
- return true;
-}
-
-ssize_t IpcHandle::Read(void* buff, size_t buffer_size,
- std::string* error_message) const {
- auto* buffer = static_cast<char*>(buff);
- DWORD count = 0;
- if (!ReadFile(handle_, buffer, buffer_size, &count, nullptr)) {
- DWORD error = GetLastError();
- if (error == ERROR_BROKEN_PIPE) // Pipe was closed.
- return 0;
- *error_message = Win32ErrorMessage("Could not read from pipe");
- return -1;
- }
- return static_cast<ssize_t>(count);
-}
-
-ssize_t IpcHandle::Write(const void* buff, size_t buffer_size,
- std::string* error_message) const {
- const auto* buffer = static_cast<const char*>(buff);
- DWORD count = 0;
- if (!WriteFile(handle_, buffer, buffer_size, &count, nullptr)) {
- LONG error = GetLastError();
- if (error == ERROR_BROKEN_PIPE) // Pipe was closed.
- return 0;
- *error_message = Win32ErrorMessage("Could not write to pipe", error);
- return -1;
- }
- return static_cast<ssize_t>(count);
-}
-
-struct HandleMessage {
- HANDLE process_id;
- HANDLE handle;
-};
-
-bool IpcHandle::SendNativeHandle(HandleType native,
- std::string* error_message) const {
- // Send a message that contains the current process ID, and the handle
- // through the named pipe. The ReceiveNativeHandle() method will use them
- // to call DuplicateHandle(). Note that this does not work for console
- // input/output handles (the handles can be duplicated, but trying to use them
- // from a different process returns an error), so in addition to using this
- // method, the sender should use a specialized class to redirect stdout/stderr
- // using new named pipe handles. See the Win32StdOutputBridge class.
- HandleMessage msg;
- msg.process_id =
- OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
- msg.handle = native;
-
- ssize_t count = Write(&msg, sizeof(msg), error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(sizeof(msg))) {
- *error_message = "Error when sending handle";
- return false;
- }
- return true;
-}
-
-bool IpcHandle::ReceiveNativeHandle(IpcHandle* handle,
- std::string* error_message) const {
- HandleMessage msg;
- ssize_t count = Read(&msg, sizeof(msg), error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(sizeof(msg))) {
- *error_message = "Error when receiving handle";
- return false;
- }
-
- // Create a duplicate of the source handle in the current process.
- HANDLE native = INVALID_HANDLE_VALUE;
- if (!DuplicateHandle(msg.process_id, // source process
- msg.handle, // source handle
- GetCurrentProcess(), // target process
- &native, // target handle pointer
- 0, // ignored with DUPLICATE_SAME_ACCESS
- FALSE, // not inheritable
- DUPLICATE_SAME_ACCESS)) {
- *error_message = Win32ErrorMessage("Could not duplicate handle");
- return false;
- }
- *handle = IpcHandle(native);
- return true;
-}
-
-IpcServiceHandle::~IpcServiceHandle() {
- Close();
-}
-
-void IpcServiceHandle::Close() {
- this->IpcHandle::Close();
-}
-
-// static
-IpcServiceHandle IpcServiceHandle::BindTo(StringPiece service_name,
- std::string* error_message) {
- return IpcServiceHandle(CreateNamedPipeHandle(GetNamedPipePath(service_name),
- true, error_message));
-}
-
-IpcHandle IpcServiceHandle::AcceptClient(std::string* error_message) const {
- // Duplicate the handle to return it into a new IpcHandle. This
- // will allow the caller to communicate with the client, and the next
- // ConnectNamedPipe() call will wait for another client instead.
- HANDLE peer = IpcHandle::CloneNativeHandle(handle_);
- if (peer == INVALID_HANDLE_VALUE) {
- DWORD error = GetLastError();
- *error_message =
- Win32ErrorMessage("Could not duplicate client pipe handle", error);
- return {};
- }
- if (!ConnectNamedPipe(peer, NULL)) {
- DWORD error = GetLastError();
- // ERROR_PIPE_CONNECTED is not an actual error and means that
- // a client is already connected, which happens during unit-testing.
- if (error != ERROR_PIPE_CONNECTED) {
- ::CloseHandle(peer);
- *error_message =
- Win32ErrorMessage("Could not accept named pipe client", error);
- return {};
- }
- }
- return IpcHandle(peer);
-}
-
-IpcHandle IpcServiceHandle::AcceptClient(int64_t timeout_ms, bool* did_timeout,
- std::string* error_message) const {
- *did_timeout = false;
- // Duplicate the handle to return it into a new IpcHandle. This
- // will allow the caller to communicate with the client, and the next
- // ConnectNamedPipe() call will wait for another client instead.
- HANDLE peer = IpcHandle::CloneNativeHandle(handle_);
- if (peer == INVALID_HANDLE_VALUE) {
- DWORD error = GetLastError();
- *error_message =
- Win32ErrorMessage("Could not duplicate client pipe handle", error);
- return {};
- }
-
- HANDLE result = INVALID_HANDLE_VALUE;
- OVERLAPPED overlapped = {};
- overlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
-
- do {
- if (ConnectNamedPipe(peer, &overlapped)) {
- // Should not happen with overlapped i/o, but just in case.
- result = peer;
- peer = INVALID_HANDLE_VALUE;
- break;
- }
-
- DWORD error = GetLastError();
- if (error == ERROR_PIPE_CONNECTED) {
- // Immediate connection, success!
- result = peer;
- peer = INVALID_HANDLE_VALUE;
- break;
- }
-
- if (error != ERROR_IO_PENDING) {
- // Something unexpected happened here!
- *error_message =
- Win32ErrorMessage("Could not accept named pipe client", error);
- break;
- }
-
- // Wait for connection.
- DWORD timeout =
- (timeout_ms < 0) ? INFINITE : static_cast<DWORD>(timeout_ms);
- DWORD ret = WaitForSingleObject(overlapped.hEvent, timeout);
-
- if (ret == WAIT_TIMEOUT) {
- *did_timeout = true;
- *error_message = "timed out";
- break;
- }
-
- if (ret != WAIT_OBJECT_0) {
- Win32Fatal("WaitForSingleObject");
- }
-
- // Success!
- result = peer;
- peer = INVALID_HANDLE_VALUE;
-
- } while (0);
-
- ::CloseHandle(overlapped.hEvent);
- if (peer != INVALID_HANDLE_VALUE)
- ::CloseHandle(peer);
-
- return { result };
-}
-
-// static
-bool IpcServiceHandle::IsBound(StringPiece service_name) {
- std::wstring pipe_path = GetNamedPipePath(service_name);
- if (WaitNamedPipeW(pipe_path.data(), 1))
- return true;
-
- DWORD error = GetLastError();
- if (error == ERROR_SEM_TIMEOUT)
- return true;
-
- return false;
-}
-
-// static
-IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name,
- std::string* error_message) {
-#if 0
- // For some reason, the line below generates code that crashes
- // at runtime when compiled with the Mingw64 toolchain, so use
- // the equivalent below.
- return { ConnectToNamedPipe(GetNamedPipePath(service_name), true, error_message) };
-#else
- std::wstring pipe_path = GetNamedPipePath(service_name);
- HANDLE handle = ConnectToNamedPipe(pipe_path, true, error_message);
- return { handle };
-#endif
-}
-
-// static
-IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name,
- int64_t timeout_ms, bool* did_timeout,
- std::string* error_message) {
- // On Windows, connecting to a named pipe either succeeds or fails immediately
- *did_timeout = (timeout_ms > 0);
- return ConnectTo(service_name, error_message);
-}
-
-// static
-IpcHandle IpcServiceHandle::AsyncConnectTo(StringPiece service_name,
- bool* did_connect,
- std::string* error_message) {
- IpcHandle result =
- ConnectToNamedPipe(GetNamedPipePath(service_name), true, error_message);
- if (result) {
- // On Win32, connecting to a named pipe always fails or succeeds
- // immediately.
- *did_connect = true;
- }
- return result;
-}
-
-static bool CreatePipeInternal(IpcHandle* read, IpcHandle* write, bool async,
- std::string* error_message) {
- std::wstring pipe_path = GetUniqueNamedPipePath();
- *read = IpcHandle(
- CreateNamedPipeHandle(std::wstring(pipe_path), async, error_message));
- if (!*read)
- return false;
-
- *write = IpcHandle(ConnectToNamedPipe(pipe_path, async, error_message));
- if (!*write) {
- read->Close();
- return false;
- }
- return true;
-}
-
-// static
-bool IpcHandle::CreatePipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message) {
- return CreatePipeInternal(read, write, false, error_message);
-}
-
-// static
-bool IpcHandle::CreateAsyncPipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message) {
- return CreatePipeInternal(read, write, true, error_message);
-}
-
-bool IpcHandle::ReadFull(void* buffer, size_t buffer_size,
- std::string* error_message) const {
- ssize_t count = Read(buffer, buffer_size, error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(buffer_size)) {
- *error_message = StringFormat("Received %u bytes, expected %u",
- static_cast<unsigned>(count),
- static_cast<unsigned>(buffer_size));
- return false;
- }
- return true;
-}
-
-bool IpcHandle::WriteFull(const void* buffer, size_t buffer_size,
- std::string* error_message) const {
- ssize_t count = Write(buffer, buffer_size, error_message);
- if (count < 0)
- return false;
- if (count != static_cast<ssize_t>(buffer_size)) {
- *error_message =
- StringFormat("Sent %u bytes, expected %u", static_cast<unsigned>(count),
- static_cast<unsigned>(buffer_size));
- }
- return true;
-}
-
-std::string IpcHandle::display() const {
- return StringFormat("handle=%p", handle_);
-}
diff --git a/src/ipc_handle.h b/src/ipc_handle.h
deleted file mode 100644
index f58f7cd..0000000
--- a/src/ipc_handle.h
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2023 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_IPC_HANDLE_H_
-#define NINJA_IPC_HANDLE_H_
-
-#include <stddef.h>
-
-#include <string>
-#include <string_view>
-
-#include "string_piece.h"
-
-#ifdef _WIN32
-#include <basetsd.h>
-#include <windows.h>
-using ssize_t = SSIZE_T;
-#else
-#include <signal.h>
-#endif
-
-/// Support for basic inter-process communication.
-///
-/// The IpcHandle models a scoped pipe handle (Win32) or unix socket file
-/// descriptor (Unix) and provides method to read/write data through
-/// them, as well as passing other handles/file descriptors.
-///
-/// For Win32, all handles are created with FILE_FLAG_OVERLAPPED and
-/// support overlapped operations. For Posix, the non-blocking flag of
-/// a descriptor can be checked and changed with IsNonBlocking() and
-/// SetNonBlocking() methods.
-///
-
-/// Wrapper for a local Unix socket or Win32 named pipe handle
-/// used for inter-process communication.
-class IpcHandle {
- public:
-#ifdef _WIN32
- using HandleType = HANDLE;
- static const HandleType kInvalid;
-#else
- using HandleType = int;
- static constexpr int kInvalid = -1;
-#endif
-
- IpcHandle() = default;
- IpcHandle(HandleType handle) : handle_(handle) {}
-
- /// Disallow copy operations.
- IpcHandle(const IpcHandle&) = delete;
- IpcHandle& operator=(const IpcHandle&) = delete;
-
- /// Allow move operations.
- IpcHandle(IpcHandle&& other) noexcept : handle_(other.handle_) {
- other.handle_ = kInvalid;
- }
- IpcHandle& operator=(IpcHandle&& other) noexcept {
- if (this != &other) {
- this->~IpcHandle();
- new (this) IpcHandle(std::move(other));
- }
- return *this;
- }
-
- ~IpcHandle() { Close(); }
-
- /// bool conversion allows easy checks for valid handles with:
- /// if (!handle) { ... handle is invalid };
- explicit operator bool() const noexcept { return handle_ != kInvalid; }
-
- /// Try to read |buffer_size| bytes into |buffer|. On success
- /// return the number of bytes actually read into the buffer,
- /// which will be 0 if the connection was closed by the peer.
- /// On failure, return -1 and sets |*error_message|.
- ssize_t Read(void* buffer, size_t buffer_size,
- std::string* error_message) const;
-
- /// Read |buffer_size| bytes exactly into |buffer|. On success
- /// return true. On failure, return false and set |*error_message|.
- bool ReadFull(void* buffer, size_t buffer_size,
- std::string* error_message) const;
-
- /// Try to write |buffer_size| bytes form |buffer|. On success
- /// return the number of bytes actually written to the handle,
- /// which will be 0 if the connection was called by the peer.
- /// On failure, return -1 and sets |*error_message|.
- ssize_t Write(const void* buffer, size_t buffer_size,
- std::string* error_message) const;
-
- /// Write |buffer_size| bytes exactly from |buffer|. On success
- /// return true. On failure, return false and set |*error_message|.
- bool WriteFull(const void* buffer, size_t buffer_size,
- std::string* error_message) const;
-
- /// Send a |native| handle to the peer. On success return true.
- /// On failure, return false and sets |*error_message|.
- bool SendNativeHandle(HandleType native, std::string* error_message) const;
-
- /// Receive a |native| handle from the peer. On success return true
- /// and sets |*handle|. On failure, return false and sets |*error_message|.
- bool ReceiveNativeHandle(IpcHandle* handle, std::string* error_message) const;
-
- /// Return true if the handle is inheritable (on Win32), or is _not_ closed
- /// on exec (on Posix).
- bool IsInheritable() const;
-
- /// Reset the inheritable / non-O_CLOEXEC flag for this handle.
- void SetInheritable(bool enable);
-
-#ifndef _WIN32
- /// Return true if the file descriptor is in non-blocking mode.
- bool IsNonBlocking() const;
-
- /// Reset the O_NONBLOCK flag for the corresponding file descriptor.
- void SetNonBlocking(bool enable);
-
- /// Return the current status of an asynchronous AsyncClientTo() connection.
- /// This only makes sense on Posix, and this function should only be called
- /// after a writable event was detected on the native handle with select()
- /// or one of its variants. It returns an errno value, which will be 0 if
- /// the connection succeeded, EAGAIN or EINPROGRESS in case of a spurious
- /// select() wakeup, or any other error value in case of failed connection,
- /// or using an invalid handle.
- static int GetNativeAsyncConnectStatus(int fd);
-#endif
-
- /// Create anonymous bi-directional pipe. On success return true and
- /// sets |*read| and |*write|. On failure, return false and sets
- /// |*error_message|.
- static bool CreatePipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message);
-
- /// Same as CreatePipe() but returns handles that can be used for asychronous
- /// operations (i.e. O_NONBLOCK on Posix, and FILE_FLAG_OVERLAPPED on Win32).
- static bool CreateAsyncPipe(IpcHandle* read, IpcHandle* write,
- std::string* error_message);
-
- /// Close the handle, making it invalid.
- void Close();
-
- /// Clone a native handle.
- static HandleType CloneNativeHandle(HandleType handle,
- bool inherited = false);
-
- /// Clone existing instance. Result is not inheritable on Win32, and has
- /// O_CLOEXEC on Posix.
- IpcHandle Clone() { return { CloneNativeHandle(handle_) }; };
-
- /// Flush |file| then clone its handle. Result is not inheritable on Win32,
- /// and has O_CLOEXEC on Posix.
- static IpcHandle CloneFromStdio(FILE* file);
-
- /// Clone the handle into an stdio FILE instance.
- /// |file| can only be one of stdout, stderr, or stdin.
- /// On success return true, on failure, set errno (even on Win32)
- /// then return false.
- bool CloneIntoStdio(FILE* file);
-
- /// Return the native handle value for an stdio stream.
- static HandleType NativeForStdio(FILE* file);
-
- /// Return native handle value.
- HandleType native_handle() const { return handle_; }
-
- /// Release the native handle, transferring ownership to the caller.
- HandleType ReleaseNativeHandle();
-
- /// Return user visible string for this handle.
- std::string display() const;
-
- protected:
- HandleType handle_ = kInvalid;
-};
-
-/// Models an IpcHandle used to bind to specific service.
-/// Only one process can bind to a specific named service at
-/// a time on a given machine.
-class IpcServiceHandle : public IpcHandle {
- public:
- IpcServiceHandle() = default;
- ~IpcServiceHandle();
-
- IpcServiceHandle(IpcServiceHandle&&) noexcept = default;
- IpcServiceHandle& operator=(IpcServiceHandle&&) noexcept = default;
-
- /// Create a server handle for |service_name|, which is an arbitrary name
- /// used to identify a specific global service, which can have only one
- /// serving instance per (machine, user) combination.
- ///
- /// On success, return a valid IpcHandle, that can be used with
- /// AcceptClient(). On failure, return an invalid handle, and sets
- /// |*error_message|.
- ///
- /// This will fail if another server is already running with the
- /// same name on the current machine (for the same user). Closing
- /// the service handle will release the corresponding socket or
- /// named pipe immediately, and of course the destructor closes
- /// automatically.
- ///
- /// Note that the implementation should be resilient to program
- /// crashes as well, i.e. on Linux and Win32, it uses kernel features
- /// that ensure proper socket/pipe cleanup on process exit. On
- /// other Unix systems, a Unix-domain socket and associated PID file
- /// are used to detect stale socket files and remove them properly.
- static IpcServiceHandle BindTo(StringPiece service_name,
- std::string* error_message);
-
- /// Return true if a given service is already bound. This is racy by
- /// definition, but useful during unit-tests.
- static bool IsBound(StringPiece service_name);
-
- /// Accept one client connection. This is only valid for instances
- /// returned from BindTo().
- IpcHandle AcceptClient(std::string* error_message) const;
-
- /// Accept one client connection, with a timeout. On success, return a valid
- /// IpcHandle value. On failure, set |*did_timeout| to true if the timeout
- /// occured, and set |*error_message| to an error message.
- IpcHandle AcceptClient(int64_t timeout_ms, bool* did_timeout,
- std::string* error_message) const;
-
- /// Connect to the server implementing |service_name|.
- /// On success, return valid IpcHandle. On failure, set |*error_message|
- /// then return an invalid value.
- static IpcHandle ConnectTo(StringPiece service_name,
- std::string* error_message);
-
- /// Connect to the server implementing |service_name|, with a timeout
- /// in milliseconds. On success, return a valid IpcHandle. On error or
- /// timeout, set |*error_message| and |*did_timeout| before returning
- /// an invalid value.
- static IpcHandle ConnectTo(StringPiece service_name, int64_t timeout_ms,
- bool* did_timeout, std::string* error_message);
-
- /// Connect asychronously to local |service_name|. On success, set
- /// |*did_connect| and return a valid handle. On failure, set |*error_message|
- /// and return an invalid handle.
- ///
- /// Note that the connection can happen immediately, in which case
- /// |*did_connect| will be set to true.
- ///
- /// On Win32, the returned handle has FILE_FLAG_OVERLAPPED and connection
- /// always succeeds.
- ///
- /// On Posix, the returned handle is non-blocking, and the caller should
- /// wait for a writable event on it (using select() or one of its variants)
- /// then use GetNativeAsyncConnectStatus() on the native_handle() value.
- static IpcHandle AsyncConnectTo(StringPiece service_name, bool* did_connect,
- std::string* error_message);
-
- /// Close the handle, make it invalid and remove service socket.
- void Close();
-
- private:
-#ifdef _WIN32
- IpcServiceHandle(IpcHandle::HandleType handle) : IpcHandle(handle) {}
-#else
- IpcServiceHandle(IpcHandle::HandleType handle, const std::string& path)
- : IpcHandle(handle), socket_path_(path) {}
-
- std::string socket_path_;
-#endif
-};
-
-#ifndef _WIN32
-/// Helper class to temporarily disable SIGPIPE, which halts
-/// the current process by default on Posix. These happen when IPC
-/// pipes are broken by the client.
-class SigPipeIgnore {
- public:
- SigPipeIgnore() {
- struct sigaction new_handler = {};
- new_handler.sa_handler = SIG_IGN;
- sigaction(SIGPIPE, &new_handler, &prev_handler_);
- }
-
- ~SigPipeIgnore() { sigaction(SIGPIPE, &prev_handler_, nullptr); }
-
- private:
- struct sigaction prev_handler_;
-};
-#endif // !_WIN32
-
-#endif // NINJA_IPC_HANDLE_H_
diff --git a/src/ipc_handle_test.cc b/src/ipc_handle_test.cc
deleted file mode 100644
index 47412e2..0000000
--- a/src/ipc_handle_test.cc
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2023 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 "ipc_handle.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include "test.h"
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <fcntl.h>
-#include <unistd.h>
-#endif
-
-namespace {
-
-IpcHandle::HandleType CreateTestNativeHandle() {
-#ifdef _WIN32
- return CreateFileW(L"NUL", GENERIC_READ | GENERIC_WRITE, 0, nullptr,
- OPEN_EXISTING, 0, NULL);
-#else
- return open("/dev/null", O_RDWR);
-#endif
-}
-
-IpcHandle CreateTestHandle() {
- return { CreateTestNativeHandle() };
-}
-
-// Create a pipe for testing. On success return true and
-// set |*read| and |*write| to be the read and write ends
-// of the pipe. Note that the pipe is unidirectional due
-// to portability with Windows.
-bool CreateTestPipe(IpcHandle* read, IpcHandle* write) {
- std::string error;
- if (!IpcHandle::CreatePipe(read, write, &error)) {
- fprintf(stderr, "ERROR: when creating pipe: %s\n", error.c_str());
- return false;
- }
- return true;
-}
-
-} // namespace
-
-TEST(IpcHandle, Constructor) {
- // Default constructor makes invalid handle
- IpcHandle empty;
- EXPECT_FALSE(empty);
- EXPECT_EQ(IpcHandle::kInvalid, empty.native_handle());
-
- // Constructor gets test native handle
- IpcHandle::HandleType test_native_handle = CreateTestNativeHandle();
- IpcHandle test_handle(test_native_handle);
- EXPECT_TRUE(test_handle);
- EXPECT_EQ(test_native_handle, test_handle.native_handle());
-
- // Move constructor works.
- IpcHandle test_handle2 = std::move(test_handle);
- EXPECT_TRUE(test_handle2);
- EXPECT_EQ(test_native_handle, test_handle2.native_handle());
- EXPECT_FALSE(test_handle);
- EXPECT_EQ(IpcHandle::kInvalid, test_handle.native_handle());
-}
-
-TEST(IpcHandle, Close) {
- IpcHandle test_handle = CreateTestHandle();
- EXPECT_TRUE(test_handle);
- EXPECT_NE(IpcHandle::kInvalid, test_handle.native_handle());
-
- test_handle.Close();
- EXPECT_FALSE(test_handle);
- EXPECT_EQ(IpcHandle::kInvalid, test_handle.native_handle());
-
- // Ensure closing a closed handle doesn't crash.
- test_handle.Close();
- EXPECT_FALSE(test_handle);
- EXPECT_EQ(IpcHandle::kInvalid, test_handle.native_handle());
-}
-
-TEST(IpcHandle, ReadWrite) {
- const char kInputData[] = "Hello World!";
- const size_t kInputDataSize = sizeof(kInputData); // includes terminating \0
- IpcHandle pipe_read;
- IpcHandle pipe_write;
- ASSERT_TRUE(CreateTestPipe(&pipe_read, &pipe_write));
-
- std::string error_message;
- ssize_t count = pipe_write.Write(kInputData, kInputDataSize, &error_message);
- EXPECT_EQ(static_cast<ssize_t>(kInputDataSize), count);
- EXPECT_TRUE(error_message.empty());
-
- char data[kInputDataSize] = {};
- count = pipe_read.Read(data, sizeof(data), &error_message);
- EXPECT_EQ(static_cast<ssize_t>(kInputDataSize), count);
- EXPECT_TRUE(error_message.empty());
-
- // Check transfered content.
- EXPECT_TRUE(!memcmp(data, kInputData, kInputDataSize));
-
- // Close the write pipe, trying to read should result in zero
- // being returned without an error (indicating closed pipe).
- pipe_write.Close();
- count = pipe_read.Read(data, kInputDataSize, &error_message);
- EXPECT_EQ(0, count);
- EXPECT_TRUE(error_message.empty());
-}
-
-TEST(IpcHandle, Clone) {
- const char kInputData[] = "Hello World!";
- const size_t kInputDataSize = sizeof(kInputData); // includes terminating \0
- IpcHandle pipe_read;
- IpcHandle pipe_write;
- ASSERT_TRUE(CreateTestPipe(&pipe_read, &pipe_write));
-
- // Clone pipe_read, then close it.
- IpcHandle pipe_read2 = pipe_read.Clone();
- pipe_read.Close();
-
- std::string error_message;
- ssize_t count = pipe_write.Write(kInputData, kInputDataSize, &error_message);
- EXPECT_EQ(static_cast<ssize_t>(kInputDataSize), count);
- EXPECT_TRUE(error_message.empty());
-
- char data[kInputDataSize] = {};
- count = pipe_read2.Read(data, sizeof(data), &error_message);
- EXPECT_EQ(static_cast<ssize_t>(kInputDataSize), count);
- EXPECT_TRUE(error_message.empty());
-
- // Check transfered content.
- EXPECT_TRUE(!memcmp(data, kInputData, kInputDataSize));
-
- // Close the write pipe, trying to read should result in zero
- // being returned without an error (indicating closed pipe).
- pipe_write.Close();
- count = pipe_read2.Read(data, kInputDataSize, &error_message);
- EXPECT_EQ(0, count);
- EXPECT_TRUE(error_message.empty());
-}
-
-TEST(IpcHandle, ReadWriteFull) {
- const char kInputData[] = "Hello World!";
- const size_t kInputDataSize = sizeof(kInputData); // includes terminating \0
- IpcHandle pipe_read;
- IpcHandle pipe_write;
- ASSERT_TRUE(CreateTestPipe(&pipe_read, &pipe_write));
-
- std::string error_message;
- EXPECT_TRUE(pipe_write.WriteFull(kInputData, kInputDataSize, &error_message));
- EXPECT_TRUE(error_message.empty());
-
- char data[kInputDataSize] = {};
- EXPECT_TRUE(pipe_read.ReadFull(data, kInputDataSize, &error_message));
- EXPECT_TRUE(error_message.empty());
-
- // Check transfered content.
- EXPECT_TRUE(!memcmp(data, kInputData, kInputDataSize));
-
- // Close the write pipe, trying to read should result in an error.
- pipe_write.Close();
- EXPECT_FALSE(pipe_read.ReadFull(data, kInputDataSize, &error_message));
- EXPECT_FALSE(error_message.empty());
-}
-
-TEST(IpcHandle, BindConnectAndAccept) {
- std::string error_message;
- StringPiece service = "test_service";
-
- ASSERT_FALSE(IpcServiceHandle::IsBound(service));
-
- // Tring to connect to a service without a server should fail.
- IpcHandle no_client = IpcServiceHandle::ConnectTo(service, &error_message);
- EXPECT_FALSE(no_client);
- EXPECT_FALSE(error_message.empty());
-
- ASSERT_FALSE(IpcServiceHandle::IsBound(service));
-
- // Create server
- IpcServiceHandle server = IpcServiceHandle::BindTo(service, &error_message);
- ASSERT_TRUE(server);
- EXPECT_FALSE(error_message.empty());
- error_message.clear();
-
- ASSERT_TRUE(IpcServiceHandle::IsBound(service));
-
- // Trying to create a secondary server for the same service should fail.
- IpcServiceHandle no_server =
- IpcServiceHandle::BindTo(service, &error_message);
- ASSERT_FALSE(no_server);
- EXPECT_FALSE(error_message.empty());
- error_message.clear();
-
- // Connecto the server, and receive a peer handle.
- IpcHandle client = IpcServiceHandle::ConnectTo(service, &error_message);
- EXPECT_TRUE(client);
- EXPECT_TRUE(error_message.empty());
-
- IpcHandle peer = server.AcceptClient(&error_message);
- EXPECT_TRUE(peer);
- EXPECT_TRUE(error_message.empty());
-
- // Send data from the client to the peer.
- const char kInput[] = "sending data";
- const size_t kInputSize = sizeof(kInput);
- EXPECT_TRUE(client.WriteFull(kInput, kInputSize, &error_message));
- EXPECT_TRUE(error_message.empty());
-
- char output[kInputSize] = {};
- EXPECT_TRUE(peer.ReadFull(output, sizeof(output), &error_message));
- EXPECT_TRUE(error_message.empty());
-
- EXPECT_TRUE(!memcmp(output, kInput, kInputSize));
-
- // Close all handles and verify that IsBound() returns false.
- // Note that on Unix, only closing the server handle is necessary for this,
- // but the way Win32 named pipes work require all client handles closed too.
- peer.Close();
- client.Close();
- server.Close();
- ASSERT_FALSE(IpcServiceHandle::IsBound(service));
-}
-
-TEST(IpcHandle, BindAcceptWithTimeout) {
- std::string error_message;
- StringPiece service = "test_service";
-
- // Create server
- IpcServiceHandle server = IpcServiceHandle::BindTo(service, &error_message);
- ASSERT_TRUE(server);
- EXPECT_TRUE(error_message.empty());
-
- // Accepting should time out since no client is trying to connect.
- bool did_timeout = false;
- IpcHandle connection = server.AcceptClient(10, &did_timeout, &error_message);
- EXPECT_FALSE(connection);
- EXPECT_TRUE(did_timeout);
- EXPECT_FALSE(error_message.empty());
- error_message.clear();
-
- // Connecto the server, and receive a peer handle.
- IpcHandle client = IpcServiceHandle::ConnectTo(service, &error_message);
- EXPECT_TRUE(client);
- EXPECT_TRUE(error_message.empty());
-
- // Accepting should not work.
- connection = server.AcceptClient(10, &did_timeout, &error_message);
- EXPECT_TRUE(connection);
- EXPECT_FALSE(did_timeout);
- EXPECT_TRUE(error_message.empty());
-}
-
-TEST(IpcHandle, SendAndReceiveNativeHandle) {
- IpcHandle pipe1_read;
- IpcHandle pipe1_write;
- ASSERT_TRUE(CreateTestPipe(&pipe1_read, &pipe1_write));
-
- std::string error_message;
- StringPiece service = "test_service";
- IpcServiceHandle server = IpcServiceHandle::BindTo(service, &error_message);
- ASSERT_TRUE(server);
- EXPECT_TRUE(error_message.empty());
-
- IpcHandle client = IpcServiceHandle::ConnectTo(service, &error_message);
- EXPECT_TRUE(client);
- EXPECT_TRUE(error_message.empty());
-
- IpcHandle peer = server.AcceptClient(&error_message);
- EXPECT_TRUE(peer);
- EXPECT_TRUE(error_message.empty());
-
- // Send pipe1_write.native_handle() from client to peer.
- EXPECT_TRUE(
- client.SendNativeHandle(pipe1_write.native_handle(), &error_message));
- EXPECT_TRUE(error_message.empty());
-
- IpcHandle received;
- EXPECT_TRUE(peer.ReceiveNativeHandle(&received, &error_message));
- EXPECT_TRUE(error_message.empty());
-
- // Write to |received|, this should send data to the first pipe.
- const char kInputData[] = "Bonjour monde!";
- const size_t kInputDataSize = sizeof(kInputData);
-
- EXPECT_TRUE(received.WriteFull(kInputData, kInputDataSize, &error_message));
- EXPECT_TRUE(error_message.empty());
-
- char data[kInputDataSize] = {};
- ssize_t count = pipe1_read.Read(data, sizeof(data), &error_message);
- EXPECT_EQ(static_cast<ssize_t>(sizeof(data)), count);
- EXPECT_TRUE(error_message.empty());
-
- // Verify content.
- EXPECT_TRUE(!memcmp(data, kInputData, kInputDataSize));
-}
diff --git a/src/ipc_utils.cc b/src/ipc_utils.cc
deleted file mode 100644
index ca08848..0000000
--- a/src/ipc_utils.cc
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2023 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 "ipc_utils.h"
-
-#include "util.h"
-
-#ifndef _WIN32
-#include <sys/signal.h>
-#include <unistd.h>
-#endif
-
-RemoteArguments::RemoteArguments(int argc, char** argv) {
- if (argc > 0) {
- args_.reserve(static_cast<size_t>(argc));
- for (int n = 0; n < argc; ++n) {
- args_.push_back(argv[n]);
- }
- }
-}
-
-void RemoteArguments::Reset(const std::vector<std::string>& args) {
- args_ = args;
-}
-
-char** RemoteArguments::argv() const {
- if (args_.empty())
- return nullptr;
-
- argv_.clear();
- argv_.reserve(args_.size());
- for (const auto& arg : args_)
- argv_.push_back(const_cast<char*>(arg.data()));
- return argv_.data();
-}
-
-void RemoteArguments::InsertAt(size_t pos, std::string arg) {
- if (pos > args_.size())
- pos = args_.size();
- args_.insert(args_.begin() + pos, std::move(arg));
-}
-
-template <>
-bool RemoteWrite<std::string>(const std::string& str, IpcHandle& con,
- std::string* error) {
- size_t size = str.size();
- return RemoteWrite(size, con, error) &&
- con.WriteFull(str.data(), size, error);
-}
-
-template <>
-bool RemoteWrite<RemoteArguments>(const RemoteArguments& args, IpcHandle& con,
- std::string* error) {
- size_t size = args.args().size();
- if (!RemoteWrite(size, con, error))
- return false;
- for (const auto& arg : args.args()) {
- if (!RemoteWrite(arg, con, error))
- return false;
- }
- return true;
-}
-
-template <>
-bool RemoteRead<std::string>(std::string& str, IpcHandle& con,
- std::string* error) {
- size_t size;
- if (!RemoteRead(size, con, error))
- return false;
- str.resize(size);
- if (!size)
- return true;
- return con.ReadFull(const_cast<char*>(str.data()), size, error);
-}
-
-template <>
-bool RemoteRead<RemoteArguments>(RemoteArguments& args, IpcHandle& con,
- std::string* error) {
- size_t size;
- if (!RemoteRead(size, con, error))
- return false;
- std::vector<std::string> vec;
- vec.resize(size);
- for (auto& arg : vec) {
- if (!RemoteRead(arg, con, error))
- return false;
- }
- args.Reset(std::move(vec));
- return true;
-}
-
-void WireEncoder::Write(const void* buffer, size_t size) {
- result_.append(static_cast<const char*>(buffer), size);
-}
-
-void WireEncoder::Write(const std::string& str) {
- Write(static_cast<uint32_t>(str.size()));
- Write(str.data(), str.size());
-}
-
-WireDecoder::WireDecoder(const void* buffer, size_t size)
- : p_(static_cast<const char*>(buffer)), end_(p_ + size) {}
-
-WireDecoder::WireDecoder(const std::string& str)
- : WireDecoder(str.data(), str.size()) {}
-
-void WireDecoder::Read(void* buffer, size_t size) {
- if (p_ + size <= end_) {
- ::memcpy(buffer, p_, size);
- p_ += size;
- } else {
- // To avoid relying on un-initialized memory at runtime
- // if the client fails to check for errors.
- ::memset(buffer, '\0', size);
- p_ = end_;
- has_error_ = true;
- }
-}
-
-void WireDecoder::Read(std::string& str) {
- uint32_t size = 0;
- Read(size);
- std::string result;
- if (size) {
- result.resize(size);
- Read(const_cast<char*>(result.data()), result.size());
- }
- str = std::move(result);
-}
-
-#ifndef _WIN32
-SigPipeBlocker::SigPipeBlocker() {
- struct sigaction action = {};
- action.sa_handler = SIG_IGN;
- if (sigaction(SIGPIPE, &action, &prev_action_) < 0)
- ErrnoFatal("sigaction");
-}
-
-SigPipeBlocker::~SigPipeBlocker() {
- if (sigaction(SIGPIPE, &prev_action_, nullptr) < 0)
- ErrnoFatal("sigaction");
-}
-#endif // !_WIN32
diff --git a/src/ipc_utils.h b/src/ipc_utils.h
deleted file mode 100644
index 8e3ecf0..0000000
--- a/src/ipc_utils.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2023 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_SRC_IPC_UTILS_H_
-#define NINJA_SRC_IPC_UTILS_H_
-
-#include <string>
-#include <vector>
-
-#include "ipc_handle.h"
-
-/// Convenience wrapper for an std::vector<std::string> that can save
-/// the content of an (argc, argv) list of command-line arguments.
-struct RemoteArguments {
- RemoteArguments() = default;
- RemoteArguments(int argc, char** argv);
-
- void Reset(const std::vector<std::string>& args);
-
- const std::vector<std::string>& args() const { return args_; }
-
- /// Export instance as (argc, argv) pair.
- int argc() const { return static_cast<int>(args_.size()); }
- char** argv() const;
-
- void InsertAt(size_t pos, std::string arg);
-
- protected:
- std::vector<std::string> args_;
- mutable std::vector<char*> argv_;
-};
-
-/// Helper template to write values through an IpcHandle.
-template <typename T>
-bool RemoteWrite(const T& obj, IpcHandle& con, std::string* error) {
- return con.WriteFull(&obj, sizeof(obj), error);
-}
-
-template <>
-bool RemoteWrite<std::string>(const std::string& str, IpcHandle& con,
- std::string* error);
-
-template <>
-bool RemoteWrite<RemoteArguments>(const RemoteArguments& args, IpcHandle& con,
- std::string* error);
-
-/// Helper template to read values through an IpcHandle
-template <typename T>
-bool RemoteRead(T& obj, IpcHandle& con, std::string* error) {
- return con.ReadFull(&obj, sizeof(obj), error);
-}
-
-template <>
-bool RemoteRead<std::string>(std::string& str, IpcHandle& con,
- std::string* error);
-
-template <>
-bool RemoteRead<RemoteArguments>(RemoteArguments& args, IpcHandle& con,
- std::string* error);
-
-/// Simple class to encode values into a byte stream that
-/// can be sent through IPC to another process. Usage is:
-///
-/// 1) Create instance.
-/// 2) Call one of the Write() methods any number of times.
-/// 3) Call GetResult() to retrieve the result as an std::string.
-///
-struct WireEncoder {
- void Write(const void* buffer, size_t size);
- void Write(const std::string& str);
-
- template <typename T>
- void Write(const T& v) {
- Write(&v, sizeof(v));
- }
-
- std::string TakeResult() { return std::move(result_); }
-
- std::string result_;
-};
-
-/// Decode a string of bytes received through IPC into values.
-/// Usage is:
-/// 1) Create instance passing the input stream as an std::string
-/// 2) Call Read() methods as many times as necessary.
-/// 3) Check has_error() after each Read(), or after a series of
-/// Read() calls to see if there was any error (i.e. read overflow).
-///
-struct WireDecoder {
- WireDecoder(const void* buffer, size_t size);
- explicit WireDecoder(const std::string& str);
-
- void Read(void* buffer, size_t size);
- void Read(std::string& str);
-
- template <typename T>
- void Read(T& v) {
- Read(&v, sizeof(T));
- }
-
- bool has_error() const { return has_error_; }
-
- const char* p_ = nullptr;
- const char* end_ = nullptr;
- bool has_error_ = false;
-};
-
-#ifndef _WIN32
-/// Convenience class to ignore SIGPIPE signals when
-/// doing inter-process writes through a pipe.
-class SigPipeBlocker {
- public:
- SigPipeBlocker();
- ~SigPipeBlocker();
-
- private:
- struct sigaction prev_action_;
-};
-#endif // !_WIN32
-
-#endif // NINJA_SRC_IPC_UTILS_H_
diff --git a/src/ipc_utils_test.cc b/src/ipc_utils_test.cc
deleted file mode 100644
index 7edc5c0..0000000
--- a/src/ipc_utils_test.cc
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2023 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 "ipc_utils.h"
-
-#include "test.h"
-
-namespace {
-
-#ifndef _WIN32
-// Helper class used to catch SIGPIPE and set a global flag when
-// it happens instead of terminating the current process. Will be
-// used to verify that SigPipeBlocker works.
-struct SigPipeCatcher {
- SigPipeCatcher() {
- // Install signal handler.
- struct sigaction action = {};
- action.sa_handler = [](int) { sigpipe_happened_ = 1; };
- sigaction(SIGPIPE, &action, &prev_action_);
-
- // Unblock signal
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGPIPE);
- sigprocmask(SIG_UNBLOCK, &mask, &prev_mask_);
- }
-
- ~SigPipeCatcher() {
- sigaction(SIGPIPE, &prev_action_, nullptr);
- sigprocmask(SIG_SETMASK, &prev_mask_, nullptr);
- }
-
- void Reset() { sigpipe_happened_ = 0; }
-
- bool sigpipe_happened() const { return bool(sigpipe_happened_); }
-
- struct sigaction prev_action_;
- sigset_t prev_mask_;
- static volatile sig_atomic_t sigpipe_happened_;
-};
-
-// static
-volatile sig_atomic_t SigPipeCatcher::sigpipe_happened_ = 0;
-
-#endif // !_WIN32
-
-} // namespace
-
-TEST(RemoteArguments, DefaultConstruction) {
- RemoteArguments args;
- EXPECT_EQ(0, args.argc());
- EXPECT_FALSE(args.argv());
- EXPECT_EQ(0u, args.args().size());
-}
-
-TEST(RemoteArguments, Construction) {
- const char* const kTestArgs[] = {
- "foo",
- "bar",
- };
- RemoteArguments args(2, (char**)kTestArgs);
- EXPECT_EQ(2, args.argc());
- char** argv = args.argv();
- ASSERT_TRUE(argv);
- ASSERT_TRUE(argv[0]);
- ASSERT_TRUE(argv[1]);
- ASSERT_TRUE(!strcmp(argv[0], kTestArgs[0]));
- ASSERT_TRUE(!strcmp(argv[1], kTestArgs[1]));
-
- auto vec = args.args();
- ASSERT_EQ(2u, vec.size());
- ASSERT_EQ(vec[0], kTestArgs[0]);
- ASSERT_EQ(vec[1], kTestArgs[1]);
-}
-
-TEST(RemoteArguments, Reset) {
- const char* const kTestArgs[] = {
- "foo",
- "bar",
- };
- RemoteArguments args(2, (char**)kTestArgs);
- EXPECT_EQ(2, args.argc());
-
- std::vector<std::string> new_args;
- new_args.push_back("zoo");
- args.Reset(new_args);
-
- EXPECT_EQ(1, args.argc());
- char** argv = args.argv();
- ASSERT_TRUE(argv);
- ASSERT_TRUE(argv[0]);
- ASSERT_EQ(new_args[0], argv[0]);
-}
-
-TEST(RemoteArguments, InsertAt) {
- RemoteArguments args;
- args.InsertAt(10, "foo");
- ASSERT_EQ(1, args.argc());
- ASSERT_EQ(args.args()[0], "foo");
- args.InsertAt(0, "bar");
- ASSERT_EQ(2, args.argc());
- ASSERT_EQ(args.args()[0], "bar");
- ASSERT_EQ(args.args()[1], "foo");
- args.InsertAt(1, "zoo");
- ASSERT_EQ(3, args.argc());
- ASSERT_EQ(args.args()[0], "bar");
- ASSERT_EQ(args.args()[1], "zoo");
- ASSERT_EQ(args.args()[2], "foo");
-}
-
-TEST(WireEncoderAndDecoder, SimpleValues) {
- WireEncoder encoder;
- uint8_t byte1 = 12;
- uint32_t word1 = 42;
- uint8_t byte2 = 56;
- encoder.Write(byte1);
- encoder.Write(word1);
- encoder.Write(byte2);
-
- std::string r = encoder.TakeResult();
- ASSERT_EQ(6u, r.size());
-
- WireDecoder decoder(r);
- uint8_t rbyte1 = 0;
- uint32_t rword1 = 0;
- uint8_t rbyte2 = 0;
- decoder.Read(rbyte1);
- ASSERT_FALSE(decoder.has_error());
- decoder.Read(rword1);
- ASSERT_FALSE(decoder.has_error());
- decoder.Read(rbyte2);
- ASSERT_FALSE(decoder.has_error());
- ASSERT_EQ(byte1, rbyte1);
- ASSERT_EQ(word1, rword1);
- ASSERT_EQ(byte2, rbyte2);
-
- decoder.Read(rbyte1);
- ASSERT_TRUE(decoder.has_error());
-}
-
-TEST(WireEncoderAndDecoder, Strings) {
- static const char* const kStrings[] = {
- "foo_bar",
- "",
- "hello world!",
- };
- WireEncoder encoder;
- size_t expected_size = 0;
- for (const char* str : kStrings) {
- encoder.Write(std::string(str));
- expected_size += 4 + ::strlen(str);
- }
-
- std::string r = encoder.TakeResult();
- ASSERT_EQ(expected_size, r.size());
-
- WireDecoder decoder(r);
- std::vector<std::string> result;
- for (const char* str : kStrings) {
- result.emplace_back(str);
- decoder.Read(result.back());
- }
- ASSERT_FALSE(decoder.has_error());
-
- auto it = result.begin();
- for (const char* str : kStrings) {
- ASSERT_EQ(*it, str);
- ++it;
- }
-}
-
-#ifndef _WIN32
-
-TEST(SigPipeBlocker, Test) {
- SigPipeCatcher catcher;
-
- // First, verify that the catcher works properly.
- std::string error;
- char byte[1] = { 'x' };
- IpcHandle read_end, write_end;
-
- ASSERT_TRUE(IpcHandle::CreatePipe(&read_end, &write_end, &error));
- read_end.Close();
- errno = 0;
- ASSERT_FALSE(write_end.WriteFull(byte, 1, &error));
- ASSERT_EQ(EPIPE, errno);
- ASSERT_TRUE(catcher.sigpipe_happened());
- write_end.Close();
-
- // Then verify that the blocker works properly.
- {
- SigPipeBlocker blocker;
- catcher.Reset();
-
- ASSERT_TRUE(IpcHandle::CreatePipe(&read_end, &write_end, &error));
- read_end.Close();
- errno = 0;
- ASSERT_FALSE(write_end.WriteFull(byte, 1, &error));
- ASSERT_EQ(EPIPE, errno);
- ASSERT_FALSE(catcher.sigpipe_happened());
- write_end.Close();
- }
-
- // Do it again, to verify the destructor released the action.
- ASSERT_TRUE(IpcHandle::CreatePipe(&read_end, &write_end, &error));
- read_end.Close();
- errno = 0;
- ASSERT_FALSE(write_end.WriteFull(byte, 1, &error));
- ASSERT_EQ(EPIPE, errno);
- ASSERT_TRUE(catcher.sigpipe_happened());
-}
-#endif // !_WIN32
diff --git a/src/json.cc b/src/json.cc
deleted file mode 100644
index 4bbf6e1..0000000
--- a/src/json.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 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 "json.h"
-
-#include <cstdio>
-#include <string>
-
-std::string EncodeJSONString(const std::string& in) {
- static const char* hex_digits = "0123456789abcdef";
- std::string out;
- out.reserve(in.length() * 1.2);
- for (std::string::const_iterator it = in.begin(); it != in.end(); ++it) {
- char c = *it;
- if (c == '\b')
- out += "\\b";
- else if (c == '\f')
- out += "\\f";
- else if (c == '\n')
- out += "\\n";
- else if (c == '\r')
- out += "\\r";
- else if (c == '\t')
- out += "\\t";
- else if (0x0 <= c && c < 0x20) {
- out += "\\u00";
- out += hex_digits[c >> 4];
- out += hex_digits[c & 0xf];
- } else if (c == '\\')
- out += "\\\\";
- else if (c == '\"')
- out += "\\\"";
- else
- out += c;
- }
- return out;
-}
-
-void PrintJSONString(const std::string& in) {
- std::string out = EncodeJSONString(in);
- fwrite(out.c_str(), 1, out.length(), stdout);
-}
diff --git a/src/json.h b/src/json.h
deleted file mode 100644
index f39c759..0000000
--- a/src/json.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 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_JSON_H_
-#define NINJA_JSON_H_
-
-#include <string>
-
-// Encode a string in JSON format without encolsing quotes
-std::string EncodeJSONString(const std::string& in);
-
-// Print a string in JSON format to stdout without enclosing quotes
-void PrintJSONString(const std::string& in);
-
-#endif
diff --git a/src/json_test.cc b/src/json_test.cc
deleted file mode 100644
index b4afc73..0000000
--- a/src/json_test.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 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 "json.h"
-
-#include "test.h"
-
-TEST(JSONTest, RegularAscii) {
- EXPECT_EQ(EncodeJSONString("foo bar"), "foo bar");
-}
-
-TEST(JSONTest, EscapedChars) {
- EXPECT_EQ(EncodeJSONString("\"\\\b\f\n\r\t"),
- "\\\""
- "\\\\"
- "\\b\\f\\n\\r\\t");
-}
-
-// codepoints between 0 and 0x1f should be escaped
-TEST(JSONTest, ControlChars) {
- EXPECT_EQ(EncodeJSONString("\x01\x1f"), "\\u0001\\u001f");
-}
-
-// Leave them alone as JSON accepts unicode literals
-// out of control character range
-TEST(JSONTest, UTF8) {
- const char* utf8str = "\xe4\xbd\xa0\xe5\xa5\xbd";
- EXPECT_EQ(EncodeJSONString(utf8str), utf8str);
-}
diff --git a/src/lexer.cc b/src/lexer.cc
deleted file mode 100644
index b1e1152..0000000
--- a/src/lexer.cc
+++ /dev/null
@@ -1,825 +0,0 @@
-/* Generated by re2c */
-// 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.
-
-#include "lexer.h"
-
-#include <stdio.h>
-
-#include "eval_env.h"
-#include "util.h"
-
-using namespace std;
-
-bool Lexer::Error(const string& message, string* err) {
- // Compute line/column.
- int line = 1;
- const char* line_start = input_.str_;
- for (const char* p = input_.str_; p < last_token_; ++p) {
- if (*p == '\n') {
- ++line;
- line_start = p + 1;
- }
- }
- int col = last_token_ ? (int)(last_token_ - line_start) : 0;
-
- *err = StringFormat("%.*s:%d: %s\n", filename_.len_, filename_.str_, line,
- message.c_str());
-
- // Add some context to the message.
- const int kTruncateColumn = 72;
- if (col > 0 && col < kTruncateColumn) {
- int len;
- bool truncated = true;
- for (len = 0; len < kTruncateColumn; ++len) {
- if (line_start[len] == 0 || line_start[len] == '\n') {
- truncated = false;
- break;
- }
- }
- *err += string(line_start, len);
- if (truncated)
- *err += "...";
- *err += "\n";
- *err += string(col, ' ');
- *err += "^ near here";
- }
-
- return false;
-}
-
-Lexer::Lexer(const char* input) {
- Start("input", input);
-}
-
-void Lexer::Start(StringPiece filename, StringPiece input) {
- filename_ = filename;
- input_ = input;
- ofs_ = input_.str_;
- last_token_ = NULL;
-}
-
-const char* Lexer::TokenName(Token t) {
- switch (t) {
- case ERROR: return "lexing error";
- case BUILD: return "'build'";
- case COLON: return "':'";
- case DEFAULT: return "'default'";
- case EQUALS: return "'='";
- case IDENT: return "identifier";
- case INCLUDE: return "'include'";
- case INDENT: return "indent";
- case NEWLINE: return "newline";
- case PIPE2: return "'||'";
- case PIPE: return "'|'";
- case PIPEAT: return "'|@'";
- case POOL: return "'pool'";
- case RULE: return "'rule'";
- case SUBNINJA: return "'subninja'";
- case TEOF: return "eof";
- }
- return NULL; // not reached
-}
-
-const char* Lexer::TokenErrorHint(Token expected) {
- switch (expected) {
- case COLON:
- return " ($ also escapes ':')";
- default:
- return "";
- }
-}
-
-string Lexer::DescribeLastError() {
- if (last_token_) {
- switch (last_token_[0]) {
- case '\t':
- return "tabs are not allowed, use spaces";
- }
- }
- return "lexing error";
-}
-
-void Lexer::UnreadToken() {
- ofs_ = last_token_;
-}
-
-Lexer::Token Lexer::ReadToken() {
- const char* p = ofs_;
- const char* q;
- const char* start;
- Lexer::Token token;
- for (;;) {
- start = p;
-
-{
- unsigned char yych;
- unsigned int yyaccept = 0;
- static const unsigned char yybm[] = {
- 0, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 0, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 160, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 192, 192, 128,
- 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 128, 128, 128, 128, 128, 128,
- 128, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 128, 128, 128, 128, 192,
- 128, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- };
- yych = *p;
- if (yybm[0+yych] & 32) {
- goto yy9;
- }
- if (yych <= '^') {
- if (yych <= ',') {
- if (yych <= '\f') {
- if (yych <= 0x00) goto yy2;
- if (yych == '\n') goto yy6;
- goto yy4;
- } else {
- if (yych <= '\r') goto yy8;
- if (yych == '#') goto yy12;
- goto yy4;
- }
- } else {
- if (yych <= ':') {
- if (yych == '/') goto yy4;
- if (yych <= '9') goto yy13;
- goto yy16;
- } else {
- if (yych <= '=') {
- if (yych <= '<') goto yy4;
- goto yy18;
- } else {
- if (yych <= '@') goto yy4;
- if (yych <= 'Z') goto yy13;
- goto yy4;
- }
- }
- }
- } else {
- if (yych <= 'i') {
- if (yych <= 'b') {
- if (yych == '`') goto yy4;
- if (yych <= 'a') goto yy13;
- goto yy20;
- } else {
- if (yych == 'd') goto yy21;
- if (yych <= 'h') goto yy13;
- goto yy22;
- }
- } else {
- if (yych <= 'r') {
- if (yych == 'p') goto yy23;
- if (yych <= 'q') goto yy13;
- goto yy24;
- } else {
- if (yych <= 'z') {
- if (yych <= 's') goto yy25;
- goto yy13;
- } else {
- if (yych == '|') goto yy26;
- goto yy4;
- }
- }
- }
- }
-yy2:
- ++p;
- { token = TEOF; break; }
-yy4:
- ++p;
-yy5:
- { token = ERROR; break; }
-yy6:
- ++p;
- { token = NEWLINE; break; }
-yy8:
- yych = *++p;
- if (yych == '\n') goto yy28;
- goto yy5;
-yy9:
- yyaccept = 0;
- yych = *(q = ++p);
- if (yybm[0+yych] & 32) {
- goto yy9;
- }
- if (yych <= '\f') {
- if (yych == '\n') goto yy6;
- } else {
- if (yych <= '\r') goto yy30;
- if (yych == '#') goto yy32;
- }
-yy11:
- { token = INDENT; break; }
-yy12:
- yyaccept = 1;
- yych = *(q = ++p);
- if (yych <= 0x00) goto yy5;
- goto yy33;
-yy13:
- yych = *++p;
-yy14:
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = IDENT; break; }
-yy16:
- ++p;
- { token = COLON; break; }
-yy18:
- ++p;
- { token = EQUALS; break; }
-yy20:
- yych = *++p;
- if (yych == 'u') goto yy36;
- goto yy14;
-yy21:
- yych = *++p;
- if (yych == 'e') goto yy37;
- goto yy14;
-yy22:
- yych = *++p;
- if (yych == 'n') goto yy38;
- goto yy14;
-yy23:
- yych = *++p;
- if (yych == 'o') goto yy39;
- goto yy14;
-yy24:
- yych = *++p;
- if (yych == 'u') goto yy40;
- goto yy14;
-yy25:
- yych = *++p;
- if (yych == 'u') goto yy41;
- goto yy14;
-yy26:
- yych = *++p;
- if (yych == '@') goto yy42;
- if (yych == '|') goto yy44;
- { token = PIPE; break; }
-yy28:
- ++p;
- { token = NEWLINE; break; }
-yy30:
- yych = *++p;
- if (yych == '\n') goto yy28;
-yy31:
- p = q;
- if (yyaccept == 0) {
- goto yy11;
- } else {
- goto yy5;
- }
-yy32:
- yych = *++p;
-yy33:
- if (yybm[0+yych] & 128) {
- goto yy32;
- }
- if (yych <= 0x00) goto yy31;
- ++p;
- { continue; }
-yy36:
- yych = *++p;
- if (yych == 'i') goto yy46;
- goto yy14;
-yy37:
- yych = *++p;
- if (yych == 'f') goto yy47;
- goto yy14;
-yy38:
- yych = *++p;
- if (yych == 'c') goto yy48;
- goto yy14;
-yy39:
- yych = *++p;
- if (yych == 'o') goto yy49;
- goto yy14;
-yy40:
- yych = *++p;
- if (yych == 'l') goto yy50;
- goto yy14;
-yy41:
- yych = *++p;
- if (yych == 'b') goto yy51;
- goto yy14;
-yy42:
- ++p;
- { token = PIPEAT; break; }
-yy44:
- ++p;
- { token = PIPE2; break; }
-yy46:
- yych = *++p;
- if (yych == 'l') goto yy52;
- goto yy14;
-yy47:
- yych = *++p;
- if (yych == 'a') goto yy53;
- goto yy14;
-yy48:
- yych = *++p;
- if (yych == 'l') goto yy54;
- goto yy14;
-yy49:
- yych = *++p;
- if (yych == 'l') goto yy55;
- goto yy14;
-yy50:
- yych = *++p;
- if (yych == 'e') goto yy57;
- goto yy14;
-yy51:
- yych = *++p;
- if (yych == 'n') goto yy59;
- goto yy14;
-yy52:
- yych = *++p;
- if (yych == 'd') goto yy60;
- goto yy14;
-yy53:
- yych = *++p;
- if (yych == 'u') goto yy62;
- goto yy14;
-yy54:
- yych = *++p;
- if (yych == 'u') goto yy63;
- goto yy14;
-yy55:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = POOL; break; }
-yy57:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = RULE; break; }
-yy59:
- yych = *++p;
- if (yych == 'i') goto yy64;
- goto yy14;
-yy60:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = BUILD; break; }
-yy62:
- yych = *++p;
- if (yych == 'l') goto yy65;
- goto yy14;
-yy63:
- yych = *++p;
- if (yych == 'd') goto yy66;
- goto yy14;
-yy64:
- yych = *++p;
- if (yych == 'n') goto yy67;
- goto yy14;
-yy65:
- yych = *++p;
- if (yych == 't') goto yy68;
- goto yy14;
-yy66:
- yych = *++p;
- if (yych == 'e') goto yy70;
- goto yy14;
-yy67:
- yych = *++p;
- if (yych == 'j') goto yy72;
- goto yy14;
-yy68:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = DEFAULT; break; }
-yy70:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = INCLUDE; break; }
-yy72:
- yych = *++p;
- if (yych != 'a') goto yy14;
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy13;
- }
- { token = SUBNINJA; break; }
-}
-
- }
-
- last_token_ = start;
- ofs_ = p;
- if (token != NEWLINE && token != TEOF)
- EatWhitespace();
- return token;
-}
-
-bool Lexer::PeekToken(Token token) {
- Token t = ReadToken();
- if (t == token)
- return true;
- UnreadToken();
- return false;
-}
-
-void Lexer::EatWhitespace() {
- const char* p = ofs_;
- const char* q;
- for (;;) {
- ofs_ = p;
-
-{
- unsigned char yych;
- static const unsigned char yybm[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 128, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- };
- yych = *p;
- if (yybm[0+yych] & 128) {
- goto yy81;
- }
- if (yych <= 0x00) goto yy77;
- if (yych == '$') goto yy84;
- goto yy79;
-yy77:
- ++p;
- { break; }
-yy79:
- ++p;
-yy80:
- { break; }
-yy81:
- yych = *++p;
- if (yybm[0+yych] & 128) {
- goto yy81;
- }
- { continue; }
-yy84:
- yych = *(q = ++p);
- if (yych == '\n') goto yy85;
- if (yych == '\r') goto yy87;
- goto yy80;
-yy85:
- ++p;
- { continue; }
-yy87:
- yych = *++p;
- if (yych == '\n') goto yy89;
- p = q;
- goto yy80;
-yy89:
- ++p;
- { continue; }
-}
-
- }
-}
-
-bool Lexer::ReadIdent(string* out) {
- const char* p = ofs_;
- const char* start;
- for (;;) {
- start = p;
-
-{
- unsigned char yych;
- static const unsigned char yybm[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 128, 128, 0,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 0, 0, 0, 0, 0, 0,
- 0, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 0, 0, 0, 0, 128,
- 0, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 128, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- };
- yych = *p;
- if (yybm[0+yych] & 128) {
- goto yy95;
- }
- ++p;
- {
- last_token_ = start;
- return false;
- }
-yy95:
- yych = *++p;
- if (yybm[0+yych] & 128) {
- goto yy95;
- }
- {
- out->assign(start, p - start);
- break;
- }
-}
-
- }
- last_token_ = start;
- ofs_ = p;
- EatWhitespace();
- return true;
-}
-
-bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) {
- const char* p = ofs_;
- const char* q;
- const char* start;
- for (;;) {
- start = p;
-
-{
- unsigned char yych;
- static const unsigned char yybm[] = {
- 0, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 0, 16, 16, 0, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 32, 16, 16, 16, 0, 16, 16, 16,
- 16, 16, 16, 16, 16, 208, 144, 16,
- 208, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 0, 16, 16, 16, 16, 16,
- 16, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 16, 16, 16, 16, 208,
- 16, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 208, 208, 208, 208, 208,
- 208, 208, 208, 16, 0, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16,
- };
- yych = *p;
- if (yybm[0+yych] & 16) {
- goto yy102;
- }
- if (yych <= '\r') {
- if (yych <= 0x00) goto yy100;
- if (yych <= '\n') goto yy105;
- goto yy107;
- } else {
- if (yych <= ' ') goto yy105;
- if (yych <= '$') goto yy109;
- goto yy105;
- }
-yy100:
- ++p;
- {
- last_token_ = start;
- return Error("unexpected EOF", err);
- }
-yy102:
- yych = *++p;
- if (yybm[0+yych] & 16) {
- goto yy102;
- }
- {
- eval->AddText(StringPiece(start, p - start));
- continue;
- }
-yy105:
- ++p;
- {
- if (path) {
- p = start;
- break;
- } else {
- if (*start == '\n')
- break;
- eval->AddText(StringPiece(start, 1));
- continue;
- }
- }
-yy107:
- yych = *++p;
- if (yych == '\n') goto yy110;
- {
- last_token_ = start;
- return Error(DescribeLastError(), err);
- }
-yy109:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy122;
- }
- if (yych <= ' ') {
- if (yych <= '\f') {
- if (yych == '\n') goto yy114;
- goto yy112;
- } else {
- if (yych <= '\r') goto yy117;
- if (yych <= 0x1F) goto yy112;
- goto yy118;
- }
- } else {
- if (yych <= '/') {
- if (yych == '$') goto yy120;
- goto yy112;
- } else {
- if (yych <= ':') goto yy125;
- if (yych <= '`') goto yy112;
- if (yych <= '{') goto yy127;
- goto yy112;
- }
- }
-yy110:
- ++p;
- {
- if (path)
- p = start;
- break;
- }
-yy112:
- ++p;
-yy113:
- {
- last_token_ = start;
- return Error("bad $-escape (literal $ must be written as $$)", err);
- }
-yy114:
- yych = *++p;
- if (yybm[0+yych] & 32) {
- goto yy114;
- }
- {
- continue;
- }
-yy117:
- yych = *++p;
- if (yych == '\n') goto yy128;
- goto yy113;
-yy118:
- ++p;
- {
- eval->AddText(StringPiece(" ", 1));
- continue;
- }
-yy120:
- ++p;
- {
- eval->AddText(StringPiece("$", 1));
- continue;
- }
-yy122:
- yych = *++p;
- if (yybm[0+yych] & 64) {
- goto yy122;
- }
- {
- eval->AddSpecial(StringPiece(start + 1, p - start - 1));
- continue;
- }
-yy125:
- ++p;
- {
- eval->AddText(StringPiece(":", 1));
- continue;
- }
-yy127:
- yych = *(q = ++p);
- if (yybm[0+yych] & 128) {
- goto yy131;
- }
- goto yy113;
-yy128:
- yych = *++p;
- if (yych == ' ') goto yy128;
- {
- continue;
- }
-yy131:
- yych = *++p;
- if (yybm[0+yych] & 128) {
- goto yy131;
- }
- if (yych == '}') goto yy134;
- p = q;
- goto yy113;
-yy134:
- ++p;
- {
- eval->AddSpecial(StringPiece(start + 2, p - start - 3));
- continue;
- }
-}
-
- }
- last_token_ = start;
- ofs_ = p;
- if (path)
- EatWhitespace();
- // Non-path strings end in newlines, so there's no whitespace to eat.
- return true;
-}
diff --git a/src/lexer.h b/src/lexer.h
deleted file mode 100644
index 683fd6c..0000000
--- a/src/lexer.h
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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.
-
-#ifndef NINJA_LEXER_H_
-#define NINJA_LEXER_H_
-
-#include "string_piece.h"
-
-// Windows may #define ERROR.
-#ifdef ERROR
-#undef ERROR
-#endif
-
-struct EvalString;
-
-struct Lexer {
- Lexer() {}
- /// Helper ctor useful for tests.
- explicit Lexer(const char* input);
-
- enum Token {
- ERROR,
- BUILD,
- COLON,
- DEFAULT,
- EQUALS,
- IDENT,
- INCLUDE,
- INDENT,
- NEWLINE,
- PIPE,
- PIPE2,
- PIPEAT,
- POOL,
- RULE,
- SUBNINJA,
- TEOF,
- };
-
- /// Return a human-readable form of a token, used in error messages.
- static const char* TokenName(Token t);
-
- /// Return a human-readable token hint, used in error messages.
- static const char* TokenErrorHint(Token expected);
-
- /// If the last token read was an ERROR token, provide more info
- /// or the empty string.
- std::string DescribeLastError();
-
- /// Start parsing some input.
- void Start(StringPiece filename, StringPiece input);
-
- /// Read a Token from the Token enum.
- Token ReadToken();
-
- /// Rewind to the last read Token.
- void UnreadToken();
-
- /// If the next token is \a token, read it and return true.
- bool PeekToken(Token token);
-
- /// Read a simple identifier (a rule or variable name).
- /// Returns false if a name can't be read.
- 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, 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, std::string* err) {
- return ReadEvalString(value, false, err);
- }
-
- /// Construct an error message with context.
- 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, std::string* err);
-
- StringPiece filename_;
- StringPiece input_;
- const char* ofs_;
- const char* last_token_;
-};
-
-#endif // NINJA_LEXER_H_
diff --git a/src/lexer.in.cc b/src/lexer.in.cc
deleted file mode 100644
index 6f36e6a..0000000
--- a/src/lexer.in.cc
+++ /dev/null
@@ -1,280 +0,0 @@
-// 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.
-
-#include "lexer.h"
-
-#include <stdio.h>
-
-#include "eval_env.h"
-#include "util.h"
-
-using namespace std;
-
-bool Lexer::Error(const string& message, string* err) {
- // Compute line/column.
- int line = 1;
- const char* line_start = input_.str_;
- for (const char* p = input_.str_; p < last_token_; ++p) {
- if (*p == '\n') {
- ++line;
- line_start = p + 1;
- }
- }
- int col = last_token_ ? (int)(last_token_ - line_start) : 0;
-
- *err = StringFormat("%.*s:%d: %s\n", filename_.len_, filename_.str_, line,
- message.c_str());
-
- // Add some context to the message.
- const int kTruncateColumn = 72;
- if (col > 0 && col < kTruncateColumn) {
- int len;
- bool truncated = true;
- for (len = 0; len < kTruncateColumn; ++len) {
- if (line_start[len] == 0 || line_start[len] == '\n') {
- truncated = false;
- break;
- }
- }
- *err += string(line_start, len);
- if (truncated)
- *err += "...";
- *err += "\n";
- *err += string(col, ' ');
- *err += "^ near here";
- }
-
- return false;
-}
-
-Lexer::Lexer(const char* input) {
- Start("input", input);
-}
-
-void Lexer::Start(StringPiece filename, StringPiece input) {
- filename_ = filename;
- input_ = input;
- ofs_ = input_.str_;
- last_token_ = NULL;
-}
-
-const char* Lexer::TokenName(Token t) {
- switch (t) {
- case ERROR: return "lexing error";
- case BUILD: return "'build'";
- case COLON: return "':'";
- case DEFAULT: return "'default'";
- case EQUALS: return "'='";
- case IDENT: return "identifier";
- case INCLUDE: return "'include'";
- case INDENT: return "indent";
- case NEWLINE: return "newline";
- case PIPE2: return "'||'";
- case PIPE: return "'|'";
- case PIPEAT: return "'|@'";
- case POOL: return "'pool'";
- case RULE: return "'rule'";
- case SUBNINJA: return "'subninja'";
- case TEOF: return "eof";
- }
- return NULL; // not reached
-}
-
-const char* Lexer::TokenErrorHint(Token expected) {
- switch (expected) {
- case COLON:
- return " ($ also escapes ':')";
- default:
- return "";
- }
-}
-
-string Lexer::DescribeLastError() {
- if (last_token_) {
- switch (last_token_[0]) {
- case '\t':
- return "tabs are not allowed, use spaces";
- }
- }
- return "lexing error";
-}
-
-void Lexer::UnreadToken() {
- ofs_ = last_token_;
-}
-
-Lexer::Token Lexer::ReadToken() {
- const char* p = ofs_;
- const char* q;
- const char* start;
- Lexer::Token token;
- for (;;) {
- start = p;
- /*!re2c
- re2c:define:YYCTYPE = "unsigned char";
- re2c:define:YYCURSOR = p;
- re2c:define:YYMARKER = q;
- re2c:yyfill:enable = 0;
-
- nul = "\000";
- simple_varname = [a-zA-Z0-9_-]+;
- varname = [a-zA-Z0-9_.-]+;
-
- [ ]*"#"[^\000\n]*"\n" { continue; }
- [ ]*"\r\n" { token = NEWLINE; break; }
- [ ]*"\n" { token = NEWLINE; break; }
- [ ]+ { token = INDENT; break; }
- "build" { token = BUILD; break; }
- "pool" { token = POOL; break; }
- "rule" { token = RULE; break; }
- "default" { token = DEFAULT; break; }
- "=" { token = EQUALS; break; }
- ":" { token = COLON; break; }
- "|@" { token = PIPEAT; break; }
- "||" { token = PIPE2; break; }
- "|" { token = PIPE; break; }
- "include" { token = INCLUDE; break; }
- "subninja" { token = SUBNINJA; break; }
- varname { token = IDENT; break; }
- nul { token = TEOF; break; }
- [^] { token = ERROR; break; }
- */
- }
-
- last_token_ = start;
- ofs_ = p;
- if (token != NEWLINE && token != TEOF)
- EatWhitespace();
- return token;
-}
-
-bool Lexer::PeekToken(Token token) {
- Token t = ReadToken();
- if (t == token)
- return true;
- UnreadToken();
- return false;
-}
-
-void Lexer::EatWhitespace() {
- const char* p = ofs_;
- const char* q;
- for (;;) {
- ofs_ = p;
- /*!re2c
- [ ]+ { continue; }
- "$\r\n" { continue; }
- "$\n" { continue; }
- nul { break; }
- [^] { break; }
- */
- }
-}
-
-bool Lexer::ReadIdent(string* out) {
- const char* p = ofs_;
- const char* start;
- for (;;) {
- start = p;
- /*!re2c
- varname {
- out->assign(start, p - start);
- break;
- }
- [^] {
- last_token_ = start;
- return false;
- }
- */
- }
- last_token_ = start;
- ofs_ = p;
- EatWhitespace();
- return true;
-}
-
-bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) {
- const char* p = ofs_;
- const char* q;
- const char* start;
- for (;;) {
- start = p;
- /*!re2c
- [^$ :\r\n|\000]+ {
- eval->AddText(StringPiece(start, p - start));
- continue;
- }
- "\r\n" {
- if (path)
- p = start;
- break;
- }
- [ :|\n] {
- if (path) {
- p = start;
- break;
- } else {
- if (*start == '\n')
- break;
- eval->AddText(StringPiece(start, 1));
- continue;
- }
- }
- "$$" {
- eval->AddText(StringPiece("$", 1));
- continue;
- }
- "$ " {
- eval->AddText(StringPiece(" ", 1));
- continue;
- }
- "$\r\n"[ ]* {
- continue;
- }
- "$\n"[ ]* {
- continue;
- }
- "${"varname"}" {
- eval->AddSpecial(StringPiece(start + 2, p - start - 3));
- continue;
- }
- "$"simple_varname {
- eval->AddSpecial(StringPiece(start + 1, p - start - 1));
- continue;
- }
- "$:" {
- eval->AddText(StringPiece(":", 1));
- continue;
- }
- "$". {
- last_token_ = start;
- return Error("bad $-escape (literal $ must be written as $$)", err);
- }
- nul {
- last_token_ = start;
- return Error("unexpected EOF", err);
- }
- [^] {
- last_token_ = start;
- return Error(DescribeLastError(), err);
- }
- */
- }
- last_token_ = start;
- ofs_ = p;
- if (path)
- EatWhitespace();
- // Non-path strings end in newlines, so there's no whitespace to eat.
- return true;
-}
diff --git a/src/lexer_test.cc b/src/lexer_test.cc
deleted file mode 100644
index c5c416d..0000000
--- a/src/lexer_test.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.
-
-#include "lexer.h"
-
-#include "eval_env.h"
-#include "test.h"
-
-using namespace std;
-
-TEST(Lexer, ReadVarValue) {
- Lexer lexer("plain text $var $VaR ${x}\n");
- EvalString eval;
- string err;
- EXPECT_TRUE(lexer.ReadVarValue(&eval, &err));
- EXPECT_EQ("", err);
- EXPECT_EQ("[plain text ][$var][ ][$VaR][ ][$x]",
- eval.Serialize());
-}
-
-TEST(Lexer, ReadEvalStringEscapes) {
- Lexer lexer("$ $$ab c$: $\ncde\n");
- EvalString eval;
- string err;
- EXPECT_TRUE(lexer.ReadVarValue(&eval, &err));
- EXPECT_EQ("", err);
- EXPECT_EQ("[ $ab c: cde]",
- eval.Serialize());
-}
-
-TEST(Lexer, ReadIdent) {
- Lexer lexer("foo baR baz_123 foo-bar");
- string ident;
- EXPECT_TRUE(lexer.ReadIdent(&ident));
- EXPECT_EQ("foo", ident);
- EXPECT_TRUE(lexer.ReadIdent(&ident));
- EXPECT_EQ("baR", ident);
- EXPECT_TRUE(lexer.ReadIdent(&ident));
- EXPECT_EQ("baz_123", ident);
- EXPECT_TRUE(lexer.ReadIdent(&ident));
- EXPECT_EQ("foo-bar", ident);
-}
-
-TEST(Lexer, ReadIdentCurlies) {
- // Verify that ReadIdent includes dots in the name,
- // but in an expansion $bar.dots stops at the dot.
- Lexer lexer("foo.dots $bar.dots ${bar.dots}\n");
- string ident;
- EXPECT_TRUE(lexer.ReadIdent(&ident));
- EXPECT_EQ("foo.dots", ident);
-
- EvalString eval;
- string err;
- EXPECT_TRUE(lexer.ReadVarValue(&eval, &err));
- EXPECT_EQ("", err);
- EXPECT_EQ("[$bar][.dots ][$bar.dots]",
- eval.Serialize());
-}
-
-TEST(Lexer, Error) {
- Lexer lexer("foo$\nbad $");
- EvalString eval;
- string err;
- ASSERT_FALSE(lexer.ReadVarValue(&eval, &err));
- EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n"
- "bad $\n"
- " ^ near here"
- , err);
-}
-
-TEST(Lexer, CommentEOF) {
- // Verify we don't run off the end of the string when the EOF is
- // mid-comment.
- Lexer lexer("# foo");
- Lexer::Token token = lexer.ReadToken();
- EXPECT_EQ(Lexer::ERROR, token);
-}
-
-TEST(Lexer, Tabs) {
- // Verify we print a useful error on a disallowed character.
- Lexer lexer(" \tfoobar");
- Lexer::Token token = lexer.ReadToken();
- EXPECT_EQ(Lexer::INDENT, token);
- token = lexer.ReadToken();
- EXPECT_EQ(Lexer::ERROR, token);
- EXPECT_EQ("tabs are not allowed, use spaces", lexer.DescribeLastError());
-}
diff --git a/src/line_printer.cc b/src/line_printer.cc
deleted file mode 100644
index c883837..0000000
--- a/src/line_printer.cc
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2013 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 "line_printer.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#ifdef _WIN32
-#include <windows.h>
-#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
-#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
-#endif
-#else
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-#include <sys/time.h>
-#endif
-
-#include "util.h"
-
-using namespace std;
-
-LinePrinter::LinePrinter() {
- Reset(getenv("TERM"), getenv("CLICOLOR_FORCE"));
-}
-
-void LinePrinter::Reset(const char* term, const char* clicolor_force) {
- have_blank_line_ = true;
- console_locked_ = false;
- line_buffer_.clear();
- output_buffer_.clear();
- line_type_ = FULL;
-
-#ifndef _WIN32
- smart_terminal_ = isatty(1) && term && string(term) != "dumb";
-#else
- if (term && string(term) == "dumb") {
- smart_terminal_ = false;
- } else {
- console_ = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
- }
-#endif
- supports_color_ = smart_terminal_;
-#ifdef _WIN32
- // Try enabling ANSI escape sequence support on Windows 10 terminals.
- if (supports_color_) {
- DWORD mode;
- if (GetConsoleMode(console_, &mode)) {
- if (!SetConsoleMode(console_, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
- supports_color_ = false;
- }
- }
- }
-#endif
- if (!supports_color_) {
- supports_color_ = clicolor_force && std::string(clicolor_force) != "0";
- }
-}
-
-void LinePrinter::Print(string to_print, LineType type) {
- if (console_locked_) {
- line_buffer_ = to_print;
- line_type_ = type;
- return;
- }
-
- if (smart_terminal_) {
- printf("\r"); // Print over previous line, if any.
- // On Windows, calling a C library function writing to stdout also handles
- // pausing the executable when the "Pause" key or Ctrl-S is pressed.
- }
-
- if (smart_terminal_ && type == ELIDE) {
-#ifdef _WIN32
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- GetConsoleScreenBufferInfo(console_, &csbi);
-
- to_print = ElideMiddle(to_print, static_cast<size_t>(csbi.dwSize.X));
- if (supports_color_) { // this means ENABLE_VIRTUAL_TERMINAL_PROCESSING
- // succeeded
- printf("%s\x1B[K", to_print.c_str()); // Clear to end of line.
- fflush(stdout);
- } else {
- // We don't want to have the cursor spamming back and forth, so instead of
- // printf use WriteConsoleOutput which updates the contents of the buffer,
- // but doesn't move the cursor position.
- COORD buf_size = { csbi.dwSize.X, 1 };
- COORD zero_zero = { 0, 0 };
- SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y,
- static_cast<SHORT>(csbi.dwCursorPosition.X +
- csbi.dwSize.X - 1),
- csbi.dwCursorPosition.Y };
- vector<CHAR_INFO> char_data(csbi.dwSize.X);
- for (size_t i = 0; i < static_cast<size_t>(csbi.dwSize.X); ++i) {
- char_data[i].Char.AsciiChar = i < to_print.size() ? to_print[i] : ' ';
- char_data[i].Attributes = csbi.wAttributes;
- }
- WriteConsoleOutput(console_, &char_data[0], buf_size, zero_zero, &target);
- }
-#else
- // Limit output to width of the terminal if provided so we don't cause
- // line-wrapping.
- winsize size;
- if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0) && size.ws_col) {
- to_print = ElideMiddle(to_print, size.ws_col);
- }
- printf("%s", to_print.c_str());
- printf("\x1B[K"); // Clear to end of line.
- fflush(stdout);
-#endif
-
- have_blank_line_ = false;
- } else {
- printf("%s\n", to_print.c_str());
- fflush(stdout);
- }
-}
-
-void LinePrinter::PrintOrBuffer(const char* data, size_t size) {
- if (console_locked_) {
- output_buffer_.append(data, size);
- } else {
- // Avoid printf and C strings, since the actual output might contain null
- // bytes like UTF-16 does (yuck).
- fwrite(data, 1, size, stdout);
- }
-}
-
-void LinePrinter::PrintOnNewLine(const string& to_print) {
- if (console_locked_ && !line_buffer_.empty()) {
- output_buffer_.append(line_buffer_);
- output_buffer_.append(1, '\n');
- line_buffer_.clear();
- }
- if (!have_blank_line_) {
- PrintOrBuffer("\n", 1);
- }
- if (!to_print.empty()) {
- PrintOrBuffer(&to_print[0], to_print.size());
- }
- have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n';
-}
-
-void LinePrinter::SetConsoleLocked(bool locked) {
- if (locked == console_locked_)
- return;
-
- if (locked)
- PrintOnNewLine("");
-
- console_locked_ = locked;
-
- if (!locked) {
- PrintOnNewLine(output_buffer_);
- if (!line_buffer_.empty()) {
- Print(line_buffer_, line_type_);
- }
- output_buffer_.clear();
- line_buffer_.clear();
- }
-}
diff --git a/src/line_printer.h b/src/line_printer.h
deleted file mode 100644
index 60e1450..0000000
--- a/src/line_printer.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2013 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_LINE_PRINTER_H_
-#define NINJA_LINE_PRINTER_H_
-
-#include <stddef.h>
-#include <string>
-
-/// Prints lines of text, possibly overprinting previously printed lines
-/// if the terminal supports it.
-struct LinePrinter {
- LinePrinter();
-
- bool is_smart_terminal() const { return smart_terminal_; }
- void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
-
- bool supports_color() const { return supports_color_; }
-
- enum LineType {
- FULL,
- ELIDE
- };
- /// Overprints the current line. If type is ELIDE, elides to_print to fit on
- /// one line.
- void Print(std::string to_print, LineType type);
-
- /// Prints a string on a new line, not overprinting previous output.
- 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.
- void SetConsoleLocked(bool locked);
-
- /// Reset LinePrinter instance, re-reading environment variables and other
- /// console seettings to adapt to changes during incremental builds.
- /// |term| is the value of the TERM environment variable, or NULL if not
- /// defined, and |clicolor_force| is the value of the CLICOLOR_FORCE
- /// environment variable if define, or NULL otherwise.
- void Reset(const char* term, const char* clicolor_force);
-
- private:
- /// Whether we can do fancy terminal control codes.
- bool smart_terminal_;
-
- /// Whether we can use ISO 6429 (ANSI) color sequences.
- bool supports_color_;
-
- /// Whether the caret is at the beginning of a blank line.
- bool have_blank_line_;
-
- /// Whether console is locked.
- bool console_locked_;
-
- /// Buffered current line while console is locked.
- std::string line_buffer_;
-
- /// Buffered line type while console is locked.
- LineType line_type_;
-
- /// Buffered console output while console is locked.
- std::string output_buffer_;
-
-#ifdef _WIN32
- void* console_;
-#endif
-
- /// Print the given data to the console, or buffer it if it is locked.
- void PrintOrBuffer(const char *data, size_t size);
-};
-
-#endif // NINJA_LINE_PRINTER_H_
diff --git a/src/load_status.h b/src/load_status.h
deleted file mode 100644
index 0b16b1a..0000000
--- a/src/load_status.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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
deleted file mode 100644
index c90b9b6..0000000
--- a/src/manifest_parser.cc
+++ /dev/null
@@ -1,447 +0,0 @@
-// 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.
-
-#include "manifest_parser.h"
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <vector>
-
-#include "graph.h"
-#include "state.h"
-#include "util.h"
-#include "version.h"
-
-using namespace std;
-
-ManifestParser::ManifestParser(State* state, FileReader* file_reader,
- ManifestParserOptions options)
- : Parser(state, file_reader),
- options_(options), quiet_(false) {
- env_ = &state->bindings();
-}
-
-bool ManifestParser::Parse(const string& filename, const string& input,
- string* err) {
- lexer_.Start(filename, input);
-
- for (;;) {
- Lexer::Token token = lexer_.ReadToken();
- switch (token) {
- case Lexer::POOL:
- if (!ParsePool(err))
- return false;
- break;
- case Lexer::BUILD:
- if (!ParseEdge(err))
- return false;
- break;
- case Lexer::RULE:
- if (!ParseRule(err))
- return false;
- break;
- case Lexer::DEFAULT:
- if (!ParseDefault(err))
- return false;
- break;
- case Lexer::IDENT: {
- lexer_.UnreadToken();
- string name;
- EvalString let_value;
- if (!ParseLet(&name, &let_value, err))
- return false;
- string value = let_value.Evaluate(env_);
- // Check ninja_required_version immediately so we can exit
- // before encountering any syntactic surprises.
- if (name == "ninja_required_version")
- CheckNinjaVersion(value);
- env_->AddBinding(name, value);
- break;
- }
- case Lexer::INCLUDE:
- if (!ParseFileInclude(false, err))
- return false;
- break;
- case Lexer::SUBNINJA:
- if (!ParseFileInclude(true, err))
- return false;
- break;
- case Lexer::ERROR: {
- return lexer_.Error(lexer_.DescribeLastError(), err);
- }
- case Lexer::TEOF:
- return true;
- case Lexer::NEWLINE:
- break;
- default:
- return lexer_.Error(string("unexpected ") + Lexer::TokenName(token),
- err);
- }
- }
- return false; // not reached
-}
-
-
-bool ManifestParser::ParsePool(string* err) {
- string name;
- if (!lexer_.ReadIdent(&name))
- return lexer_.Error("expected pool name", err);
-
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- if (state_->LookupPool(name) != NULL)
- return lexer_.Error("duplicate pool '" + name + "'", err);
-
- int depth = -1;
-
- while (lexer_.PeekToken(Lexer::INDENT)) {
- string key;
- EvalString value;
- if (!ParseLet(&key, &value, err))
- return false;
-
- if (key == "depth") {
- string depth_string = value.Evaluate(env_);
- depth = atol(depth_string.c_str());
- if (depth < 0)
- return lexer_.Error("invalid pool depth", err);
- } else {
- return lexer_.Error("unexpected variable '" + key + "'", err);
- }
- }
-
- if (depth < 0)
- return lexer_.Error("expected 'depth =' line", err);
-
- state_->AddPool(new Pool(name, depth));
- return true;
-}
-
-
-bool ManifestParser::ParseRule(string* err) {
- string name;
- if (!lexer_.ReadIdent(&name))
- return lexer_.Error("expected rule name", err);
-
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- if (env_->LookupRuleCurrentScope(name) != NULL)
- return lexer_.Error("duplicate rule '" + name + "'", err);
-
- auto rule = std::unique_ptr<Rule>(new Rule(name));
-
- while (lexer_.PeekToken(Lexer::INDENT)) {
- string key;
- EvalString value;
- if (!ParseLet(&key, &value, err))
- return false;
-
- if (Rule::IsReservedBinding(key)) {
- rule->AddBinding(key, value);
- } else {
- // Die on other keyvals for now; revisit if we want to add a
- // scope here.
- return lexer_.Error("unexpected variable '" + key + "'", err);
- }
- }
-
- if (rule->bindings_["rspfile"].empty() !=
- rule->bindings_["rspfile_content"].empty()) {
- return lexer_.Error("rspfile and rspfile_content need to be "
- "both specified", err);
- }
-
- if (rule->bindings_["command"].empty())
- return lexer_.Error("expected 'command =' line", err);
-
- env_->AddRule(rule.release());
- return true;
-}
-
-bool ManifestParser::ParseLet(string* key, EvalString* value, string* err) {
- if (!lexer_.ReadIdent(key))
- return lexer_.Error("expected variable name", err);
- if (!ExpectToken(Lexer::EQUALS, err))
- return false;
- if (!lexer_.ReadVarValue(value, err))
- return false;
- return true;
-}
-
-bool ManifestParser::ParseDefault(string* err) {
- EvalString eval;
- if (!lexer_.ReadPath(&eval, err))
- return false;
- if (eval.empty())
- return lexer_.Error("expected target name", err);
-
- do {
- string path = eval.Evaluate(env_);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits; // Unused because this only does lookup.
- CanonicalizePath(&path, &slash_bits);
- std::string default_err;
- if (!state_->AddDefault(path, &default_err))
- return lexer_.Error(default_err, err);
-
- eval.Clear();
- if (!lexer_.ReadPath(&eval, err))
- return false;
- } while (!eval.empty());
-
- return ExpectToken(Lexer::NEWLINE, err);
-}
-
-bool ManifestParser::ParseEdge(string* err) {
- vector<EvalString> ins, outs, validations;
-
- {
- EvalString out;
- if (!lexer_.ReadPath(&out, err))
- return false;
- while (!out.empty()) {
- outs.push_back(out);
-
- out.Clear();
- if (!lexer_.ReadPath(&out, err))
- return false;
- }
- }
-
- // Add all implicit outs, counting how many as we go.
- int implicit_outs = 0;
- if (lexer_.PeekToken(Lexer::PIPE)) {
- for (;;) {
- EvalString out;
- if (!lexer_.ReadPath(&out, err))
- return false;
- if (out.empty())
- break;
- outs.push_back(out);
- ++implicit_outs;
- }
- }
-
- if (outs.empty())
- return lexer_.Error("expected path", err);
-
- if (!ExpectToken(Lexer::COLON, err))
- return false;
-
- string rule_name;
- if (!lexer_.ReadIdent(&rule_name))
- return lexer_.Error("expected build command name", err);
-
- const Rule* rule = env_->LookupRule(rule_name);
- if (!rule)
- return lexer_.Error("unknown build rule '" + rule_name + "'", err);
-
- for (;;) {
- // XXX should we require one path here?
- EvalString in;
- if (!lexer_.ReadPath(&in, err))
- return false;
- if (in.empty())
- break;
- ins.push_back(in);
- }
-
- // Add all implicit deps, counting how many as we go.
- int implicit = 0;
- if (lexer_.PeekToken(Lexer::PIPE)) {
- for (;;) {
- EvalString in;
- if (!lexer_.ReadPath(&in, err))
- return false;
- if (in.empty())
- break;
- ins.push_back(in);
- ++implicit;
- }
- }
-
- // Add all order-only deps, counting how many as we go.
- int order_only = 0;
- if (lexer_.PeekToken(Lexer::PIPE2)) {
- for (;;) {
- EvalString in;
- if (!lexer_.ReadPath(&in, err))
- return false;
- if (in.empty())
- break;
- ins.push_back(in);
- ++order_only;
- }
- }
-
- // Add all validations, counting how many as we go.
- if (lexer_.PeekToken(Lexer::PIPEAT)) {
- for (;;) {
- EvalString validation;
- if (!lexer_.ReadPath(&validation, err))
- return false;
- if (validation.empty())
- break;
- validations.push_back(validation);
- }
- }
-
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- // Bindings on edges are rare, so allocate per-edge envs only when needed.
- bool has_indent_token = lexer_.PeekToken(Lexer::INDENT);
- BindingEnv* env = has_indent_token ? state_->CreateNewEnv(env_) : env_;
- while (has_indent_token) {
- string key;
- EvalString val;
- if (!ParseLet(&key, &val, err))
- return false;
-
- env->AddBinding(key, val.Evaluate(env_));
- has_indent_token = lexer_.PeekToken(Lexer::INDENT);
- }
-
- Edge* edge = state_->AddEdge(rule);
- edge->env_ = env;
-
- string pool_name = edge->GetBinding("pool");
- if (!pool_name.empty()) {
- Pool* pool = state_->LookupPool(pool_name);
- if (pool == NULL)
- return lexer_.Error("unknown pool name '" + pool_name + "'", err);
- edge->pool_ = pool;
- }
-
- edge->outputs_.reserve(outs.size());
- for (size_t i = 0, e = outs.size(); i != e; ++i) {
- string path = outs[i].Evaluate(env);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- if (!state_->AddOut(edge, path, slash_bits)) {
- if (options_.dupe_edge_action_ == kDupeEdgeActionError) {
- lexer_.Error("multiple rules generate " + path, err);
- return false;
- } else {
- if (!quiet_) {
- Warning(
- "multiple rules generate %s. builds involving this target will "
- "not be correct; continuing anyway",
- path.c_str());
- }
- if (e - i <= static_cast<size_t>(implicit_outs))
- --implicit_outs;
- }
- }
- }
-
- if (edge->outputs_.empty()) {
- // All outputs of the edge are already created by other edges. Don't add
- // this edge. Do this check before input nodes are connected to the edge.
- state_->edges_.pop_back();
- delete edge;
- return true;
- }
- edge->static_implicit_outs_ = edge->implicit_outs_ = implicit_outs;
-
- edge->inputs_.reserve(ins.size());
- for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
- string path = i->Evaluate(env);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- state_->AddIn(edge, path, slash_bits);
- }
- edge->static_implicit_deps_ = edge->implicit_deps_ = implicit;
- edge->order_only_deps_ = order_only;
-
- edge->validations_.reserve(validations.size());
- for (std::vector<EvalString>::iterator v = validations.begin();
- v != validations.end(); ++v) {
- string path = v->Evaluate(env);
- if (path.empty())
- return lexer_.Error("empty path", err);
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
- state_->AddValidation(edge, path, slash_bits);
- }
-
- if (options_.phony_cycle_action_ == kPhonyCycleActionWarn &&
- edge->maybe_phonycycle_diagnostic()) {
- // CMake 2.8.12.x and 3.0.x incorrectly write phony build statements
- // that reference themselves. Ninja used to tolerate these in the
- // build graph but that has since been fixed. Filter them out to
- // support users of those old CMake versions.
- Node* out = edge->outputs_[0];
- vector<Node*>::iterator new_end =
- remove(edge->inputs_.begin(), edge->inputs_.end(), out);
- if (new_end != edge->inputs_.end()) {
- edge->inputs_.erase(new_end, edge->inputs_.end());
- if (!quiet_) {
- Warning("phony target '%s' names itself as an input; "
- "ignoring [-w phonycycle=warn]",
- out->path().c_str());
- }
- }
- }
-
- // 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.
- string dyndep = edge->GetUnescapedDyndep();
- if (!dyndep.empty()) {
- uint64_t slash_bits;
- CanonicalizePath(&dyndep, &slash_bits);
- edge->dyndep_ = state_->GetNode(dyndep, slash_bits);
- edge->dyndep_->set_dyndep_pending(true);
- vector<Node*>::iterator dgi =
- std::find(edge->inputs_.begin(), edge->inputs_.end(), edge->dyndep_);
- if (dgi == edge->inputs_.end()) {
- return lexer_.Error("dyndep '" + dyndep + "' is not an input", err);
- }
- assert(!edge->dyndep_->generated_by_dep_loader());
- }
-
- return true;
-}
-
-bool ManifestParser::ParseFileInclude(bool new_scope, string* err) {
- EvalString eval;
- if (!lexer_.ReadPath(&eval, err))
- return false;
- string path = eval.Evaluate(env_);
-
- ManifestParser subparser(state_, file_reader_, options_);
- if (new_scope) {
- subparser.env_ = state_->CreateNewEnv(env_);
- } else {
- subparser.env_ = env_;
- }
-
- if (!subparser.Load(path, err, &lexer_))
- return false;
-
- if (!ExpectToken(Lexer::NEWLINE, err))
- return false;
-
- return true;
-}
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
deleted file mode 100644
index 954cf46..0000000
--- a/src/manifest_parser.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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.
-
-#ifndef NINJA_MANIFEST_PARSER_H_
-#define NINJA_MANIFEST_PARSER_H_
-
-#include "parser.h"
-
-struct BindingEnv;
-struct EvalString;
-
-enum DupeEdgeAction {
- kDupeEdgeActionWarn,
- kDupeEdgeActionError,
-};
-
-enum PhonyCycleAction {
- kPhonyCycleActionWarn,
- kPhonyCycleActionError,
-};
-
-struct ManifestParserOptions {
- ManifestParserOptions()
- : dupe_edge_action_(kDupeEdgeActionWarn),
- phony_cycle_action_(kPhonyCycleActionWarn) {}
- DupeEdgeAction dupe_edge_action_;
- PhonyCycleAction phony_cycle_action_;
-};
-
-/// Parses .ninja files.
-struct ManifestParser : public Parser {
- ManifestParser(State* state, FileReader* file_reader,
- ManifestParserOptions options = ManifestParserOptions());
-
- /// Parse a text string of input. Used by tests.
- 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 std::string& filename, const std::string& input,
- std::string* err);
-
- /// Parse various statement types.
- 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, std::string* err);
-
- BindingEnv* env_;
- ManifestParserOptions options_;
- bool quiet_;
-};
-
-#endif // NINJA_MANIFEST_PARSER_H_
diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc
deleted file mode 100644
index fb1dbbf..0000000
--- a/src/manifest_parser_perftest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2014 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.
-
-// Tests manifest parser performance. Expects to be run in ninja's root
-// directory.
-
-#include <numeric>
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifdef _WIN32
-#include "getopt.h"
-#include <direct.h>
-#elif defined(_AIX)
-#include "getopt.h"
-#include <unistd.h>
-#else
-#include <getopt.h>
-#include <unistd.h>
-#endif
-
-#include "disk_interface.h"
-#include "graph.h"
-#include "manifest_parser.h"
-#include "metrics.h"
-#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);
- if (mtime != 0) // 0 means that the file doesn't exist yet.
- return mtime != -1;
-
- string command = "python misc/write_fake_manifests.py " + dir;
- printf("Creating manifest data..."); fflush(stdout);
- int exit_code = system(command.c_str());
- printf("done.\n");
- if (exit_code != 0)
- *err = "Failed to run " + command;
- return exit_code == 0;
-}
-
-int LoadManifests(bool measure_command_evaluation) {
- string err;
- RealDiskInterface disk_interface;
- State state;
- ManifestParser parser(&state, &disk_interface);
- if (!parser.Load("build.ninja", &err)) {
- fprintf(stderr, "Failed to read test data: %s\n", err.c_str());
- exit(1);
- }
- // Doing an empty build involves reading the manifest and evaluating all
- // commands required for the requested targets. So include command
- // evaluation in the perftest by default.
- int optimization_guard = 0;
- if (measure_command_evaluation)
- for (size_t i = 0; i < state.edges_.size(); ++i)
- optimization_guard += state.edges_[i]->EvaluateCommand().size();
- return optimization_guard;
-}
-
-int main(int argc, char* argv[]) {
- bool measure_command_evaluation = true;
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("fh"))) != -1) {
- switch (opt) {
- case 'f':
- measure_command_evaluation = false;
- break;
- case 'h':
- default:
- printf("usage: manifest_parser_perftest\n"
-"\n"
-"options:\n"
-" -f only measure manifest load time, not command evaluation time\n"
- );
- return 1;
- }
- }
-
- const char kManifestDir[] = "build/manifest_perftest";
-
- string err;
- if (!WriteFakeManifests(kManifestDir, &err)) {
- fprintf(stderr, "Failed to write test data: %s\n", err.c_str());
- return 1;
- }
-
- if (chdir(kManifestDir) < 0)
- ErrnoFatal("chdir");
-
- const int kNumRepetitions = 5;
- vector<int> times;
- for (int i = 0; i < kNumRepetitions; ++i) {
- int64_t start = GetTimeMillis();
- int optimization_guard = LoadManifests(measure_command_evaluation);
- int delta = (int)(GetTimeMillis() - start);
- printf("%dms (hash: %x)\n", delta, optimization_guard);
- times.push_back(delta);
- }
-
- int min = *min_element(times.begin(), times.end());
- int max = *max_element(times.begin(), times.end());
- float total = accumulate(times.begin(), times.end(), 0.0f);
- printf("min %dms max %dms avg %.1fms\n", min, max, total / times.size());
-}
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
deleted file mode 100644
index b286d85..0000000
--- a/src/manifest_parser_test.cc
+++ /dev/null
@@ -1,1167 +0,0 @@
-// 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.
-
-#include "manifest_parser.h"
-
-#include <map>
-#include <vector>
-
-#include "graph.h"
-#include "state.h"
-#include "test.h"
-
-using namespace std;
-
-struct ParserTest : public testing::Test {
- void AssertParse(const char* input) {
- ManifestParser parser(&state, &fs_);
- string err;
- EXPECT_TRUE(parser.ParseTest(input, &err));
- ASSERT_EQ("", err);
- VerifyGraph(state);
- }
-
- State state;
- VirtualFileSystem fs_;
-};
-
-TEST_F(ParserTest, Empty) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(""));
-}
-
-TEST_F(ParserTest, Rules) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"\n"
-"rule date\n"
-" command = date > $out\n"
-"\n"
-"build result: cat in_1.cc in-2.O\n"));
-
- ASSERT_EQ(3u, state.bindings().GetRules().size());
- const Rule* rule = state.bindings().GetRules().begin()->second.get();
- EXPECT_EQ("cat", rule->name());
- EXPECT_EQ("[cat ][$in][ > ][$out]",
- rule->GetBinding("command")->Serialize());
-}
-
-TEST_F(ParserTest, RuleAttributes) {
- // Check that all of the allowed rule attributes are parsed ok.
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = a\n"
-" depfile = a\n"
-" deps = a\n"
-" description = a\n"
-" generator = a\n"
-" restat = a\n"
-" rspfile = a\n"
-" rspfile_content = a\n"
-));
-}
-
-TEST_F(ParserTest, IgnoreIndentedComments) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-" #indented comment\n"
-"rule cat\n"
-" command = cat $in > $out\n"
-" #generator = 1\n"
-" restat = 1 # comment\n"
-" #comment\n"
-"build result: cat in_1.cc in-2.O\n"
-" #comment\n"));
-
- ASSERT_EQ(2u, state.bindings().GetRules().size());
- const Rule* rule = state.bindings().GetRules().begin()->second.get();
- EXPECT_EQ("cat", rule->name());
- Edge* edge = state.GetNode("result", 0)->in_edge();
- EXPECT_TRUE(edge->GetBindingBool("restat"));
- EXPECT_FALSE(edge->GetBindingBool("generator"));
-}
-
-TEST_F(ParserTest, IgnoreIndentedBlankLines) {
- // the indented blanks used to cause parse errors
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-" \n"
-"rule cat\n"
-" command = cat $in > $out\n"
-" \n"
-"build result: cat in_1.cc in-2.O\n"
-" \n"
-"variable=1\n"));
-
- // the variable must be in the top level environment
- EXPECT_EQ("1", state.bindings().LookupVariable("variable"));
-}
-
-TEST_F(ParserTest, ResponseFiles) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat_rsp\n"
-" command = cat $rspfile > $out\n"
-" rspfile = $rspfile\n"
-" rspfile_content = $in\n"
-"\n"
-"build out: cat_rsp in\n"
-" rspfile=out.rsp\n"));
-
- ASSERT_EQ(2u, state.bindings().GetRules().size());
- const Rule* rule = state.bindings().GetRules().begin()->second.get();
- EXPECT_EQ("cat_rsp", rule->name());
- EXPECT_EQ("[cat ][$rspfile][ > ][$out]",
- rule->GetBinding("command")->Serialize());
- EXPECT_EQ("[$rspfile]", rule->GetBinding("rspfile")->Serialize());
- EXPECT_EQ("[$in]", rule->GetBinding("rspfile_content")->Serialize());
-}
-
-TEST_F(ParserTest, InNewline) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat_rsp\n"
-" command = cat $in_newline > $out\n"
-"\n"
-"build out: cat_rsp in in2\n"
-" rspfile=out.rsp\n"));
-
- ASSERT_EQ(2u, state.bindings().GetRules().size());
- const Rule* rule = state.bindings().GetRules().begin()->second.get();
- EXPECT_EQ("cat_rsp", rule->name());
- EXPECT_EQ("[cat ][$in_newline][ > ][$out]",
- rule->GetBinding("command")->Serialize());
-
- Edge* edge = state.edges_[0];
- EXPECT_EQ("cat in\nin2 > out", edge->EvaluateCommand());
-}
-
-TEST_F(ParserTest, Variables) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"l = one-letter-test\n"
-"rule link\n"
-" command = ld $l $extra $with_under -o $out $in\n"
-"\n"
-"extra = -pthread\n"
-"with_under = -under\n"
-"build a: link b c\n"
-"nested1 = 1\n"
-"nested2 = $nested1/2\n"
-"build supernested: link x\n"
-" extra = $nested2/3\n"));
-
- ASSERT_EQ(2u, state.edges_.size());
- Edge* edge = state.edges_[0];
- EXPECT_EQ("ld one-letter-test -pthread -under -o a b c",
- edge->EvaluateCommand());
- EXPECT_EQ("1/2", state.bindings().LookupVariable("nested2"));
-
- edge = state.edges_[1];
- EXPECT_EQ("ld one-letter-test 1/2/3 -under -o supernested x",
- edge->EvaluateCommand());
-}
-
-TEST_F(ParserTest, VariableScope) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"foo = bar\n"
-"rule cmd\n"
-" command = cmd $foo $in $out\n"
-"\n"
-"build inner: cmd a\n"
-" foo = baz\n"
-"build outer: cmd b\n"
-"\n" // Extra newline after build line tickles a regression.
-));
-
- ASSERT_EQ(2u, state.edges_.size());
- EXPECT_EQ("cmd baz a inner", state.edges_[0]->EvaluateCommand());
- EXPECT_EQ("cmd bar b outer", state.edges_[1]->EvaluateCommand());
-}
-
-TEST_F(ParserTest, Continuation) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule link\n"
-" command = foo bar $\n"
-" baz\n"
-"\n"
-"build a: link c $\n"
-" d e f\n"));
-
- ASSERT_EQ(2u, state.bindings().GetRules().size());
- const Rule* rule = state.bindings().GetRules().begin()->second.get();
- EXPECT_EQ("link", rule->name());
- EXPECT_EQ("[foo bar baz]", rule->GetBinding("command")->Serialize());
-}
-
-TEST_F(ParserTest, Backslash) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"foo = bar\\baz\n"
-"foo2 = bar\\ baz\n"
-));
- EXPECT_EQ("bar\\baz", state.bindings().LookupVariable("foo"));
- EXPECT_EQ("bar\\ baz", state.bindings().LookupVariable("foo2"));
-}
-
-TEST_F(ParserTest, Comment) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"# this is a comment\n"
-"foo = not # a comment\n"));
- EXPECT_EQ("not # a comment", state.bindings().LookupVariable("foo"));
-}
-
-TEST_F(ParserTest, Dollars) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule foo\n"
-" command = ${out}bar$$baz$$$\n"
-"blah\n"
-"x = $$dollar\n"
-"build $x: foo y\n"
-));
- EXPECT_EQ("$dollar", state.bindings().LookupVariable("x"));
-#ifdef _WIN32
- EXPECT_EQ("$dollarbar$baz$blah", state.edges_[0]->EvaluateCommand());
-#else
- EXPECT_EQ("'$dollar'bar$baz$blah", state.edges_[0]->EvaluateCommand());
-#endif
-}
-
-TEST_F(ParserTest, EscapeSpaces) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule spaces\n"
-" command = something\n"
-"build foo$ bar: spaces $$one two$$$ three\n"
-));
- EXPECT_TRUE(state.LookupNode("foo bar"));
- EXPECT_EQ(state.edges_[0]->outputs_[0]->path(), "foo bar");
- EXPECT_EQ(state.edges_[0]->inputs_[0]->path(), "$one");
- EXPECT_EQ(state.edges_[0]->inputs_[1]->path(), "two$ three");
- EXPECT_EQ(state.edges_[0]->EvaluateCommand(), "something");
-}
-
-TEST_F(ParserTest, CanonicalizeFile) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build out: cat in/1 in//2\n"
-"build in/1: cat\n"
-"build in/2: cat\n"));
-
- EXPECT_TRUE(state.LookupNode("in/1"));
- EXPECT_TRUE(state.LookupNode("in/2"));
- EXPECT_FALSE(state.LookupNode("in//1"));
- EXPECT_FALSE(state.LookupNode("in//2"));
-}
-
-#ifdef _WIN32
-TEST_F(ParserTest, CanonicalizeFileBackslashes) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build out: cat in\\1 in\\\\2\n"
-"build in\\1: cat\n"
-"build in\\2: cat\n"));
-
- Node* node = state.LookupNode("in/1");;
- EXPECT_TRUE(node);
- EXPECT_EQ(1, node->slash_bits());
- node = state.LookupNode("in/2");
- EXPECT_TRUE(node);
- EXPECT_EQ(1, node->slash_bits());
- EXPECT_FALSE(state.LookupNode("in//1"));
- EXPECT_FALSE(state.LookupNode("in//2"));
-}
-#endif
-
-TEST_F(ParserTest, PathVariables) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"dir = out\n"
-"build $dir/exe: cat src\n"));
-
- EXPECT_FALSE(state.LookupNode("$dir/exe"));
- EXPECT_TRUE(state.LookupNode("out/exe"));
-}
-
-TEST_F(ParserTest, CanonicalizePaths) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build ./out.o: cat ./bar/baz/../foo.cc\n"));
-
- EXPECT_FALSE(state.LookupNode("./out.o"));
- EXPECT_TRUE(state.LookupNode("out.o"));
- EXPECT_FALSE(state.LookupNode("./bar/baz/../foo.cc"));
- EXPECT_TRUE(state.LookupNode("bar/foo.cc"));
-}
-
-#ifdef _WIN32
-TEST_F(ParserTest, CanonicalizePathsBackslashes) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build ./out.o: cat ./bar/baz/../foo.cc\n"
-"build .\\out2.o: cat .\\bar/baz\\..\\foo.cc\n"
-"build .\\out3.o: cat .\\bar\\baz\\..\\foo3.cc\n"
-));
-
- EXPECT_FALSE(state.LookupNode("./out.o"));
- EXPECT_FALSE(state.LookupNode(".\\out2.o"));
- EXPECT_FALSE(state.LookupNode(".\\out3.o"));
- EXPECT_TRUE(state.LookupNode("out.o"));
- EXPECT_TRUE(state.LookupNode("out2.o"));
- EXPECT_TRUE(state.LookupNode("out3.o"));
- EXPECT_FALSE(state.LookupNode("./bar/baz/../foo.cc"));
- EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo.cc"));
- EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo3.cc"));
- Node* node = state.LookupNode("bar/foo.cc");
- EXPECT_TRUE(node);
- EXPECT_EQ(0, node->slash_bits());
- node = state.LookupNode("bar/foo3.cc");
- EXPECT_TRUE(node);
- EXPECT_EQ(1, node->slash_bits());
-}
-#endif
-
-TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputs) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build out1 out2: cat in1\n"
-"build out1: cat in2\n"
-"build final: cat out1\n"
-));
- // AssertParse() checks that the generated build graph is self-consistent.
- // That's all the checking that this test needs.
-}
-
-TEST_F(ParserTest, NoDeadPointerFromDuplicateEdge) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build out: cat in\n"
-"build out: cat in\n"
-));
- // AssertParse() checks that the generated build graph is self-consistent.
- // That's all the checking that this test needs.
-}
-
-TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputsError) {
- const char kInput[] =
-"rule cat\n"
-" command = cat $in > $out\n"
-"build out1 out2: cat in1\n"
-"build out1: cat in2\n"
-"build final: cat out1\n";
- ManifestParserOptions parser_opts;
- parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
- ManifestParser parser(&state, &fs_, parser_opts);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("input:5: multiple rules generate out1\n", err);
-}
-
-TEST_F(ParserTest, DuplicateEdgeInIncludedFile) {
- fs_.Create("sub.ninja",
- "rule cat\n"
- " command = cat $in > $out\n"
- "build out1 out2: cat in1\n"
- "build out1: cat in2\n"
- "build final: cat out1\n");
- const char kInput[] =
- "subninja sub.ninja\n";
- ManifestParserOptions parser_opts;
- parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
- ManifestParser parser(&state, &fs_, parser_opts);
- string err;
- EXPECT_FALSE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("sub.ninja:5: multiple rules generate out1\n", err);
-}
-
-TEST_F(ParserTest, PhonySelfReferenceIgnored) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"build a: phony a\n"
-));
-
- Node* node = state.LookupNode("a");
- Edge* edge = node->in_edge();
- ASSERT_TRUE(edge->inputs_.empty());
-}
-
-TEST_F(ParserTest, PhonySelfReferenceKept) {
- const char kInput[] =
-"build a: phony a\n";
- ManifestParserOptions parser_opts;
- parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
- ManifestParser parser(&state, &fs_, parser_opts);
- string err;
- EXPECT_TRUE(parser.ParseTest(kInput, &err));
- EXPECT_EQ("", err);
-
- Node* node = state.LookupNode("a");
- Edge* edge = node->in_edge();
- ASSERT_EQ(edge->inputs_.size(), 1);
- ASSERT_EQ(edge->inputs_[0], node);
-}
-
-TEST_F(ParserTest, ReservedWords) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule build\n"
-" command = rule run $out\n"
-"build subninja: build include default foo.cc\n"
-"default subninja\n"));
-}
-
-TEST_F(ParserTest, Errors) {
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest(string("subn", 4), &err));
- EXPECT_EQ("input:1: expected '=', got eof\n"
- "subn\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("foobar", &err));
- EXPECT_EQ("input:1: expected '=', got eof\n"
- "foobar\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x 3", &err));
- EXPECT_EQ("input:1: expected '=', got identifier\n"
- "x 3\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x = 3", &err));
- EXPECT_EQ("input:1: unexpected EOF\n"
- "x = 3\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x = 3\ny 2", &err));
- EXPECT_EQ("input:2: expected '=', got identifier\n"
- "y 2\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x = $", &err));
- EXPECT_EQ("input:1: bad $-escape (literal $ must be written as $$)\n"
- "x = $\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x = $\n $[\n", &err));
- EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n"
- " $[\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("x = a$\n b$\n $\n", &err));
- EXPECT_EQ("input:4: unexpected EOF\n"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("build\n", &err));
- EXPECT_EQ("input:1: expected path\n"
- "build\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("build x: y z\n", &err));
- EXPECT_EQ("input:1: unknown build rule 'y'\n"
- "build x: y z\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("build x:: y z\n", &err));
- EXPECT_EQ("input:1: expected build command name\n"
- "build x:: y z\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n command = cat ok\n"
- "build x: cat $\n :\n",
- &err));
- EXPECT_EQ("input:4: expected newline, got ':'\n"
- " :\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n",
- &err));
- EXPECT_EQ("input:2: expected 'command =' line\n", err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n"
- " command = echo\n"
- "rule cat\n"
- " command = echo\n", &err));
- EXPECT_EQ("input:3: duplicate rule 'cat'\n"
- "rule cat\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n"
- " command = echo\n"
- " rspfile = cat.rsp\n", &err));
- EXPECT_EQ(
- "input:4: rspfile and rspfile_content need to be both specified\n",
- err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n"
- " command = ${fafsd\n"
- "foo = bar\n",
- &err));
- EXPECT_EQ("input:2: bad $-escape (literal $ must be written as $$)\n"
- " command = ${fafsd\n"
- " ^ near here"
- , err);
- }
-
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n"
- " command = cat\n"
- "build $.: cat foo\n",
- &err));
- EXPECT_EQ("input:3: bad $-escape (literal $ must be written as $$)\n"
- "build $.: cat foo\n"
- " ^ near here"
- , err);
- }
-
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cat\n"
- " command = cat\n"
- "build $: cat foo\n",
- &err));
- EXPECT_EQ("input:3: expected ':', got newline ($ also escapes ':')\n"
- "build $: cat foo\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule %foo\n",
- &err));
- EXPECT_EQ("input:1: expected rule name\n"
- "rule %foo\n"
- " ^ near here",
- err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cc\n"
- " command = foo\n"
- " othervar = bar\n",
- &err));
- EXPECT_EQ("input:3: unexpected variable 'othervar'\n"
- " othervar = bar\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
- "build $.: cc bar.cc\n",
- &err));
- EXPECT_EQ("input:3: bad $-escape (literal $ must be written as $$)\n"
- "build $.: cc bar.cc\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n && bar",
- &err));
- EXPECT_EQ("input:3: expected variable name\n"
- " && bar\n"
- " ^ near here",
- err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n"
- "build $: cc bar.cc\n",
- &err));
- EXPECT_EQ("input:3: expected ':', got newline ($ also escapes ':')\n"
- "build $: cc bar.cc\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("default\n",
- &err));
- EXPECT_EQ("input:1: expected target name\n"
- "default\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("default nonexistent\n",
- &err));
- EXPECT_EQ("input:1: unknown target 'nonexistent'\n"
- "default nonexistent\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule r\n command = r\n"
- "build b: r\n"
- "default b:\n",
- &err));
- EXPECT_EQ("input:4: expected newline, got ':'\n"
- "default b:\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("default $a\n", &err));
- EXPECT_EQ("input:1: empty path\n"
- "default $a\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("rule r\n"
- " command = r\n"
- "build $a: r $c\n", &err));
- // XXX the line number is wrong; we should evaluate paths in ParseEdge
- // as we see them, not after we've read them all!
- EXPECT_EQ("input:4: empty path\n", err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- // the indented blank line must terminate the rule
- // this also verifies that "unexpected (token)" errors are correct
- EXPECT_FALSE(parser.ParseTest("rule r\n"
- " command = r\n"
- " \n"
- " generator = 1\n", &err));
- EXPECT_EQ("input:4: unexpected indent\n", err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("pool\n", &err));
- EXPECT_EQ("input:1: expected pool name\n"
- "pool\n"
- " ^ near here", err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("pool foo\n", &err));
- EXPECT_EQ("input:2: expected 'depth =' line\n", err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("pool foo\n"
- " depth = 4\n"
- "pool foo\n", &err));
- EXPECT_EQ("input:3: duplicate pool 'foo'\n"
- "pool foo\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("pool foo\n"
- " depth = -1\n", &err));
- EXPECT_EQ("input:2: invalid pool depth\n"
- " depth = -1\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest("pool foo\n"
- " bar = 1\n", &err));
- EXPECT_EQ("input:2: unexpected variable 'bar'\n"
- " bar = 1\n"
- " ^ near here"
- , err);
- }
-
- {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- // Pool names are dereferenced at edge parsing time.
- EXPECT_FALSE(parser.ParseTest("rule run\n"
- " command = echo\n"
- " pool = unnamed_pool\n"
- "build out: run in\n", &err));
- EXPECT_EQ("input:5: unknown pool name 'unnamed_pool'\n", err);
- }
-}
-
-TEST_F(ParserTest, MissingInput) {
- State local_state;
- ManifestParser parser(&local_state, &fs_);
- string err;
- EXPECT_FALSE(parser.Load("build.ninja", &err));
- EXPECT_EQ("loading 'build.ninja': No such file or directory", err);
-}
-
-TEST_F(ParserTest, MultipleOutputs) {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- EXPECT_TRUE(parser.ParseTest("rule cc\n command = foo\n depfile = bar\n"
- "build a.o b.o: cc c.cc\n",
- &err));
- EXPECT_EQ("", err);
-}
-
-TEST_F(ParserTest, MultipleOutputsWithDeps) {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
- 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("", err);
-}
-
-TEST_F(ParserTest, SubNinja) {
- fs_.Create("test.ninja",
- "var = inner\n"
- "build $builddir/inner: varref\n");
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"builddir = some_dir/\n"
-"rule varref\n"
-" command = varref $var\n"
-"var = outer\n"
-"build $builddir/outer: varref\n"
-"subninja test.ninja\n"
-"build $builddir/outer2: varref\n"));
- ASSERT_EQ(1u, fs_.files_read_.size());
-
- EXPECT_EQ("test.ninja", fs_.files_read_[0]);
- EXPECT_TRUE(state.LookupNode("some_dir/outer"));
- // Verify our builddir setting is inherited.
- EXPECT_TRUE(state.LookupNode("some_dir/inner"));
-
- ASSERT_EQ(3u, state.edges_.size());
- EXPECT_EQ("varref outer", state.edges_[0]->EvaluateCommand());
- EXPECT_EQ("varref inner", state.edges_[1]->EvaluateCommand());
- EXPECT_EQ("varref outer", state.edges_[2]->EvaluateCommand());
-}
-
-TEST_F(ParserTest, MissingSubNinja) {
- ManifestParser parser(&state, &fs_);
- string err;
- EXPECT_FALSE(parser.ParseTest("subninja foo.ninja\n", &err));
- EXPECT_EQ("input:1: loading 'foo.ninja': No such file or directory\n"
- "subninja foo.ninja\n"
- " ^ near here"
- , err);
-}
-
-TEST_F(ParserTest, DuplicateRuleInDifferentSubninjas) {
- // Test that rules are scoped to subninjas.
- fs_.Create("test.ninja", "rule cat\n"
- " command = cat\n");
- ManifestParser parser(&state, &fs_);
- string err;
- EXPECT_TRUE(parser.ParseTest("rule cat\n"
- " command = cat\n"
- "subninja test.ninja\n", &err));
-}
-
-TEST_F(ParserTest, DuplicateRuleInDifferentSubninjasWithInclude) {
- // Test that rules are scoped to subninjas even with includes.
- fs_.Create("rules.ninja", "rule cat\n"
- " command = cat\n");
- fs_.Create("test.ninja", "include rules.ninja\n"
- "build x : cat\n");
- ManifestParser parser(&state, &fs_);
- string err;
- EXPECT_TRUE(parser.ParseTest("include rules.ninja\n"
- "subninja test.ninja\n"
- "build y : cat\n", &err));
-}
-
-TEST_F(ParserTest, Include) {
- fs_.Create("include.ninja", "var = inner\n");
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"var = outer\n"
-"include include.ninja\n"));
-
- ASSERT_EQ(1u, fs_.files_read_.size());
- EXPECT_EQ("include.ninja", fs_.files_read_[0]);
- EXPECT_EQ("inner", state.bindings().LookupVariable("var"));
-}
-
-TEST_F(ParserTest, BrokenInclude) {
- fs_.Create("include.ninja", "build\n");
- ManifestParser parser(&state, &fs_);
- string err;
- EXPECT_FALSE(parser.ParseTest("include include.ninja\n", &err));
- EXPECT_EQ("include.ninja:1: expected path\n"
- "build\n"
- " ^ near here"
- , err);
-}
-
-TEST_F(ParserTest, Implicit) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build foo: cat bar | baz\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_TRUE(edge->is_implicit(1));
-}
-
-TEST_F(ParserTest, OrderOnly) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n command = cat $in > $out\n"
-"build foo: cat bar || baz\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_TRUE(edge->is_order_only(1));
-}
-
-TEST_F(ParserTest, Validations) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n command = cat $in > $out\n"
-"build foo: cat bar |@ baz\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_EQ(edge->validations_.size(), 1);
- EXPECT_EQ(edge->validations_[0]->path(), "baz");
-}
-
-TEST_F(ParserTest, ImplicitOutput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build foo | imp: cat bar\n"));
-
- Edge* edge = state.LookupNode("imp")->in_edge();
- ASSERT_EQ(edge->outputs_.size(), 2);
- EXPECT_TRUE(edge->is_implicit_out(1));
-}
-
-TEST_F(ParserTest, ImplicitOutputEmpty) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build foo | : cat bar\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_EQ(edge->outputs_.size(), 1);
- EXPECT_FALSE(edge->is_implicit_out(0));
-}
-
-TEST_F(ParserTest, ImplicitOutputDupe) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build foo baz | foo baq foo: cat bar\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_EQ(edge->outputs_.size(), 3);
- EXPECT_FALSE(edge->is_implicit_out(0));
- EXPECT_FALSE(edge->is_implicit_out(1));
- EXPECT_TRUE(edge->is_implicit_out(2));
-}
-
-TEST_F(ParserTest, ImplicitOutputDupes) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build foo foo foo | foo foo foo foo: cat bar\n"));
-
- Edge* edge = state.LookupNode("foo")->in_edge();
- ASSERT_EQ(edge->outputs_.size(), 1);
- EXPECT_FALSE(edge->is_implicit_out(0));
-}
-
-TEST_F(ParserTest, NoExplicitOutput) {
- ManifestParser parser(&state, NULL);
- string err;
- EXPECT_TRUE(parser.ParseTest(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build | imp : cat bar\n", &err));
-}
-
-TEST_F(ParserTest, DefaultDefault) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n command = cat $in > $out\n"
-"build a: cat foo\n"
-"build b: cat foo\n"
-"build c: cat foo\n"
-"build d: cat foo\n"));
-
- string err;
- EXPECT_EQ(4u, state.DefaultNodes(&err).size());
- EXPECT_EQ("", err);
-}
-
-TEST_F(ParserTest, DefaultDefaultCycle) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n command = cat $in > $out\n"
-"build a: cat a\n"));
-
- string err;
- EXPECT_EQ(0u, state.DefaultNodes(&err).size());
- EXPECT_EQ("could not determine root nodes of build graph", err);
-}
-
-TEST_F(ParserTest, DefaultStatements) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n command = cat $in > $out\n"
-"build a: cat foo\n"
-"build b: cat foo\n"
-"build c: cat foo\n"
-"build d: cat foo\n"
-"third = c\n"
-"default a b\n"
-"default $third\n"));
-
- string err;
- vector<Node*> nodes = state.DefaultNodes(&err);
- EXPECT_EQ("", err);
- ASSERT_EQ(3u, nodes.size());
- EXPECT_EQ("a", nodes[0]->path());
- EXPECT_EQ("b", nodes[1]->path());
- EXPECT_EQ("c", nodes[2]->path());
-}
-
-TEST_F(ParserTest, UTF8) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule utf8\n"
-" command = true\n"
-" description = compilaci\xC3\xB3\n"));
-}
-
-TEST_F(ParserTest, CRLF) {
- State local_state;
- ManifestParser parser(&local_state, NULL);
- string err;
-
- EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err));
- EXPECT_TRUE(parser.ParseTest("foo = foo\nbar = bar\r\n", &err));
- EXPECT_TRUE(parser.ParseTest(
- "pool link_pool\r\n"
- " depth = 15\r\n\r\n"
- "rule xyz\r\n"
- " command = something$expand \r\n"
- " description = YAY!\r\n",
- &err));
-}
-
-TEST_F(ParserTest, DyndepNotSpecified) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build result: cat in\n"));
- Edge* edge = state.GetNode("result", 0)->in_edge();
- ASSERT_FALSE(edge->dyndep_);
-}
-
-TEST_F(ParserTest, DyndepNotInput) {
- State lstate;
- ManifestParser parser(&lstate, NULL);
- string err;
- EXPECT_FALSE(parser.ParseTest(
-"rule touch\n"
-" command = touch $out\n"
-"build result: touch\n"
-" dyndep = notin\n",
- &err));
- EXPECT_EQ("input:5: dyndep 'notin' is not an input\n", err);
-}
-
-TEST_F(ParserTest, DyndepExplicitInput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build result: cat in\n"
-" dyndep = in\n"));
- Edge* edge = state.GetNode("result", 0)->in_edge();
- ASSERT_TRUE(edge->dyndep_);
- EXPECT_TRUE(edge->dyndep_->dyndep_pending());
- EXPECT_EQ(edge->dyndep_->path(), "in");
-}
-
-TEST_F(ParserTest, DyndepImplicitInput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build result: cat in | dd\n"
-" dyndep = dd\n"));
- Edge* edge = state.GetNode("result", 0)->in_edge();
- ASSERT_TRUE(edge->dyndep_);
- EXPECT_TRUE(edge->dyndep_->dyndep_pending());
- EXPECT_EQ(edge->dyndep_->path(), "dd");
-}
-
-TEST_F(ParserTest, DyndepOrderOnlyInput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-"build result: cat in || dd\n"
-" dyndep = dd\n"));
- Edge* edge = state.GetNode("result", 0)->in_edge();
- ASSERT_TRUE(edge->dyndep_);
- EXPECT_TRUE(edge->dyndep_->dyndep_pending());
- EXPECT_EQ(edge->dyndep_->path(), "dd");
-}
-
-TEST_F(ParserTest, DyndepRuleInput) {
- ASSERT_NO_FATAL_FAILURE(AssertParse(
-"rule cat\n"
-" command = cat $in > $out\n"
-" dyndep = $in\n"
-"build result: cat in\n"));
- Edge* edge = state.GetNode("result", 0)->in_edge();
- ASSERT_TRUE(edge->dyndep_);
- EXPECT_TRUE(edge->dyndep_->dyndep_pending());
- EXPECT_EQ(edge->dyndep_->path(), "in");
-}
diff --git a/src/metrics.cc b/src/metrics.cc
deleted file mode 100644
index 88d0180..0000000
--- a/src/metrics.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// 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.
-
-#include "metrics.h"
-
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <algorithm>
-#include <chrono>
-
-#include "util.h"
-
-using namespace std;
-
-Metrics* g_metrics = NULL;
-
-namespace {
-
-/// Compute a platform-specific high-res timer value that fits into an int64.
-int64_t HighResTimer() {
- auto now = chrono::steady_clock::now();
- return chrono::duration_cast<chrono::steady_clock::duration>(
- now.time_since_epoch())
- .count();
-}
-
-constexpr int64_t GetFrequency() {
- // If numerator isn't 1 then we lose precision and that will need to be
- // assessed.
- static_assert(std::chrono::steady_clock::period::num == 1,
- "Numerator must be 1");
- return std::chrono::steady_clock::period::den /
- std::chrono::steady_clock::period::num;
-}
-
-int64_t TimerToMicros(int64_t dt) {
- // dt is in ticks. We want microseconds.
- return (dt * 1000000) / GetFrequency();
-}
-
-int64_t TimerToMicros(double dt) {
- // dt is in ticks. We want microseconds.
- return (dt * 1000000) / GetFrequency();
-}
-
-} // anonymous namespace
-
-ScopedMetric::ScopedMetric(Metric* metric) {
- metric_ = metric;
- if (!metric_)
- return;
- start_ = HighResTimer();
-}
-ScopedMetric::~ScopedMetric() {
- if (!metric_)
- return;
- metric_->count++;
- // Leave in the timer's natural frequency to avoid paying the conversion cost
- // on every measurement.
- int64_t dt = HighResTimer() - start_;
- metric_->sum += dt;
-}
-
-Metric* MetricsDomain::NewMetric(StringPiece name) {
- metrics_.emplace_back(new Metric(name));
- return metrics_.back().get();
-}
-
-void MetricsDomain::Reset() {
- for (auto& metric : metrics_) {
- metric->count = 0;
- metric->sum = 0;
- }
-}
-
-int MetricsDomain::ComputeMaxNameWidth(int max_width) {
- for (const auto& metric : metrics_)
- max_width = max((int)(metric->name.size()), max_width);
- return max_width;
-}
-
-// static
-void MetricsDomain::PrintBanner(int max_width) {
- printf("%-*s\t%-6s\t%-9s\t%s\n", max_width, "metric", "count", "avg (us)",
- "total (ms)");
-}
-
-void MetricsDomain::Print(int max_width) {
- for (const auto& metric : metrics_) {
- uint64_t micros = TimerToMicros(metric->sum);
- double total = micros / (double)1000;
- double avg = micros / (double)metric->count;
- printf("%-*s\t%-6d\t%-8.1f\t%.1f\n", max_width, metric->name.c_str(),
- metric->count, avg, total);
- }
-}
-
-void MetricsDomain::Report() {
- int max_width = ComputeMaxNameWidth(0);
- PrintBanner(max_width);
- Print(max_width);
-}
-
-void Metrics::Report() {
- int max_width = 0;
- max_width = load_.ComputeMaxNameWidth(max_width);
- max_width = build_.ComputeMaxNameWidth(max_width);
-
- MetricsDomain::PrintBanner(max_width);
- load_.Print(max_width);
- build_.Print(max_width);
-}
-
-double Stopwatch::Elapsed() const {
- // Convert to micros after converting to double to minimize error.
- return 1e-6 * TimerToMicros(static_cast<double>(NowRaw() - started_));
-}
-
-uint64_t Stopwatch::NowRaw() const {
- return HighResTimer();
-}
-
-int64_t GetTimeMillis() {
- return TimerToMicros(HighResTimer()) / 1000;
-}
diff --git a/src/metrics.h b/src/metrics.h
deleted file mode 100644
index a105984..0000000
--- a/src/metrics.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// 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.
-
-#ifndef NINJA_METRICS_H_
-#define NINJA_METRICS_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "string_piece.h"
-#include "util.h" // For int64_t.
-
-/// The Metrics module is used for the debug mode that dumps timing stats of
-/// various actions. To use, see METRIC_RECORD below.
-
-/// A single metrics we're tracking, like "depfile load time".
-struct Metric {
- Metric(StringPiece name) : name(name.AsString()) {}
-
- std::string name;
- /// Number of times we've hit the code path.
- int count = 0;
- /// Total time (in platform-dependent units) we've spent on the code path.
- int64_t sum = 0;
-};
-
-/// A scoped object for recording a metric across the body of a function.
-/// Used by the METRIC_RECORD macro.
-struct ScopedMetric {
- explicit ScopedMetric(Metric* metric);
- ~ScopedMetric();
-
-private:
- Metric* metric_;
- /// Timestamp when the measurement started.
- /// Value is platform-dependent.
- int64_t start_;
-};
-
-/// A set of metrics scoped to a given domain, e.g. manifest loading, or
-/// build invocation.
-struct MetricsDomain {
- Metric* NewMetric(StringPiece name);
-
- /// Reset all Metric instances in this domain.
- void Reset();
-
- /// Compute the max width of all metric names in this domain.
- /// If |cur_max_width| is not 0, then ensure the result is always greater
- /// or equal to it, which is useful when combining several domain reports.
- int ComputeMaxNameWidth(int cur_max_width = 0);
-
- /// Print a one-line table banner describing metric details.
- static void PrintBanner(int max_width);
-
- /// Print a summary of all metrics to stdout, without any banner.
- void Print(int max_width = 0);
-
- /// Print a summary report for just this domain to stdout.
- void Report();
-
-private:
- std::vector<std::unique_ptr<Metric>> metrics_;
-};
-
-/// The set of Ninja metrics domains.
-struct Metrics {
- /// Print a summary report for all domains to stdout.
- void Report();
-
- MetricsDomain load_;
- MetricsDomain build_;
-};
-
-/// Get the current time as relative to some epoch.
-/// Epoch varies between platforms; only useful for measuring elapsed time.
-int64_t GetTimeMillis();
-
-/// A simple stopwatch which returns the time
-/// in seconds since Restart() was called.
-struct Stopwatch {
- public:
- Stopwatch() : started_(0) {}
-
- /// Seconds since Restart() call.
- double Elapsed() const;
-
- void Restart() { started_ = NowRaw(); }
-
- private:
- uint64_t started_;
- // Return the current time using the native frequency of the high resolution
- // timer.
- uint64_t NowRaw() const;
-};
-
-/// The primary interface to metrics. Use METRIC_RECORD("foobar") at the top
-/// of a function to get timing stats recorded for each call of the function.
-/// This creates a metric in the "build" domain, for operations performed
-/// when loading the manifest, use METRIC_RECORD_LOAD() instead.
-#define METRIC_RECORD(name) \
- static Metric* metrics_h_metric = \
- g_metrics ? g_metrics->build_.NewMetric(name) : NULL; \
- ScopedMetric metrics_h_scoped(metrics_h_metric);
-
-/// A variant of METRIC_RECORD() for timing manifest loading operations.
-#define METRIC_RECORD_LOAD(name) \
- static Metric* metrics_h_metric = \
- g_metrics ? g_metrics->load_.NewMetric(name) : NULL; \
- ScopedMetric metrics_h_scoped(metrics_h_metric);
-
-/// A variant of METRIC_RECORD that doesn't record anything if |condition|
-/// is false.
-#define METRIC_RECORD_IF(name, condition) \
- static Metric* metrics_h_metric = \
- g_metrics ? g_metrics->build_.NewMetric(name) : NULL; \
- ScopedMetric metrics_h_scoped((condition) ? metrics_h_metric : NULL);
-
-/// A variant of METRIC_RECORD_IF for timing manifest loading operations.
-#define METRIC_RECORD_LOAD_IF(name, condition) \
- static Metric* metrics_h_metric = \
- g_metrics ? g_metrics->load_.NewMetric(name) : NULL; \
- ScopedMetric metrics_h_scoped((condition) ? metrics_h_metric : NULL);
-
-extern Metrics* g_metrics;
-
-#endif // NINJA_METRICS_H_
diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc
deleted file mode 100644
index 9aea767..0000000
--- a/src/minidump-win32.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2012 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.
-
-#ifdef _MSC_VER
-
-#include <windows.h>
-#include <DbgHelp.h>
-
-#include "util.h"
-
-using namespace std;
-
-typedef BOOL (WINAPI *MiniDumpWriteDumpFunc) (
- IN HANDLE,
- IN DWORD,
- IN HANDLE,
- IN MINIDUMP_TYPE,
- IN CONST PMINIDUMP_EXCEPTION_INFORMATION, OPTIONAL
- IN CONST PMINIDUMP_USER_STREAM_INFORMATION, OPTIONAL
- IN CONST PMINIDUMP_CALLBACK_INFORMATION OPTIONAL
- );
-
-/// Creates a windows minidump in temp folder.
-void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) {
- char temp_path[MAX_PATH];
- GetTempPathA(sizeof(temp_path), temp_path);
- char temp_file[MAX_PATH];
- sprintf(temp_file, "%s\\ninja_crash_dump_%lu.dmp",
- temp_path, GetCurrentProcessId());
-
- // Delete any previous minidump of the same name.
- DeleteFileA(temp_file);
-
- // Load DbgHelp.dll dynamically, as library is not present on all
- // Windows versions.
- HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
- if (dbghelp == NULL) {
- Error("failed to create minidump: LoadLibrary('dbghelp.dll'): %s",
- GetLastErrorString().c_str());
- return;
- }
-
- MiniDumpWriteDumpFunc mini_dump_write_dump =
- (MiniDumpWriteDumpFunc)GetProcAddress(dbghelp, "MiniDumpWriteDump");
- if (mini_dump_write_dump == NULL) {
- Error("failed to create minidump: GetProcAddress('MiniDumpWriteDump'): %s",
- GetLastErrorString().c_str());
- return;
- }
-
- HANDLE hFile = CreateFileA(temp_file, GENERIC_READ | GENERIC_WRITE, 0, NULL,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if (hFile == NULL) {
- Error("failed to create minidump: CreateFileA(%s): %s",
- temp_file, GetLastErrorString().c_str());
- return;
- }
-
- MINIDUMP_EXCEPTION_INFORMATION mdei;
- mdei.ThreadId = GetCurrentThreadId();
- mdei.ExceptionPointers = pep;
- mdei.ClientPointers = FALSE;
- MINIDUMP_TYPE mdt = (MINIDUMP_TYPE) (MiniDumpWithDataSegs |
- MiniDumpWithHandleData);
-
- BOOL rv = mini_dump_write_dump(GetCurrentProcess(), GetCurrentProcessId(),
- hFile, mdt, (pep != 0) ? &mdei : 0, 0, 0);
- CloseHandle(hFile);
-
- if (!rv) {
- Error("MiniDumpWriteDump failed: %s", GetLastErrorString().c_str());
- return;
- }
-
- Warning("minidump created: %s", temp_file);
-}
-
-#endif // _MSC_VER
diff --git a/src/missing_deps.cc b/src/missing_deps.cc
deleted file mode 100644
index de76620..0000000
--- a/src/missing_deps.cc
+++ /dev/null
@@ -1,192 +0,0 @@
-// 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.
-
-#include "missing_deps.h"
-
-#include <string.h>
-
-#include <iostream>
-
-#include "depfile_parser.h"
-#include "deps_log.h"
-#include "disk_interface.h"
-#include "graph.h"
-#include "state.h"
-#include "util.h"
-
-namespace {
-
-/// ImplicitDepLoader variant that stores dep nodes into the given output
-/// without updating graph deps like the base loader does.
-struct NodeStoringImplicitDepLoader : public ImplicitDepLoader {
- NodeStoringImplicitDepLoader(
- State* state, DepsLog* deps_log, DiskInterface* disk_interface,
- DepfileParserOptions const* depfile_parser_options,
- std::vector<Node*>* dep_nodes_output)
- : ImplicitDepLoader(state, deps_log, disk_interface,
- depfile_parser_options),
- dep_nodes_output_(dep_nodes_output) {}
-
- protected:
- virtual bool ProcessDepfileDeps(Edge* edge,
- std::vector<StringPiece>* depfile_ins,
- std::string* err);
-
- private:
- std::vector<Node*>* dep_nodes_output_;
-};
-
-bool NodeStoringImplicitDepLoader::ProcessDepfileDeps(
- Edge* edge, std::vector<StringPiece>* depfile_ins, std::string* err) {
- for (std::vector<StringPiece>::iterator i = depfile_ins->begin();
- i != depfile_ins->end(); ++i) {
- uint64_t slash_bits;
- CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits);
- Node* node = state_->GetNode(*i, slash_bits);
- dep_nodes_output_->push_back(node);
- }
- return true;
-}
-
-} // namespace
-
-MissingDependencyScannerDelegate::~MissingDependencyScannerDelegate() {}
-
-void MissingDependencyPrinter::OnMissingDep(Node* node, const std::string& path,
- const Rule& generator) {
- std::cout << "Missing dep: " << node->path() << " uses " << path
- << " (generated by " << generator.name() << ")\n";
-}
-
-MissingDependencyScanner::MissingDependencyScanner(
- MissingDependencyScannerDelegate* delegate, DepsLog* deps_log, State* state,
- DiskInterface* disk_interface)
- : delegate_(delegate), deps_log_(deps_log), state_(state),
- disk_interface_(disk_interface), missing_dep_path_count_(0) {}
-
-void MissingDependencyScanner::ProcessNode(Node* node) {
- if (!node)
- return;
- Edge* edge = node->in_edge();
- if (!edge)
- return;
- if (!seen_.insert(node).second)
- return;
-
- for (std::vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in) {
- ProcessNode(*in);
- }
-
- std::string deps_type = edge->GetBinding("deps");
- if (!deps_type.empty()) {
- DepsLog::Deps* deps = deps_log_->GetDeps(node);
- if (deps)
- ProcessNodeDeps(node, deps->nodes, deps->node_count);
- } else {
- DepfileParserOptions parser_opts;
- std::vector<Node*> depfile_deps;
- NodeStoringImplicitDepLoader dep_loader(state_, deps_log_, disk_interface_,
- &parser_opts, &depfile_deps);
- std::string err;
- dep_loader.LoadDeps(edge, &err);
- if (!depfile_deps.empty())
- ProcessNodeDeps(node, &depfile_deps[0], depfile_deps.size());
- }
-}
-
-void MissingDependencyScanner::ProcessNodeDeps(Node* node, Node** dep_nodes,
- int dep_nodes_count) {
- Edge* edge = node->in_edge();
- std::set<Edge*> deplog_edges;
- for (int i = 0; i < dep_nodes_count; ++i) {
- Node* deplog_node = dep_nodes[i];
- // Special exception: A dep on build.ninja can be used to mean "always
- // rebuild this target when the build is reconfigured", but build.ninja is
- // often generated by a configuration tool like cmake or gn. The rest of
- // the build "implicitly" depends on the entire build being reconfigured,
- // so a missing dep path to build.ninja is not an actual missing dependency
- // problem.
- if (deplog_node->path() == "build.ninja")
- return;
- Edge* deplog_edge = deplog_node->in_edge();
- if (deplog_edge) {
- deplog_edges.insert(deplog_edge);
- }
- }
- std::vector<Edge*> missing_deps;
- for (std::set<Edge*>::iterator de = deplog_edges.begin();
- de != deplog_edges.end(); ++de) {
- if (!PathExistsBetween(*de, edge)) {
- missing_deps.push_back(*de);
- }
- }
-
- if (!missing_deps.empty()) {
- std::set<std::string> missing_deps_rule_names;
- for (std::vector<Edge*>::iterator ne = missing_deps.begin();
- ne != missing_deps.end(); ++ne) {
- for (int i = 0; i < dep_nodes_count; ++i) {
- if (dep_nodes[i]->in_edge() == *ne) {
- generated_nodes_.insert(dep_nodes[i]);
- generator_rules_.insert(&(*ne)->rule());
- missing_deps_rule_names.insert((*ne)->rule().name());
- delegate_->OnMissingDep(node, dep_nodes[i]->path(), (*ne)->rule());
- }
- }
- }
- missing_dep_path_count_ += missing_deps_rule_names.size();
- nodes_missing_deps_.insert(node);
- }
-}
-
-void MissingDependencyScanner::PrintStats() {
- std::cout << "Processed " << seen_.size() << " nodes.\n";
- if (HadMissingDeps()) {
- std::cout << "Error: There are " << missing_dep_path_count_
- << " missing dependency paths.\n";
- std::cout << nodes_missing_deps_.size()
- << " targets had depfile dependencies on "
- << generated_nodes_.size() << " distinct generated inputs "
- << "(from " << generator_rules_.size() << " rules) "
- << " without a non-depfile dep path to the generator.\n";
- std::cout << "There might be build flakiness if any of the targets listed "
- "above are built alone, or not late enough, in a clean output "
- "directory.\n";
- } else {
- std::cout << "No missing dependencies on generated files found.\n";
- }
-}
-
-bool MissingDependencyScanner::PathExistsBetween(Edge* from, Edge* to) {
- AdjacencyMap::iterator it = adjacency_map_.find(from);
- if (it != adjacency_map_.end()) {
- InnerAdjacencyMap::iterator inner_it = it->second.find(to);
- if (inner_it != it->second.end()) {
- return inner_it->second;
- }
- } else {
- it = adjacency_map_.insert(std::make_pair(from, InnerAdjacencyMap())).first;
- }
- bool found = false;
- for (size_t i = 0; i < to->inputs_.size(); ++i) {
- Edge* e = to->inputs_[i]->in_edge();
- if (e && (e == from || PathExistsBetween(from, e))) {
- found = true;
- break;
- }
- }
- it->second.insert(std::make_pair(to, found));
- return found;
-}
diff --git a/src/missing_deps.h b/src/missing_deps.h
deleted file mode 100644
index 7a615da..0000000
--- a/src/missing_deps.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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_MISSING_DEPS_H_
-#define NINJA_MISSING_DEPS_H_
-
-#include <map>
-#include <set>
-#include <string>
-
-#include <unordered_map>
-
-struct DepsLog;
-struct DiskInterface;
-struct Edge;
-struct Node;
-struct Rule;
-struct State;
-
-class MissingDependencyScannerDelegate {
- public:
- virtual ~MissingDependencyScannerDelegate();
- virtual void OnMissingDep(Node* node, const std::string& path,
- const Rule& generator) = 0;
-};
-
-class MissingDependencyPrinter : public MissingDependencyScannerDelegate {
- void OnMissingDep(Node* node, const std::string& path, const Rule& generator);
- void OnStats(int nodes_processed, int nodes_missing_deps,
- int missing_dep_path_count, int generated_nodes,
- int generator_rules);
-};
-
-struct MissingDependencyScanner {
- public:
- MissingDependencyScanner(MissingDependencyScannerDelegate* delegate,
- DepsLog* deps_log, State* state,
- DiskInterface* disk_interface);
- void ProcessNode(Node* node);
- void PrintStats();
- bool HadMissingDeps() { return !nodes_missing_deps_.empty(); }
-
- void ProcessNodeDeps(Node* node, Node** dep_nodes, int dep_nodes_count);
-
- bool PathExistsBetween(Edge* from, Edge* to);
-
- MissingDependencyScannerDelegate* delegate_;
- DepsLog* deps_log_;
- State* state_;
- DiskInterface* disk_interface_;
- std::set<Node*> seen_;
- std::set<Node*> nodes_missing_deps_;
- std::set<Node*> generated_nodes_;
- std::set<const Rule*> generator_rules_;
- int missing_dep_path_count_;
-
- private:
- using InnerAdjacencyMap = std::unordered_map<Edge*, bool>;
- using AdjacencyMap = std::unordered_map<Edge*, InnerAdjacencyMap>;
- AdjacencyMap adjacency_map_;
-};
-
-#endif // NINJA_MISSING_DEPS_H_
diff --git a/src/missing_deps_test.cc b/src/missing_deps_test.cc
deleted file mode 100644
index db66885..0000000
--- a/src/missing_deps_test.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-// 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.
-
-#include <memory>
-
-#include "deps_log.h"
-#include "graph.h"
-#include "missing_deps.h"
-#include "state.h"
-#include "test.h"
-
-const char kTestDepsLogFilename[] = "MissingDepTest-tempdepslog";
-
-class MissingDependencyTestDelegate : public MissingDependencyScannerDelegate {
- void OnMissingDep(Node* node, const std::string& path,
- const Rule& generator) {}
-};
-
-struct MissingDependencyScannerTest : public testing::Test {
- MissingDependencyScannerTest()
- : generator_rule_("generator_rule"), compile_rule_("compile_rule"),
- scanner_(&delegate_, &deps_log_, &state_, &filesystem_) {
- std::string err;
- deps_log_.OpenForWrite(kTestDepsLogFilename, &err);
- ASSERT_EQ("", err);
- }
-
- MissingDependencyScanner& scanner() { return scanner_; }
-
- void RecordDepsLogDep(const std::string& from, const std::string& to) {
- Node* node_deps[] = { state_.LookupNode(to) };
- deps_log_.RecordDeps(state_.LookupNode(from), 0, 1, node_deps);
- }
-
- void ProcessAllNodes() {
- std::string err;
- std::vector<Node*> nodes = state_.RootNodes(&err);
- EXPECT_EQ("", err);
- for (std::vector<Node*>::iterator it = nodes.begin(); it != nodes.end();
- ++it) {
- scanner().ProcessNode(*it);
- }
- }
-
- void CreateInitialState() {
- EvalString deps_type;
- deps_type.AddText("gcc");
- compile_rule_.AddBinding("deps", deps_type);
- generator_rule_.AddBinding("deps", deps_type);
- Edge* header_edge = state_.AddEdge(&generator_rule_);
- state_.AddOut(header_edge, "generated_header", 0);
- Edge* compile_edge = state_.AddEdge(&compile_rule_);
- state_.AddOut(compile_edge, "compiled_object", 0);
- }
-
- void CreateGraphDependencyBetween(const char* from, const char* to) {
- Node* from_node = state_.LookupNode(from);
- Edge* from_edge = from_node->in_edge();
- state_.AddIn(from_edge, to, 0);
- }
-
- void AssertMissingDependencyBetween(const char* flaky, const char* generated,
- Rule* rule) {
- Node* flaky_node = state_.LookupNode(flaky);
- ASSERT_EQ(1u, scanner().nodes_missing_deps_.count(flaky_node));
- Node* generated_node = state_.LookupNode(generated);
- ASSERT_EQ(1u, scanner().generated_nodes_.count(generated_node));
- ASSERT_EQ(1u, scanner().generator_rules_.count(rule));
- }
-
- MissingDependencyTestDelegate delegate_;
- Rule generator_rule_;
- Rule compile_rule_;
- DepsLog deps_log_;
- State state_;
- VirtualFileSystem filesystem_;
- MissingDependencyScanner scanner_;
-};
-
-TEST_F(MissingDependencyScannerTest, EmptyGraph) {
- ProcessAllNodes();
- ASSERT_FALSE(scanner().HadMissingDeps());
-}
-
-TEST_F(MissingDependencyScannerTest, NoMissingDep) {
- CreateInitialState();
- ProcessAllNodes();
- ASSERT_FALSE(scanner().HadMissingDeps());
-}
-
-TEST_F(MissingDependencyScannerTest, MissingDepPresent) {
- CreateInitialState();
- // compiled_object uses generated_header, without a proper dependency
- RecordDepsLogDep("compiled_object", "generated_header");
- ProcessAllNodes();
- ASSERT_TRUE(scanner().HadMissingDeps());
- ASSERT_EQ(1u, scanner().nodes_missing_deps_.size());
- ASSERT_EQ(1u, scanner().missing_dep_path_count_);
- AssertMissingDependencyBetween("compiled_object", "generated_header",
- &generator_rule_);
-}
-
-TEST_F(MissingDependencyScannerTest, MissingDepFixedDirect) {
- CreateInitialState();
- // Adding the direct dependency fixes the missing dep
- CreateGraphDependencyBetween("compiled_object", "generated_header");
- RecordDepsLogDep("compiled_object", "generated_header");
- ProcessAllNodes();
- ASSERT_FALSE(scanner().HadMissingDeps());
-}
-
-TEST_F(MissingDependencyScannerTest, MissingDepFixedIndirect) {
- CreateInitialState();
- // Adding an indirect dependency also fixes the issue
- Edge* intermediate_edge = state_.AddEdge(&generator_rule_);
- state_.AddOut(intermediate_edge, "intermediate", 0);
- CreateGraphDependencyBetween("compiled_object", "intermediate");
- CreateGraphDependencyBetween("intermediate", "generated_header");
- RecordDepsLogDep("compiled_object", "generated_header");
- ProcessAllNodes();
- ASSERT_FALSE(scanner().HadMissingDeps());
-}
-
-TEST_F(MissingDependencyScannerTest, CyclicMissingDep) {
- CreateInitialState();
- RecordDepsLogDep("generated_header", "compiled_object");
- RecordDepsLogDep("compiled_object", "generated_header");
- // In case of a cycle, both paths are reported (and there is
- // no way to fix the issue by adding deps).
- ProcessAllNodes();
- ASSERT_TRUE(scanner().HadMissingDeps());
- ASSERT_EQ(2u, scanner().nodes_missing_deps_.size());
- ASSERT_EQ(2u, scanner().missing_dep_path_count_);
- AssertMissingDependencyBetween("compiled_object", "generated_header",
- &generator_rule_);
- AssertMissingDependencyBetween("generated_header", "compiled_object",
- &compile_rule_);
-}
-
-TEST_F(MissingDependencyScannerTest, CycleInGraph) {
- CreateInitialState();
- CreateGraphDependencyBetween("compiled_object", "generated_header");
- CreateGraphDependencyBetween("generated_header", "compiled_object");
- // The missing-deps tool doesn't deal with cycles in the graph, because
- // there will be an error loading the graph before we get to the tool.
- // This test is to illustrate that.
- std::string err;
- std::vector<Node*> nodes = state_.RootNodes(&err);
- ASSERT_NE("", err);
-}
-
diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc
deleted file mode 100644
index 1148ae5..0000000
--- a/src/msvc_helper-win32.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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.
-
-#include "msvc_helper.h"
-
-#include <windows.h>
-
-#include "util.h"
-
-using namespace std;
-
-namespace {
-
-string Replace(const string& input, const string& find, const string& replace) {
- string result = input;
- size_t start_pos = 0;
- while ((start_pos = result.find(find, start_pos)) != string::npos) {
- result.replace(start_pos, find.length(), replace);
- start_pos += replace.length();
- }
- return result;
-}
-
-} // anonymous namespace
-
-string EscapeForDepfile(const string& path) {
- // Depfiles don't escape single \.
- return Replace(path, " ", "\\ ");
-}
-
-int CLWrapper::Run(const string& command, string* output) {
- SECURITY_ATTRIBUTES security_attributes = {};
- security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
- security_attributes.bInheritHandle = TRUE;
-
- // Must be inheritable so subprocesses can dup to children.
- HANDLE nul =
- CreateFileA("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
- if (nul == INVALID_HANDLE_VALUE)
- Fatal("couldn't open nul");
-
- HANDLE stdout_read, stdout_write;
- if (!CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0))
- Win32Fatal("CreatePipe");
-
- if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0))
- Win32Fatal("SetHandleInformation");
-
- PROCESS_INFORMATION process_info = {};
- STARTUPINFOA startup_info = {};
- startup_info.cb = sizeof(STARTUPINFOA);
- startup_info.hStdInput = nul;
- startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
- startup_info.hStdOutput = stdout_write;
- startup_info.dwFlags |= STARTF_USESTDHANDLES;
-
- if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL,
- /* inherit handles */ TRUE, 0,
- env_block_, NULL,
- &startup_info, &process_info)) {
- Win32Fatal("CreateProcess");
- }
-
- if (!CloseHandle(nul) ||
- !CloseHandle(stdout_write)) {
- Win32Fatal("CloseHandle");
- }
-
- // Read all output of the subprocess.
- DWORD read_len = 1;
- while (read_len) {
- char buf[64 << 10];
- read_len = 0;
- if (!::ReadFile(stdout_read, buf, sizeof(buf), &read_len, NULL) &&
- GetLastError() != ERROR_BROKEN_PIPE) {
- Win32Fatal("ReadFile");
- }
- output->append(buf, read_len);
- }
-
- // Wait for it to exit and grab its exit code.
- if (WaitForSingleObject(process_info.hProcess, INFINITE) == WAIT_FAILED)
- Win32Fatal("WaitForSingleObject");
- DWORD exit_code = 0;
- if (!GetExitCodeProcess(process_info.hProcess, &exit_code))
- Win32Fatal("GetExitCodeProcess");
-
- if (!CloseHandle(stdout_read) ||
- !CloseHandle(process_info.hProcess) ||
- !CloseHandle(process_info.hThread)) {
- Win32Fatal("CloseHandle");
- }
-
- return exit_code;
-}
diff --git a/src/msvc_helper.h b/src/msvc_helper.h
deleted file mode 100644
index 568b9f9..0000000
--- a/src/msvc_helper.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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.
-
-#include <string>
-
-std::string EscapeForDepfile(const std::string& path);
-
-/// Wraps a synchronous execution of a CL subprocess.
-struct CLWrapper {
- CLWrapper() : env_block_(NULL) {}
-
- /// Set the environment block (as suitable for CreateProcess) to be used
- /// by Run().
- void SetEnvBlock(void* env_block) { env_block_ = env_block; }
-
- /// Start a process and gather its raw output. Returns its exit code.
- /// Crashes (calls Fatal()) on error.
- 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
deleted file mode 100644
index 7d59307..0000000
--- a/src/msvc_helper_main-win32.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// 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.
-
-#include "msvc_helper.h"
-
-#include <fcntl.h>
-#include <io.h>
-#include <stdio.h>
-#include <windows.h>
-
-#include "clparser.h"
-#include "util.h"
-
-#include "getopt.h"
-
-using namespace std;
-
-namespace {
-
-void Usage() {
- printf(
-"usage: ninja -t msvc [options] -- cl.exe /showIncludes /otherArgs\n"
-"options:\n"
-" -e ENVFILE load environment block from ENVFILE as environment\n"
-" -o FILE write output dependency information to FILE.d\n"
-" -p STRING localized prefix of msvc's /showIncludes output\n"
- );
-}
-
-void PushPathIntoEnvironment(const string& env_block) {
- const char* as_str = env_block.c_str();
- while (as_str[0]) {
- if (_strnicmp(as_str, "path=", 5) == 0) {
- _putenv(as_str);
- return;
- } else {
- as_str = &as_str[strlen(as_str) + 1];
- }
- }
-}
-
-void WriteDepFileOrDie(const char* object_path, const CLParser& parse) {
- string depfile_path = string(object_path) + ".d";
- FILE* depfile = fopen(depfile_path.c_str(), "w");
- if (!depfile) {
- unlink(object_path);
- Fatal("opening %s: %s", depfile_path.c_str(),
- GetLastErrorString().c_str());
- }
- if (fprintf(depfile, "%s: ", object_path) < 0) {
- unlink(object_path);
- fclose(depfile);
- unlink(depfile_path.c_str());
- Fatal("writing %s", depfile_path.c_str());
- }
- const set<string>& headers = parse.includes_;
- for (set<string>::const_iterator i = headers.begin();
- i != headers.end(); ++i) {
- if (fprintf(depfile, "%s\n", EscapeForDepfile(*i).c_str()) < 0) {
- unlink(object_path);
- fclose(depfile);
- unlink(depfile_path.c_str());
- Fatal("writing %s", depfile_path.c_str());
- }
- }
- fclose(depfile);
-}
-
-} // anonymous namespace
-
-int MSVCHelperMain(int argc, char** argv) {
- const char* output_filename = NULL;
- const char* envfile = NULL;
-
- const option kLongOptions[] = {
- { "help", no_argument, NULL, 'h' },
- { NULL, 0, NULL, 0 }
- };
- int opt;
- string deps_prefix;
- while ((opt = getopt_long(argc, argv, "e:o:p:h", kLongOptions, NULL)) != -1) {
- switch (opt) {
- case 'e':
- envfile = optarg;
- break;
- case 'o':
- output_filename = optarg;
- break;
- case 'p':
- deps_prefix = optarg;
- break;
- case 'h':
- default:
- Usage();
- return 0;
- }
- }
-
- string env;
- if (envfile) {
- string err;
- if (ReadFile(envfile, &env, &err) != 0)
- Fatal("couldn't open %s: %s", envfile, err.c_str());
- PushPathIntoEnvironment(env);
- }
-
- char* command = GetCommandLineA();
- command = strstr(command, " -- ");
- if (!command) {
- Fatal("expected command line to end with \" -- command args\"");
- }
- command += 4;
-
- CLWrapper cl;
- if (!env.empty())
- cl.SetEnvBlock((void*)env.data());
- string output;
- int exit_code = cl.Run(command, &output);
-
- if (output_filename) {
- CLParser parser;
- string err;
- if (!parser.Parse(output, deps_prefix, &output, &err))
- Fatal("%s\n", err.c_str());
- WriteDepFileOrDie(output_filename, parser);
- }
-
- if (output.empty())
- return exit_code;
-
- // CLWrapper's output already as \r\n line endings, make sure the C runtime
- // doesn't expand this to \r\r\n.
- _setmode(_fileno(stdout), _O_BINARY);
- // Avoid printf and C strings, since the actual output might contain null
- // bytes like UTF-16 does (yuck).
- fwrite(&output[0], 1, output.size(), stdout);
-
- return exit_code;
-}
diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc
deleted file mode 100644
index d9e2ee6..0000000
--- a/src/msvc_helper_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.
-
-#include "msvc_helper.h"
-
-#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"));
-}
-
-TEST(MSVCHelperTest, EnvBlock) {
- char env_block[] = "foo=bar\0";
- CLWrapper cl;
- cl.SetEnvBlock(env_block);
- string output;
- cl.Run("cmd /c \"echo foo is %foo%", &output);
- ASSERT_EQ("foo is bar\r\n", output);
-}
-
-TEST(MSVCHelperTest, NoReadOfStderr) {
- CLWrapper cl;
- string output;
- cl.Run("cmd /c \"echo to stdout&& echo to stderr 1>&2", &output);
- ASSERT_EQ("to stdout\r\n", output);
-}
diff --git a/src/ninja.cc b/src/ninja.cc
deleted file mode 100644
index df67736..0000000
--- a/src/ninja.cc
+++ /dev/null
@@ -1,1974 +0,0 @@
-// 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.
-
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <algorithm>
-#include <cstdlib>
-
-#ifdef _WIN32
-#include "getopt.h"
-#include <direct.h>
-#include <windows.h>
-#elif defined(_AIX)
-#include "getopt.h"
-#include <unistd.h>
-#else
-#include <getopt.h>
-#include <unistd.h>
-#endif
-
-#include "async_loop.h"
-#include "browse.h"
-#include "build.h"
-#include "build_config.h"
-#include "build_log.h"
-#include "clean.h"
-#include "debug_flags.h"
-#include "depfile_parser.h"
-#include "deps_log.h"
-#include "disk_interface.h"
-#include "graph.h"
-#include "graphviz.h"
-#include "ipc_utils.h"
-#include "json.h"
-#include "manifest_parser.h"
-#include "metrics.h"
-#include "missing_deps.h"
-#include "persistent_mode.h"
-#include "state.h"
-#include "status.h"
-#include "string_piece.h"
-#include "util.h"
-#include "version.h"
-
-using namespace std;
-
-#ifdef _WIN32
-// Defined in msvc_helper_main-win32.cc.
-int MSVCHelperMain(int argc, char** argv);
-
-// Defined in minidump-win32.cc.
-void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep);
-#endif
-
-namespace {
-
-struct Tool;
-
-/// Command-line options.
-struct Options {
- /// Directory to change into before running.
- const char* working_dir = nullptr;
-
- /// Tool to run rather than building.
- const Tool* tool = nullptr;
-
- /// Whether duplicate rules for one target should warn or print an error.
- bool dupe_edges_should_err = true;
-
- /// Whether phony cycles should warn or print an error.
- bool phony_cycle_should_err = false;
-};
-
-/// A special value return by NinjaMain::RunBeforeBuild() to
-/// indicate that the build can proceed after loading the manifest
-/// and the logs. Must be strictly negative.
-static const int kBuildCanProceed = -1;
-
-/// The Ninja main() loads up a series of data structures; various tools need
-/// to poke into these, so store them as fields on an object.
-struct NinjaMain : public BuildLogUser {
- NinjaMain(const char* ninja_command, const BuildConfig& config)
- : ninja_command_(ninja_command), config_(&config),
- status_(new StatusPrinter(config)),
- path_recording_disk_interface_(&disk_interface_),
- start_time_millis_(GetTimeMillis()) {}
-
- /// Command line used to run Ninja.
- const char* ninja_command_;
-
- /// Build configuration set from flags (e.g. parallelism).
- const BuildConfig* config_;
-
- /// The Status instance to send update status messages.
- Status* status() const { return status_.get(); }
-
- std::unique_ptr<Status> status_;
-
- /// Completely reset state. This gets rid of the current build graph,
- /// rule definitions, logs and timestamp caches.
- void Reset() {
- // Note: state_.Reset() does not fully reset the variable!
- build_log_ = BuildLog();
-
- // deps_log_ contains pointers into Node instance owned by state_, so
- // reset it before it.
- deps_log_ = DepsLog();
-
- state_ = State();
- start_time_millis_ = GetTimeMillis();
- path_recording_disk_interface_.Reset();
- disk_interface_.FlushCache();
- }
-
- /// Reset just enough state to allow a new (possibly incremental) build.
- /// This keeps the build graph in memory, but resets its state properly
- /// to avoid relying on previously computed data from a previous build
- /// (e.g. timestamps, or undrained pools when the build was
- /// user-interrupted).
- void PrepareBuild(const BuildConfig& config) {
- state_.Reset();
- start_time_millis_ = GetTimeMillis();
- config_ = &config;
- // Reset status, since stdio was redirected when in persistent
- // server process.
- status_.reset(new StatusPrinter(config));
- disk_interface_.Sync();
- }
-
- /// Loaded state (rules, nodes).
- State state_;
-
- /// Functions for accessing the disk.
- RealDiskInterface disk_interface_;
-
- /// A DiskInterface instance that records which input files were opened
- /// when loading manifests. Used later to shutdown a persistent server
- /// automatically if any one of these files changed.
- PathRecordingFileReader path_recording_disk_interface_;
-
- /// The build directory, used for storing the build log etc.
- string build_dir_;
-
- BuildLog build_log_;
- DepsLog deps_log_;
-
- /// The type of functions that are the entry points to tools (subcommands).
- typedef int (NinjaMain::*ToolFunc)(const Options*, int, char**);
-
- /// Get the Node for a given command-line path, handling features like
- /// spell correction.
- Node* CollectTarget(const char* cpath, string* err);
-
- /// CollectTarget for all command-line arguments, filling in \a targets.
- bool CollectTargetsFromArgs(int argc, char* argv[],
- vector<Node*>* targets, string* err);
-
- // The various subcommands, run via "-t XXX".
- int ToolGraph(const Options* options, int argc, char* argv[]);
- int ToolQuery(const Options* options, int argc, char* argv[]);
- int ToolDeps(const Options* options, int argc, char* argv[]);
- int ToolMissingDeps(const Options* options, int argc, char* argv[]);
- int ToolBrowse(const Options* options, int argc, char* argv[]);
- int ToolMSVC(const Options* options, int argc, char* argv[]);
- int ToolTargets(const Options* options, int argc, char* argv[]);
- int ToolCommands(const Options* options, int argc, char* argv[]);
- int ToolInputs(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[]);
- int ToolWinCodePage(const Options* options, int argc, char* argv[]);
- int ToolServer(const Options* options, int argc, char* argv[]);
-
- /// Open the build log.
- /// @return false on error.
- bool OpenBuildLog(bool recompact_only = false);
-
- /// Open the deps log: load it, then open for writing.
- /// @return false on error.
- bool OpenDepsLog(bool recompact_only = false);
-
- /// Ensure the build directory exists, creating it if necessary.
- /// @return false on error.
- bool EnsureBuildDirExists();
-
- /// Rebuild the manifest, if necessary.
- /// Fills in \a err on error.
- /// @return true if the manifest was rebuilt.
- bool RebuildManifest(std::string* err, bool silent_dry_run = false);
-
- /// Perform all operations before the build itself, i.e.
- /// load the manifest, the build log, the deps log, and
- /// any tool if needed. Return kBuildCanProceed to indicate that
- /// the build can proceed after the call, or a positive process
- /// exit code to tell Ninja to stop.
- int RunBeforeBuild(const Options& options, int argc, char** argv);
-
- /// Build the targets listed on the command line.
- /// @return an exit code.
- int RunBuild(int argc, char** argv);
-
- /// Dump the output requested by '-d stats'.
- void DumpMetrics();
-
- virtual bool IsPathDead(StringPiece s) const {
- Node* n = state_.LookupNode(s);
- if (n && n->in_edge())
- return false;
- // Just checking n isn't enough: If an old output is both in the build log
- // and in the deps log, it will have a Node object in state_. (It will also
- // have an in edge if one of its inputs is another output that's in the deps
- // log, but having a deps edge product an output that's input to another deps
- // edge is rare, and the first recompaction will delete all old outputs from
- // the deps log, and then a second recompaction will clear the build log,
- // which seems good enough for this corner case.)
- // Do keep entries around for files which still exist on disk, for
- // generators that want to use this information.
- string err;
- TimeStamp mtime = disk_interface_.Stat(s.AsString(), &err);
- if (mtime == -1)
- Error("%s", err.c_str()); // Log and ignore Stat() errors.
- return mtime == 0;
- }
-
- int64_t start_time_millis_;
-};
-
-/// Subtools, accessible via "-t foo".
-struct Tool {
- /// Short name of the tool.
- const char* name;
-
- /// Description (shown in "-t list").
- const char* desc;
-
- /// When to run the tool.
- enum {
- /// Run after parsing the command-line flags and potentially changing
- /// the current working directory (as early as possible).
- RUN_AFTER_FLAGS,
-
- /// Run after loading build.ninja.
- RUN_AFTER_LOAD,
-
- /// Run after loading the build/deps logs.
- RUN_AFTER_LOGS,
- } when;
-
- /// Implementation of the tool.
- NinjaMain::ToolFunc func;
-};
-
-/// Print usage information.
-void Usage(const BuildConfig& config) {
- fprintf(stderr,
-"usage: ninja [options] [targets...]\n"
-"\n"
-"if targets are unspecified, builds the 'default' target (see manual).\n"
-"\n"
-"options:\n"
-" --version print ninja version (\"%s\")\n"
-" -v, --verbose show all command lines while building\n"
-" --quiet don't show progress status, just command output\n"
-"\n"
-" -C DIR change to DIR before doing anything else\n"
-" -f FILE specify input build file [default=build.ninja]\n"
-"\n"
-" -j N run N jobs in parallel (0 means infinity) [default=%d on this system]\n"
-" -k N keep going until N jobs fail (0 means infinity) [default=1]\n"
-" -l N do not start new jobs if the load average is greater than N\n"
-" -n dry run (don't run commands but act like they succeeded)\n"
-"\n"
-" -d MODE enable debugging (use '-d list' to list modes)\n"
-" -t TOOL run a subtool (use '-t list' to list subtools)\n"
-" terminates toplevel options; further flags are passed to the tool\n"
-" -w FLAG adjust warnings (use '-w list' to list warnings)\n",
- kNinjaVersion, config.parallelism);
-}
-
-/// Choose a default value for the -j (parallelism) flag.
-int GuessParallelism() {
- switch (int processors = GetProcessorCount()) {
- case 0:
- case 1:
- return 2;
- case 2:
- return 3;
- default:
- return processors + 2;
- }
-}
-
-/// Rebuild the build manifest, if necessary.
-/// If \arg silenty_dry_run is true, do not modify anything on disk.
-/// Returns true if the manifest was rebuilt.
-bool NinjaMain::RebuildManifest(std::string* err, bool silent_dry_run) {
- std::string path = config_->input_file;
- if (path.empty()) {
- *err = "empty path";
- return false;
- }
-
- uint64_t slash_bits; // Unused because this path is only used for lookup.
- CanonicalizePath(&path, &slash_bits);
- Node* node = state_.LookupNode(path);
- if (!node)
- return false;
-
- BuildConfig config = *config_;
- if (silent_dry_run) {
- config.verbosity = BuildConfig::QUIET;
- config.dry_run = true;
- }
-
- Builder builder(&state_, config, &build_log_, &deps_log_, &disk_interface_,
- status(), start_time_millis_);
-
- if (!builder.AddTarget(node, err))
- return false;
-
- if (builder.AlreadyUpToDate())
- return false; // Not an error, but we didn't rebuild.
-
- if (!builder.Build(err))
- return false;
-
- // The manifest was only rebuilt if it is now dirty (it may have been cleaned
- // by a restat).
- if (!node->dirty()) {
- // Reset the state to prevent problems like
- // https://github.com/ninja-build/ninja/issues/874
- state_.Reset();
- return false;
- }
-
- return true;
-}
-
-Node* NinjaMain::CollectTarget(const char* cpath, string* err) {
- string path = cpath;
- if (path.empty()) {
- *err = "empty path";
- return NULL;
- }
- uint64_t slash_bits;
- CanonicalizePath(&path, &slash_bits);
-
- // Special syntax: "foo.cc^" means "the first output of foo.cc".
- bool first_dependent = false;
- if (!path.empty() && path[path.size() - 1] == '^') {
- path.resize(path.size() - 1);
- first_dependent = true;
- }
-
- Node* node = state_.LookupNode(path);
- if (node) {
- if (first_dependent) {
- if (node->out_edges().empty()) {
- Node* rev_deps = deps_log_.GetFirstReverseDepsNode(node);
- if (!rev_deps) {
- *err = "'" + path + "' has no out edge";
- return NULL;
- }
- node = rev_deps;
- } else {
- Edge* edge = node->out_edges()[0];
- if (edge->outputs_.empty()) {
- edge->Dump();
- Fatal("edge has no outputs");
- }
- node = edge->outputs_[0];
- }
- }
- return node;
- } else {
- *err =
- "unknown target '" + Node::PathDecanonicalized(path, slash_bits) + "'";
- if (path == "clean") {
- *err += ", did you mean 'ninja -t clean'?";
- } else if (path == "help") {
- *err += ", did you mean 'ninja -h'?";
- } else {
- Node* suggestion = state_.SpellcheckNode(path);
- if (suggestion) {
- *err += ", did you mean '" + suggestion->path() + "'?";
- }
- }
- return NULL;
- }
-}
-
-bool NinjaMain::CollectTargetsFromArgs(int argc, char* argv[],
- vector<Node*>* targets, string* err) {
- if (argc == 0) {
- *targets = state_.DefaultNodes(err);
- return err->empty();
- }
-
- for (int i = 0; i < argc; ++i) {
- Node* node = CollectTarget(argv[i], err);
- if (node == NULL)
- return false;
- targets->push_back(node);
- }
- return true;
-}
-
-int NinjaMain::ToolGraph(const Options* options, int argc, char* argv[]) {
- vector<Node*> nodes;
- string err;
- if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
-
- GraphViz graph(&state_, &disk_interface_);
- graph.Start();
- for (vector<Node*>::const_iterator n = nodes.begin(); n != nodes.end(); ++n)
- graph.AddTarget(*n);
- graph.Finish();
-
- return 0;
-}
-
-int NinjaMain::ToolQuery(const Options* options, int argc, char* argv[]) {
- if (argc == 0) {
- Error("expected a target to query");
- return 1;
- }
-
- DyndepLoader dyndep_loader(&state_, &disk_interface_);
-
- for (int i = 0; i < argc; ++i) {
- string err;
- Node* node = CollectTarget(argv[i], &err);
- if (!node) {
- Error("%s", err.c_str());
- return 1;
- }
-
- printf("%s:\n", node->path().c_str());
- if (Edge* edge = node->in_edge()) {
- if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
- if (!dyndep_loader.LoadDyndeps(edge->dyndep_, &err)) {
- Warning("%s\n", err.c_str());
- }
- }
- printf(" input: %s\n", edge->rule_->name().c_str());
- for (int in = 0; in < (int)edge->inputs_.size(); in++) {
- const char* label = "";
- if (edge->is_implicit(in))
- label = "| ";
- else if (edge->is_order_only(in))
- label = "|| ";
- printf(" %s%s\n", label, edge->inputs_[in]->path().c_str());
- }
- if (!edge->validations_.empty()) {
- printf(" validations:\n");
- for (std::vector<Node*>::iterator validation = edge->validations_.begin();
- validation != edge->validations_.end(); ++validation) {
- printf(" %s\n", (*validation)->path().c_str());
- }
- }
- }
- printf(" outputs:\n");
- for (vector<Edge*>::const_iterator edge = node->out_edges().begin();
- edge != node->out_edges().end(); ++edge) {
- for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
- out != (*edge)->outputs_.end(); ++out) {
- printf(" %s\n", (*out)->path().c_str());
- }
- }
- const std::vector<Edge*> validation_edges = node->validation_out_edges();
- if (!validation_edges.empty()) {
- printf(" validation for:\n");
- for (std::vector<Edge*>::const_iterator edge = validation_edges.begin();
- edge != validation_edges.end(); ++edge) {
- for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
- out != (*edge)->outputs_.end(); ++out) {
- printf(" %s\n", (*out)->path().c_str());
- }
- }
- }
- }
- return 0;
-}
-
-#if defined(NINJA_HAVE_BROWSE)
-int NinjaMain::ToolBrowse(const Options* options, int argc, char* argv[]) {
- RunBrowsePython(&state_, ninja_command_, config_->input_file.c_str(), argc,
- argv);
- // If we get here, the browse failed.
- return 1;
-}
-#else
-int NinjaMain::ToolBrowse(const Options*, int, char**) {
- Fatal("browse tool not supported on this platform");
- return 1;
-}
-#endif
-
-#if defined(_WIN32)
-int NinjaMain::ToolMSVC(const Options* options, int argc, char* argv[]) {
- // Reset getopt: push one argument onto the front of argv, reset optind.
- argc++;
- argv--;
- optind = 0;
- return MSVCHelperMain(argc, argv);
-}
-#endif
-
-int ToolTargetsList(const vector<Node*>& nodes, int depth, int indent) {
- for (vector<Node*>::const_iterator n = nodes.begin();
- n != nodes.end();
- ++n) {
- for (int i = 0; i < indent; ++i)
- printf(" ");
- const char* target = (*n)->path().c_str();
- if ((*n)->in_edge()) {
- printf("%s: %s\n", target, (*n)->in_edge()->rule_->name().c_str());
- if (depth > 1 || depth <= 0)
- ToolTargetsList((*n)->in_edge()->inputs_, depth - 1, indent + 1);
- } else {
- printf("%s\n", target);
- }
- }
- return 0;
-}
-
-int ToolTargetsSourceList(State* state) {
- for (vector<Edge*>::iterator e = state->edges_.begin();
- e != state->edges_.end(); ++e) {
- for (vector<Node*>::iterator inps = (*e)->inputs_.begin();
- inps != (*e)->inputs_.end(); ++inps) {
- if (!(*inps)->in_edge())
- printf("%s\n", (*inps)->path().c_str());
- }
- }
- return 0;
-}
-
-int ToolTargetsList(State* state, const string& rule_name) {
- set<string> rules;
-
- // Gather the outputs.
- for (vector<Edge*>::iterator e = state->edges_.begin();
- e != state->edges_.end(); ++e) {
- if ((*e)->rule_->name() == rule_name) {
- for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
- out_node != (*e)->outputs_.end(); ++out_node) {
- rules.insert((*out_node)->path());
- }
- }
- }
-
- // Print them.
- for (set<string>::const_iterator i = rules.begin();
- i != rules.end(); ++i) {
- printf("%s\n", (*i).c_str());
- }
-
- return 0;
-}
-
-int ToolTargetsList(State* state) {
- for (vector<Edge*>::iterator e = state->edges_.begin();
- e != state->edges_.end(); ++e) {
- for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
- out_node != (*e)->outputs_.end(); ++out_node) {
- printf("%s: %s\n",
- (*out_node)->path().c_str(),
- (*e)->rule_->name().c_str());
- }
- }
- return 0;
-}
-
-int NinjaMain::ToolDeps(const Options* options, int argc, char** argv) {
- vector<Node*> nodes;
- if (argc == 0) {
- for (vector<Node*>::const_iterator ni = deps_log_.nodes().begin();
- ni != deps_log_.nodes().end(); ++ni) {
- if (DepsLog::IsDepsEntryLiveFor(*ni))
- nodes.push_back(*ni);
- }
- } else {
- string err;
- if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
- }
-
- RealDiskInterface disk_interface;
- for (vector<Node*>::iterator it = nodes.begin(), end = nodes.end();
- it != end; ++it) {
- DepsLog::Deps* deps = deps_log_.GetDeps(*it);
- if (!deps) {
- printf("%s: deps not found\n", (*it)->path().c_str());
- continue;
- }
-
- string err;
- TimeStamp mtime = disk_interface.Stat((*it)->path(), &err);
- if (mtime == -1)
- Error("%s", err.c_str()); // Log and ignore Stat() errors;
- printf("%s: #deps %d, deps mtime %" PRId64 " (%s)\n",
- (*it)->path().c_str(), deps->node_count, deps->mtime,
- (!mtime || mtime > deps->mtime ? "STALE":"VALID"));
- for (int i = 0; i < deps->node_count; ++i)
- printf(" %s\n", deps->nodes[i]->path().c_str());
- printf("\n");
- }
-
- return 0;
-}
-
-int NinjaMain::ToolMissingDeps(const Options* options, int argc, char** argv) {
- vector<Node*> nodes;
- string err;
- if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
- RealDiskInterface disk_interface;
- MissingDependencyPrinter printer;
- MissingDependencyScanner scanner(&printer, &deps_log_, &state_,
- &disk_interface);
- for (vector<Node*>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
- scanner.ProcessNode(*it);
- }
- scanner.PrintStats();
- if (scanner.HadMissingDeps())
- return 3;
- return 0;
-}
-
-int NinjaMain::ToolTargets(const Options* options, int argc, char* argv[]) {
- int depth = 1;
- if (argc >= 1) {
- string mode = argv[0];
- if (mode == "rule") {
- string rule;
- if (argc > 1)
- rule = argv[1];
- if (rule.empty())
- return ToolTargetsSourceList(&state_);
- else
- return ToolTargetsList(&state_, rule);
- } else if (mode == "depth") {
- if (argc > 1)
- depth = atoi(argv[1]);
- } else if (mode == "all") {
- return ToolTargetsList(&state_);
- } else {
- const char* suggestion =
- SpellcheckString(mode.c_str(), "rule", "depth", "all", NULL);
- if (suggestion) {
- Error("unknown target tool mode '%s', did you mean '%s'?",
- mode.c_str(), suggestion);
- } else {
- Error("unknown target tool mode '%s'", mode.c_str());
- }
- return 1;
- }
- }
-
- string err;
- vector<Node*> root_nodes = state_.RootNodes(&err);
- if (err.empty()) {
- return ToolTargetsList(root_nodes, depth, 0);
- } else {
- Error("%s", err.c_str());
- return 1;
- }
-}
-
-int NinjaMain::ToolRules(const Options* options, int argc, char* argv[]) {
- // Parse options.
-
- // The rules tool uses getopt, and expects argv[0] to contain the name of
- // the tool, i.e. "rules".
- argc++;
- argv--;
-
- bool print_description = false;
-
- optind = 1;
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("hd"))) != -1) {
- switch (opt) {
- case 'd':
- print_description = true;
- break;
- case 'h':
- default:
- printf("usage: ninja -t rules [options]\n"
- "\n"
- "options:\n"
- " -d also print the description of the rule\n"
- " -h print this message\n"
- );
- return 1;
- }
- }
- argv += optind;
- argc -= optind;
-
- // Print rules
-
- for (const auto& pair : state_.bindings().GetRules()) {
- printf("%s", pair.first.c_str());
- if (print_description) {
- const Rule* rule = pair.second.get();
- const EvalString* description = rule->GetBinding("description");
- if (description != NULL) {
- printf(": %s", description->Unparse().c_str());
- }
- }
- printf("\n");
- fflush(stdout);
- }
- return 0;
-}
-
-#ifdef _WIN32
-int NinjaMain::ToolWinCodePage(const Options* options, int argc, char* argv[]) {
- if (argc != 0) {
- printf("usage: ninja -t wincodepage\n");
- return 1;
- }
- printf("Build file encoding: %s\n", GetACP() == CP_UTF8? "UTF-8" : "ANSI");
- return 0;
-}
-#endif
-
-enum PrintCommandMode { PCM_Single, PCM_All };
-void PrintCommands(Edge* edge, EdgeSet* seen, PrintCommandMode mode) {
- if (!edge)
- return;
- if (!seen->insert(edge).second)
- return;
-
- if (mode == PCM_All) {
- for (vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in)
- PrintCommands((*in)->in_edge(), seen, mode);
- }
-
- if (!edge->is_phony())
- puts(edge->EvaluateCommand().c_str());
-}
-
-int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
- // The commands tool uses getopt, and expects argv[0] to contain the name of
- // the tool, i.e. "commands".
- ++argc;
- --argv;
-
- PrintCommandMode mode = PCM_All;
-
- optind = 1;
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("hs"))) != -1) {
- switch (opt) {
- case 's':
- mode = PCM_Single;
- break;
- case 'h':
- default:
- printf("usage: ninja -t commands [options] [targets]\n"
-"\n"
-"options:\n"
-" -s only print the final command to build [target], not the whole chain\n"
- );
- return 1;
- }
- }
- argv += optind;
- argc -= optind;
-
- vector<Node*> nodes;
- string err;
- if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
-
- EdgeSet seen;
- for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
- PrintCommands((*in)->in_edge(), &seen, mode);
-
- return 0;
-}
-
-void CollectInputs(Edge* edge, std::set<Edge*>* seen,
- std::vector<std::string>* result) {
- if (!edge)
- return;
- if (!seen->insert(edge).second)
- return;
-
- for (vector<Node*>::iterator in = edge->inputs_.begin();
- in != edge->inputs_.end(); ++in)
- CollectInputs((*in)->in_edge(), seen, result);
-
- if (!edge->is_phony()) {
- edge->CollectInputs(true, result);
- }
-}
-
-int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
- // The inputs tool uses getopt, and expects argv[0] to contain the name of
- // the tool, i.e. "inputs".
- argc++;
- argv--;
- optind = 1;
- int opt;
- const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
- { NULL, 0, NULL, 0 } };
- while ((opt = getopt_long(argc, argv, "h", kLongOptions, NULL)) != -1) {
- switch (opt) {
- case 'h':
- default:
- // clang-format off
- printf(
-"Usage '-t inputs [options] [targets]\n"
-"\n"
-"List all inputs used for a set of targets. Note that this includes\n"
-"explicit, implicit and order-only inputs, but not validation ones.\n\n"
-"Options:\n"
-" -h, --help Print this message.\n");
- // clang-format on
- return 1;
- }
- }
- argv += optind;
- argc -= optind;
-
- vector<Node*> nodes;
- string err;
- if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
- Error("%s", err.c_str());
- return 1;
- }
-
- std::set<Edge*> seen;
- std::vector<std::string> result;
- for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
- CollectInputs((*in)->in_edge(), &seen, &result);
-
- // Make output deterministic by sorting then removing duplicates.
- std::sort(result.begin(), result.end());
- result.erase(std::unique(result.begin(), result.end()), result.end());
-
- for (size_t n = 0; n < result.size(); ++n)
- puts(result[n].c_str());
-
- return 0;
-}
-
-int NinjaMain::ToolClean(const Options* options, int argc, char* argv[]) {
- // The clean tool uses getopt, and expects argv[0] to contain the name of
- // the tool, i.e. "clean".
- argc++;
- argv--;
-
- bool generator = false;
- bool clean_rules = false;
-
- optind = 1;
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("hgr"))) != -1) {
- switch (opt) {
- case 'g':
- generator = true;
- break;
- case 'r':
- clean_rules = true;
- break;
- case 'h':
- default:
- printf("usage: ninja -t clean [options] [targets]\n"
-"\n"
-"options:\n"
-" -g also clean files marked as ninja generator output\n"
-" -r interpret targets as a list of rules to clean instead\n"
- );
- return 1;
- }
- }
- argv += optind;
- argc -= optind;
-
- if (clean_rules && argc == 0) {
- Error("expected a rule to clean");
- return 1;
- }
-
- Cleaner cleaner(&state_, *config_, &disk_interface_);
- if (argc >= 1) {
- if (clean_rules)
- return cleaner.CleanRules(argc, argv);
- else
- return cleaner.CleanTargets(argc, argv);
- } else {
- return cleaner.CleanAll(generator);
- }
-}
-
-int NinjaMain::ToolCleanDead(const Options* options, int argc, char* argv[]) {
- Cleaner cleaner(&state_, *config_, &disk_interface_);
- return cleaner.CleanDead(build_log_.entries());
-}
-
-enum EvaluateCommandMode {
- ECM_NORMAL,
- ECM_EXPAND_RSPFILE
-};
-std::string EvaluateCommandWithRspfile(const Edge* edge,
- const EvaluateCommandMode mode) {
- string command = edge->EvaluateCommand();
- if (mode == ECM_NORMAL)
- return command;
-
- string rspfile = edge->GetUnescapedRspfile();
- if (rspfile.empty())
- return command;
-
- size_t index = command.find(rspfile);
- if (index == 0 || index == string::npos || command[index - 1] != '@')
- return command;
-
- string rspfile_content = edge->GetBinding("rspfile_content");
- size_t newline_index = 0;
- while ((newline_index = rspfile_content.find('\n', newline_index)) !=
- string::npos) {
- rspfile_content.replace(newline_index, 1, 1, ' ');
- ++newline_index;
- }
- command.replace(index - 1, rspfile.length() + 1, rspfile_content);
- return command;
-}
-
-void printCompdb(const char* const directory, const Edge* const edge,
- const EvaluateCommandMode eval_mode) {
- printf("\n {\n \"directory\": \"");
- PrintJSONString(directory);
- printf("\",\n \"command\": \"");
- PrintJSONString(EvaluateCommandWithRspfile(edge, eval_mode));
- printf("\",\n \"file\": \"");
- PrintJSONString(edge->inputs_[0]->path());
- printf("\",\n \"output\": \"");
- PrintJSONString(edge->outputs_[0]->path());
- printf("\"\n }");
-}
-
-int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
- char* argv[]) {
- // The compdb tool uses getopt, and expects argv[0] to contain the name of
- // the tool, i.e. "compdb".
- argc++;
- argv--;
-
- EvaluateCommandMode eval_mode = ECM_NORMAL;
-
- optind = 1;
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("hx"))) != -1) {
- switch(opt) {
- case 'x':
- eval_mode = ECM_EXPAND_RSPFILE;
- break;
-
- case 'h':
- default:
- printf(
- "usage: ninja -t compdb [options] [rules]\n"
- "\n"
- "options:\n"
- " -x expand @rspfile style response file invocations\n"
- );
- return 1;
- }
- }
- argv += optind;
- argc -= optind;
-
- bool first = true;
- vector<char> cwd;
- char* success = NULL;
-
- do {
- cwd.resize(cwd.size() + 1024);
- errno = 0;
- success = getcwd(&cwd[0], cwd.size());
- } while (!success && errno == ERANGE);
- if (!success) {
- Error("cannot determine working directory: %s", strerror(errno));
- return 1;
- }
-
- putchar('[');
- for (vector<Edge*>::iterator e = state_.edges_.begin();
- e != state_.edges_.end(); ++e) {
- if ((*e)->inputs_.empty())
- continue;
- if (argc == 0) {
- if (!first) {
- putchar(',');
- }
- printCompdb(&cwd[0], *e, eval_mode);
- first = false;
- } else {
- for (int i = 0; i != argc; ++i) {
- if ((*e)->rule_->name() == argv[i]) {
- if (!first) {
- putchar(',');
- }
- printCompdb(&cwd[0], *e, eval_mode);
- first = false;
- }
- }
- }
- }
-
- puts("\n]");
- return 0;
-}
-
-int NinjaMain::ToolRecompact(const Options* options, int argc, char* argv[]) {
- if (!EnsureBuildDirExists())
- return 1;
-
- if (!OpenBuildLog(/*recompact_only=*/true) ||
- !OpenDepsLog(/*recompact_only=*/true))
- 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 =
-" 13 ,3;2!2;\n8 ,;<11!;\n5 `'<10!(2`'2!\n11 ,6;, `\\. `\\9 .,c13$ec,.\n6 "
-",2;11!>; `. ,;!2> .e8$2\".2 \"?7$e.\n <:<8!'` 2.3,.2` ,3!' ;,(?7\";2!2'<"
-"; `?6$PF ,;,\n2 `'4!8;<!3'`2 3! ;,`'2`2'3!;4!`2.`!;2 3,2 .<!2'`).\n5 3`5"
-"'2`9 `!2 `4!><3;5! J2$b,`!>;2!:2!`,d?b`!>\n26 `'-;,(<9!> $F3 )3.:!.2 d\""
-"2 ) !>\n30 7`2'<3!- \"=-='5 .2 `2-=\",!>\n25 .ze9$er2 .,cd16$bc.'\n22 .e"
-"14$,26$.\n21 z45$c .\n20 J50$c\n20 14$P\"`?34$b\n20 14$ dbc `2\"?22$?7$c"
-"\n20 ?18$c.6 4\"8?4\" c8$P\n9 .2,.8 \"20$c.3 ._14 J9$\n .2,2c9$bec,.2 `?"
-"21$c.3`4%,3%,3 c8$P\"\n22$c2 2\"?21$bc2,.2` .2,c7$P2\",cb\n23$b bc,.2\"2"
-"?14$2F2\"5?2\",J5$P\" ,zd3$\n24$ ?$3?%3 `2\"2?12$bcucd3$P3\"2 2=7$\n23$P"
-"\" ,3;<5!>2;,. `4\"6?2\"2 ,9;, `\"?2$\n";
- int count = 0;
- for (const char* p = urtle; *p; p++) {
- if ('0' <= *p && *p <= '9') {
- count = count*10 + *p - '0';
- } else {
- for (int i = 0; i < max(count, 1); ++i)
- printf("%c", *p);
- count = 0;
- }
- }
- return 0;
-}
-
-int NinjaMain::ToolServer(const Options* options, int argc, char* argv[]) {
- if (argc == 0 || !strcmp(argv[0], "help")) {
- printf(
- "usage: ninja -t server <command>\n"
- "\n"
- "commands:\n"
- " help print this message\n"
- " status print server status\n"
- " stop stop server\n"
- " pid print server pid, or -1\n");
- return 1;
- }
- std::string work_dir = GetCurrentDir();
- const char* command = argv[0];
- if (!strcmp(command, "status")) {
- PersistentMode::Status status = PersistentMode::GetCurrentProcessStatus();
- if (status == PersistentMode::Disabled)
- printf("persistent mode disabled\n");
- else if (status == PersistentMode::IsClient) {
- bool running = PersistentMode::Client().IsServerRunning(work_dir);
- if (running)
- printf("server is running for %s\n", work_dir.c_str());
- else
- printf("no server found for %s\n", work_dir.c_str());
- } else {
- printf("this is a server process for %s\n", work_dir.c_str());
- }
- return 0;
- }
-
- if (!strcmp(command, "stop")) {
- std::string err;
- bool stopped = PersistentMode::Client().StopServer(work_dir, &err);
- if (stopped)
- printf("server stopped for %s\n", work_dir.c_str());
- else
- printf("no server found for %s\n", work_dir.c_str());
- return 0;
- }
-
- if (!strcmp(command, "pid")) {
- int server_pid = PersistentMode::Client().GetServerPidFor(work_dir);
- printf("%d\n", server_pid);
- return 0;
- }
-
- fprintf(stderr, "Invalid command %s, see `-t server help`.\n", command);
- return 1;
-}
-
-/// Find the function to execute for \a tool_name and return it via \a func.
-/// Returns a Tool, or NULL if Ninja should exit.
-const Tool* ChooseTool(const string& tool_name) {
- static const Tool kTools[] = {
- { "browse", "browse dependency graph in a web browser",
- Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
-#ifdef _WIN32
- { "msvc", "build helper for MSVC cl.exe (DEPRECATED)",
- Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
-#endif
- { "clean", "clean built files", Tool::RUN_AFTER_LOAD,
- &NinjaMain::ToolClean },
- { "commands", "list all commands required to rebuild given targets",
- Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
- { "inputs", "list all inputs required to rebuild given targets",
- Tool::RUN_AFTER_LOAD, &NinjaMain::ToolInputs },
- { "deps", "show dependencies stored in the deps log", Tool::RUN_AFTER_LOGS,
- &NinjaMain::ToolDeps },
- { "missingdeps", "check deps log dependencies on generated files",
- Tool::RUN_AFTER_LOGS, &NinjaMain::ToolMissingDeps },
- { "graph", "output graphviz dot file for targets", Tool::RUN_AFTER_LOAD,
- &NinjaMain::ToolGraph },
- { "query", "show inputs/outputs for a path", Tool::RUN_AFTER_LOGS,
- &NinjaMain::ToolQuery },
- { "targets", "list targets by their rule or depth in the DAG",
- Tool::RUN_AFTER_LOAD, &NinjaMain::ToolTargets },
- { "compdb", "dump JSON compilation database to stdout",
- 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 },
- { "server", "interact with persistent server", Tool::RUN_AFTER_FLAGS,
- &NinjaMain::ToolServer },
-#ifdef _WIN32
- { "wincodepage", "print the Windows code page used by ninja",
- Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolWinCodePage },
-#endif
- { NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
- };
-
- if (tool_name == "list") {
- printf("ninja subtools:\n");
- for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
- if (tool->desc)
- printf("%11s %s\n", tool->name, tool->desc);
- }
- return NULL;
- }
-
- for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
- if (tool->name == tool_name)
- return tool;
- }
-
- vector<const char*> words;
- for (const Tool* tool = &kTools[0]; tool->name; ++tool)
- words.push_back(tool->name);
- const char* suggestion = SpellcheckStringV(tool_name, words);
- if (suggestion) {
- Fatal("unknown tool '%s', did you mean '%s'?",
- tool_name.c_str(), suggestion);
- } else {
- Fatal("unknown tool '%s'", tool_name.c_str());
- }
- return NULL; // Not reached.
-}
-
-/// Enable a debugging mode. Returns false if Ninja should exit instead
-/// of continuing.
-bool DebugEnable(const string& name) {
- if (name == "list") {
- printf("debugging modes:\n"
-" stats print operation counts/timing info\n"
-" explain explain what caused a command to execute\n"
-" keepdepfile don't delete depfiles after they're read by ninja\n"
-" keeprsp don't delete @response files on success\n"
-#ifdef _WIN32
-" nostatcache don't batch stat() calls per directory and cache them\n"
-#endif
-"multiple modes can be enabled via -d FOO -d BAR\n");
- return false;
- } else if (name == "stats") {
- g_metrics = new Metrics;
- return true;
- } else if (name == "explain") {
- g_explaining = true;
- return true;
- } else if (name == "keepdepfile") {
- g_keep_depfile = true;
- return true;
- } else if (name == "keeprsp") {
- g_keep_rsp = true;
- return true;
- } else if (name == "nostatcache") {
- g_experimental_statcache = false;
- return true;
- } else {
- const char* suggestion =
- SpellcheckString(name.c_str(),
- "stats", "explain", "keepdepfile", "keeprsp",
- "nostatcache", NULL);
- if (suggestion) {
- Error("unknown debug setting '%s', did you mean '%s'?",
- name.c_str(), suggestion);
- } else {
- Error("unknown debug setting '%s'", name.c_str());
- }
- return false;
- }
-}
-
-/// 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"
-" phonycycle={err,warn} phony build statement references itself\n"
- );
- return false;
- } else if (name == "dupbuild=err") {
- options->dupe_edges_should_err = true;
- return true;
- } else if (name == "dupbuild=warn") {
- options->dupe_edges_should_err = false;
- return true;
- } else if (name == "phonycycle=err") {
- options->phony_cycle_should_err = true;
- return true;
- } else if (name == "phonycycle=warn") {
- options->phony_cycle_should_err = false;
- return true;
- } else if (name == "depfilemulti=err" ||
- name == "depfilemulti=warn") {
- Warning("deprecated warning 'depfilemulti'");
- return true;
- } else {
- const char* suggestion =
- SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
- "phonycycle=err", "phonycycle=warn", NULL);
- if (suggestion) {
- Error("unknown warning flag '%s', did you mean '%s'?",
- name.c_str(), suggestion);
- } else {
- Error("unknown warning flag '%s'", name.c_str());
- }
- return false;
- }
-}
-
-bool NinjaMain::OpenBuildLog(bool recompact_only) {
- 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 false;
- }
- if (!err.empty()) {
- // 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());
- return success;
- }
-
- if (!config_->dry_run) {
- if (!build_log_.OpenForWrite(log_path, *this, &err)) {
- Error("opening build log: %s", err.c_str());
- return false;
- }
- }
-
- return true;
-}
-
-/// Open the deps log: load it, then open for writing.
-/// @return false on error.
-bool NinjaMain::OpenDepsLog(bool recompact_only) {
- string path = ".ninja_deps";
- if (!build_dir_.empty())
- path = build_dir_ + "/" + path;
-
- string 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 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());
- return success;
- }
-
- if (!config_->dry_run) {
- if (!deps_log_.OpenForWrite(path, &err)) {
- Error("opening deps log: %s", err.c_str());
- return false;
- }
- }
-
- return true;
-}
-
-void NinjaMain::DumpMetrics() {
- g_metrics->Report();
-
- printf("\n");
- int count = (int)state_.paths_.size();
- int buckets = (int)state_.paths_.bucket_count();
- printf("path->node hash load %.2f (%d entries / %d buckets)\n",
- count / (double) buckets, count, buckets);
-
- printf("paths: %zu, edges: %zu\n", state_.paths_.size(),
- state_.edges_.size());
- printf("build_log_entries %zu, deps_log_paths %zu\n", build_log_.size(),
- deps_log_.size());
-}
-
-bool NinjaMain::EnsureBuildDirExists() {
- build_dir_ = state_.bindings().LookupVariable("builddir");
- if (!build_dir_.empty() && !config_->dry_run) {
- if (!disk_interface_.MakeDirs(build_dir_ + "/.") && errno != EEXIST) {
- Error("creating build directory %s: %s",
- build_dir_.c_str(), strerror(errno));
- return false;
- }
- }
- return true;
-}
-
-int NinjaMain::RunBeforeBuild(const Options& options, int argc, char** argv) {
- // Fully reset NinjaMain instance on each run. This gets rid of
- // the currently loaded manifest, if there is one, avoiding unexpected
- // errors like:
- //
- // ```
- // [0/1](1) Regenerating ninja files
- // ninja: error: build.ninja:3: duplicate rule 'gn'
- // rule gn
- // ^ near here
- // ```
- Reset();
-
- ManifestParserOptions parser_opts;
- if (options.dupe_edges_should_err) {
- parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
- }
- if (options.phony_cycle_should_err) {
- parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
- }
-
- ManifestParser parser(&state_, &path_recording_disk_interface_, parser_opts);
- std::string err;
- if (!parser.Load(config_->input_file.c_str(), &err)) {
- status_->Error("%s", err.c_str());
- return 1;
- }
-
- if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
- return (this->*options.tool->func)(&options, argc, argv);
-
- if (!EnsureBuildDirExists() || !OpenBuildLog() || !OpenDepsLog())
- return 1;
-
- if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
- return (this->*options.tool->func)(&options, argc, argv);
-
- return kBuildCanProceed;
-}
-
-int NinjaMain::RunBuild(int argc, char** argv) {
- string err;
- vector<Node*> targets;
- if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) {
- status_->Error("%s", err.c_str());
- return 1;
- }
-
- disk_interface_.AllowStatCache(g_experimental_statcache);
-
- Builder builder(&state_, *config_, &build_log_, &deps_log_, &disk_interface_,
- status(), start_time_millis_);
- {
- METRIC_RECORD("dependency scan");
- for (size_t i = 0; i < targets.size(); ++i) {
- if (!builder.AddTarget(targets[i], &err)) {
- if (!err.empty()) {
- status_->Error("%s", err.c_str());
- return 1;
- } else {
- // Added a target that is already up-to-date; not really
- // an error.
- }
- }
- }
-
- if (builder.AlreadyUpToDate()) {
- status_->Info("no work to do.");
- return 0;
- }
- }
-
- if (!builder.Build(&err)) {
- status_->Info("build stopped: %s.", err.c_str());
- if (err.find("interrupted by user") != string::npos) {
- return 2;
- }
- return 1;
- }
-
- return 0;
-}
-
-#ifdef _MSC_VER
-
-/// This handler processes fatal crashes that you can't catch
-/// Test example: C++ exception in a stack-unwind-block
-/// Real-world example: ninja launched a compiler to process a tricky
-/// C++ input file. The compiler got itself into a state where it
-/// generated 3 GB of output and caused ninja to crash.
-void TerminateHandler() {
- CreateWin32MiniDump(NULL);
- Fatal("terminate handler called");
-}
-
-/// On Windows, we want to prevent error dialogs in case of exceptions.
-/// This function handles the exception, and writes a minidump.
-int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
- Error("exception: 0x%X", code); // e.g. EXCEPTION_ACCESS_VIOLATION
- fflush(stderr);
- CreateWin32MiniDump(ep);
- return EXCEPTION_EXECUTE_HANDLER;
-}
-
-#endif // _MSC_VER
-
-class DeferGuessParallelism {
- public:
- bool needGuess;
- BuildConfig* config;
-
- DeferGuessParallelism(BuildConfig* config)
- : needGuess(true), config(config) {}
-
- void Refresh() {
- if (needGuess) {
- needGuess = false;
- config->parallelism = GuessParallelism();
- }
- }
- ~DeferGuessParallelism() { Refresh(); }
-};
-
-/// Parse argv for command-line options.
-/// Returns an exit code, or -1 if Ninja should continue.
-int ReadFlags(int* argc, char*** argv,
- Options* options, BuildConfig* config) {
- DeferGuessParallelism deferGuessParallelism(config);
-
- enum { OPT_VERSION = 1, OPT_QUIET = 2 };
- const option kLongOptions[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, OPT_VERSION },
- { "verbose", no_argument, NULL, 'v' },
- { "quiet", no_argument, NULL, OPT_QUIET },
- { NULL, 0, NULL, 0 }
- };
-
- // First grab values from environment variables.
- config->environment = EnvironmentBlock::CreateFromCurrentEnvironment();
-
- int opt;
- while (!options->tool &&
- (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions,
- NULL)) != -1) {
- switch (opt) {
- case 'd':
- if (!DebugEnable(optarg))
- return 1;
- break;
- case 'f':
- config->input_file = optarg;
- break;
- case 'j': {
- char* end;
- int value = strtol(optarg, &end, 10);
- if (*end != 0 || value < 0)
- Fatal("invalid -j parameter");
-
- // We want to run N jobs in parallel. For N = 0, INT_MAX
- // is close enough to infinite for most sane builds.
- config->parallelism = value > 0 ? value : INT_MAX;
- deferGuessParallelism.needGuess = false;
- break;
- }
- case 'k': {
- char* end;
- int value = strtol(optarg, &end, 10);
- if (*end != 0)
- Fatal("-k parameter not numeric; did you mean -k 0?");
-
- // We want to go until N jobs fail, which means we should allow
- // N failures and then stop. For N <= 0, INT_MAX is close enough
- // to infinite for most sane builds.
- config->failures_allowed = value > 0 ? value : INT_MAX;
- break;
- }
- case 'l': {
- char* end;
- double value = strtod(optarg, &end);
- if (end == optarg)
- Fatal("-l parameter not numeric: did you mean -l 0.0?");
- config->max_load_average = value;
- break;
- }
- case 'n':
- config->dry_run = true;
- break;
- case 't':
- options->tool = ChooseTool(optarg);
- if (!options->tool)
- return 0;
- break;
- case 'v':
- config->verbosity = BuildConfig::VERBOSE;
- break;
- case OPT_QUIET:
- config->verbosity = BuildConfig::NO_STATUS_UPDATE;
- break;
- case 'w':
- if (!WarningEnable(optarg, options))
- return 1;
- break;
- case 'C':
- options->working_dir = optarg;
- break;
- case OPT_VERSION:
- printf("%s\n", kNinjaVersion);
- return 0;
- case 'h':
- default:
- deferGuessParallelism.Refresh();
- Usage(*config);
- return 1;
- }
- }
- *argv += optind;
- *argc -= optind;
-
- return -1;
-}
-
-NORETURN void real_main(int argc, char** argv) {
- // Use exit() instead of return in this function to avoid potentially
- // expensive cleanup when destructing NinjaMain.
- BuildConfig config;
- Options options;
-
- // Save original arguments and current directory for persistent mode since
- // ReadFlags() will modify them.
- const auto original_args = RemoteArguments(argc, argv).args();
-
- setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
- const char* ninja_command = argv[0];
-
- int exit_code = ReadFlags(&argc, &argv, &options, &config);
- if (exit_code >= 0)
- exit(exit_code);
-
- // Always enable metrics when in persistent server mode,
- // since the METRIC_RECORD macro probes the value of g_metrics
- // once per execution, it must be initialized before any
- // use of that macro to ensure everything works correctly.
- PersistentMode::Status persistence =
- PersistentMode::GetCurrentProcessStatus();
-
- bool dump_metrics = !!g_metrics;
- if (persistence == PersistentMode::IsServer && !g_metrics)
- g_metrics = new Metrics();
-
- NinjaMain ninja(ninja_command, config);
-
- 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
- // subsequent commands.
- // Don't print this if a tool is being used, so that tool output
- // can be piped into a file without this string showing up.
- if (!options.tool && config.verbosity != BuildConfig::NO_STATUS_UPDATE)
- ninja.status()->Info("Entering directory `%s'", options.working_dir);
- if (chdir(options.working_dir) < 0) {
- Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno));
- }
- }
-
- if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) {
- // None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed
- // by other tools.
- exit((ninja.*options.tool->func)(&options, argc, argv));
- }
-
- // A callable object that runs all operations prior to the build itself,
- // which includes regenerating the build plan files if needed, loading the
- // build graph, and answering tool requests.
- //
- // It returns an exit status code if no more work is needed, or the
- // special value kBuildCanProceed to indicate that the build can proceed
- // afterwards.
- auto run_before_build = [&]() -> int {
- // Limit number of rebuilds, to prevent infinite loops.
- const int kCycleLimit = 100;
- for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
- int result = ninja.RunBeforeBuild(options, argc, argv);
- if (result != kBuildCanProceed)
- _exit(result);
-
- // Attempt to rebuild the manifest before building anything else
- std::string err;
- if (ninja.RebuildManifest(&err)) {
- // In dry_run mode the regeneration will succeed without changing the
- // manifest forever. Better to return immediately.
- if (config.dry_run)
- return 0;
- // Start the build over with the new manifest.
- continue;
- } else if (!err.empty()) {
- ninja.status()->Error("rebuilding '%s': %s",
- ninja.config_->input_file.c_str(), err.c_str());
- return 1;
- }
-
- return kBuildCanProceed;
- }
- ninja.status()->Error(
- "manifest '%s' still dirty after %d tries, perhaps system time is not "
- "set",
- ninja.config_->input_file.c_str(), kCycleLimit);
- return 1;
- };
-
- PersistentMode::BuildQuery build_query;
- build_query.config = config;
- build_query.debug_explaining = g_explaining;
- build_query.debug_keep_depfile = g_keep_depfile;
- build_query.debug_keep_rsp = g_keep_rsp;
- build_query.debug_experimental_statcache = g_experimental_statcache;
- build_query.dump_metrics = dump_metrics;
-
- if (options.tool)
- build_query.tool = options.tool->name;
-
- for (int n = 0; n < argc; ++n) {
- build_query.args.push_back(argv[n]);
- }
-
- // NOTE: For now, do not run tools in persistent client/server mode.
- bool use_persistent_mode =
- !options.tool && (persistence != PersistentMode::Disabled);
-
- // Note: since the working dir has already been changed here, do not
- // use options.working_dir, which is no longer valid if it is a relative
- // path. Compatibility will use the current working directory as the
- // build dir by default.
- PersistentMode::Compatibility compatibility;
- compatibility.SetInputFile(config.input_file);
- compatibility.SetFlagDupeEdgesShouldErr(options.dupe_edges_should_err);
- compatibility.SetFlagPhonyCycleShouldErr(options.phony_cycle_should_err);
-
- if (use_persistent_mode) {
- if (persistence == PersistentMode::IsServer) {
- // Persistent server mode. Load the build graph and all other expensive
- // things first. Exit immediately if there is a problem.
- PersistentMode::Server server(compatibility);
-
- std::string err;
- if (!server.StartLocalServer(&err)) {
- printf("Could not start local server, aborting: %s\n", err.c_str());
- exit(1);
- }
-
- printf(
- "\n***********************************************\n"
- "Server starting, loading build graph...\n");
- int result = ninja.RunBeforeBuild(options, argc, argv);
- if (result != kBuildCanProceed)
- exit(result);
-
- // Print load metrics
- printf("Manifest loading metrics\n");
- g_metrics->load_.Report();
-
- printf(
- "%zu nodes, %zu edges, %zu pools\n%zu build log entries, %zu deps "
- "log paths\n",
- ninja.state_.paths_.size(), ninja.state_.edges_.size(),
- ninja.state_.pools_.size(), ninja.build_log_.size(),
- ninja.deps_log_.size());
-
- // This callable returns true if the manifest(s) changed since the
- // last call. Which will force a server restart.
- auto restart_check = [&]() -> bool {
- // A fast check that returns true if any input .ninja file was
- // modified. Happens when the generator was called by the user
- // before Ninja.
- return ninja.path_recording_disk_interface_.CheckOutOfDate();
- };
-
- // This callable runs the part of the build and returns an exit status.
- // or the special value PersistentMode::kServerExit to indicate that
- // the server should exit.
- auto do_build = [&](const PersistentMode::BuildQuery& query) -> int {
- if (query.dump_metrics) {
- // Do not reset the load metrics here, only the build ones.
- g_metrics->build_.Reset();
- }
-
- ninja.PrepareBuild(query.config);
-
- if (!query.tool.empty()) {
- // Note that tools are always run on the client process because they
- // disable persistent mode support (see below). This currently happens
- // in the run_before_build lambda above.
- // TODO(digit): Implement tool in the server if it makes sense.
- fprintf(stderr,
- "UNIMPLEMENTED FEATURE: RUNNING TOOLS IN PERSISTENT MODE!\n");
- return 1;
- }
-
- std::string err;
- if (ninja.RebuildManifest(&err)) {
- // In dry_run mode the regeneration will succeed without changing the
- // manifest forever. Better to return with 0.
- if (query.config.dry_run)
- return 0;
- // The manifest has been regenerated, so the build graph in this
- // server is no longer valid. Tell it to exit so that the next
- // client invocation will start a new instance that will read
- // the new manifest.
- return PersistentMode::kServerExit;
- }
-
- RemoteArguments targets;
- targets.Reset(query.args);
-
- int result = ninja.RunBuild(targets.argc(), targets.argv());
-
- if (query.dump_metrics)
- ninja.DumpMetrics();
-
- return result;
- };
-
- printf("Server mode waiting for client requests\n");
-
- // Now serve client requests in a loop with |do_build| as the request
- // handler. This exits the process on completion.
- server.RunServerThenExit(restart_check, do_build);
- ninja.status()->Error("Server could not run or exit properly!\n");
- exit(1);
- } else {
- assert(persistence == PersistentMode::IsClient);
- }
- }
-
- // For regular and persistent client builds.
- // Limit number of rebuilds, to prevent infinite loops.
- const int kCycleLimit = 100;
- for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
- std::string err;
-
- //////////////////////////////////////////////////////////
- // Persistent client build.
-
- if (use_persistent_mode) {
- assert(persistence == PersistentMode::IsClient);
- PersistentMode::Client client;
- int result = 0;
- if (!client.RunQuery(compatibility, build_query, &result, &err)) {
- ninja.status()->Error(
- "Error contacting server, falling back to local build: %s\n",
- err.c_str());
- // Try again with a regular build.
- persistence = PersistentMode::Disabled;
- continue;
- }
-
- if (result != PersistentMode::kServerExit) {
- // Normal termination, exit with status code from server.
- _exit(result);
- }
- // The server exited after rebuilding the manifest, loop to
- // create a new server instance that loads the new manifest
- // then build with it.
- continue;
- }
-
- //////////////////////////////////////////////////////////
- // Normal (Non-persistent) builds.
-
- int result = ninja.RunBeforeBuild(options, argc, argv);
- if (result != kBuildCanProceed)
- _exit(result);
-
- // Attempt to rebuild the manifest before building anything else
- if (ninja.RebuildManifest(&err)) {
- // In dry_run mode the regeneration will succeed without changing the
- // manifest forever. Better to return immediately.
- if (config.dry_run)
- _exit(0);
- // Start the build over with the new manifest.
- continue;
- } else if (!err.empty()) {
- ninja.status()->Error("rebuilding '%s': %s", config.input_file.c_str(),
- err.c_str());
- exit(1);
- }
-
- result = ninja.RunBuild(argc, argv);
- if (g_metrics)
- ninja.DumpMetrics();
- _exit(result);
- }
-
- ninja.status()->Error(
- "manifest '%s' still dirty after %d tries, perhaps system time is not "
- "set",
- config.input_file.c_str(), kCycleLimit);
- exit(1);
-}
-
-} // anonymous namespace
-
-int main(int argc, char** argv) {
-#if defined(_MSC_VER)
- // Set a handler to catch crashes not caught by the __try..__except
- // block (e.g. an exception in a stack-unwind-block).
- std::set_terminate(TerminateHandler);
- __try {
- // Running inside __try ... __except suppresses any Windows error
- // dialogs for errors such as bad_alloc.
- real_main(argc, argv);
- }
- __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
- // Common error situations return exitCode=1. 2 was chosen to
- // indicate a more serious problem.
- return 2;
- }
-#else
- real_main(argc, argv);
-#endif
-}
diff --git a/src/ninja_test.cc b/src/ninja_test.cc
deleted file mode 100644
index 44ae03a..0000000
--- a/src/ninja_test.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2013 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 <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#ifdef _WIN32
-#include "getopt.h"
-#elif defined(_AIX)
-#include "getopt.h"
-#include <unistd.h>
-#else
-#include <getopt.h>
-#endif
-
-#include "test.h"
-#include "line_printer.h"
-
-using namespace std;
-
-struct RegisteredTest {
- testing::Test* (*factory)();
- const char *name;
- bool should_run;
-};
-// This can't be a vector because tests call RegisterTest from static
-// initializers and the order static initializers run it isn't specified. So
-// the vector constructor isn't guaranteed to run before all of the
-// RegisterTest() calls.
-static RegisteredTest tests[10000];
-testing::Test* g_current_test;
-static int ntests;
-static LinePrinter printer;
-
-void RegisterTest(testing::Test* (*factory)(), const char* name) {
- tests[ntests].factory = factory;
- tests[ntests++].name = name;
-}
-
-namespace {
-string StringPrintf(const char* format, ...) {
- const int N = 1024;
- char buf[N];
-
- va_list ap;
- va_start(ap, format);
- vsnprintf(buf, N, format, ap);
- va_end(ap);
-
- return buf;
-}
-
-void Usage() {
- fprintf(stderr,
-"usage: ninja_tests [options]\n"
-"\n"
-"options:\n"
-" --gtest_filter=POSITIVE_PATTERN[-NEGATIVE_PATTERN]\n"
-" Run tests whose names match the positive but not the negative pattern.\n"
-" '*' matches any substring. (gtest's ':', '?' are not implemented).\n");
-}
-
-bool PatternMatchesString(const char* pattern, const char* str) {
- switch (*pattern) {
- case '\0':
- case '-': return *str == '\0';
- case '*': return (*str != '\0' && PatternMatchesString(pattern, str + 1)) ||
- PatternMatchesString(pattern + 1, str);
- default: return *pattern == *str &&
- PatternMatchesString(pattern + 1, str + 1);
- }
-}
-
-bool TestMatchesFilter(const char* test, const char* filter) {
- // Split --gtest_filter at '-' into positive and negative filters.
- const char* const dash = strchr(filter, '-');
- const char* pos = dash == filter ? "*" : filter; //Treat '-test1' as '*-test1'
- const char* neg = dash ? dash + 1 : "";
- return PatternMatchesString(pos, test) && !PatternMatchesString(neg, test);
-}
-
-bool ReadFlags(int* argc, char*** argv, const char** test_filter) {
- enum { OPT_GTEST_FILTER = 1 };
- const option kLongOptions[] = {
- { "gtest_filter", required_argument, NULL, OPT_GTEST_FILTER },
- { NULL, 0, NULL, 0 }
- };
-
- int opt;
- while ((opt = getopt_long(*argc, *argv, "h", kLongOptions, NULL)) != -1) {
- switch (opt) {
- case OPT_GTEST_FILTER:
- if (strchr(optarg, '?') == NULL && strchr(optarg, ':') == NULL) {
- *test_filter = optarg;
- break;
- } // else fall through.
- default:
- Usage();
- return false;
- }
- }
- *argv += optind;
- *argc -= optind;
- return true;
-}
-
-} // namespace
-
-bool testing::Test::NullFailure(bool null_expected, const char* file, int line,
- const char* error, const char* value) {
- printer.PrintOnNewLine(StringPrintf(
- "*** Failure in %s:%d\n%s\nExpected: %s\nActual: %s\n", file, line,
- error, null_expected ? "nullptr" : "not nullptr", value));
- failed_ = true;
- return false;
-}
-
-bool testing::Test::BooleanFailure(bool expected, const char* file, int line,
- const char* error, const char* value) {
- printer.PrintOnNewLine(
- StringPrintf("*** Failure in %s:%d\n%s\nExpected: %s\nActual: %s\n",
- file, line, error, expected ? "true" : "false", value));
- failed_ = true;
- return false;
-}
-
-bool testing::Test::BinopFailure(const char* file, int line,
- const char* error, const char* first_value,
- const char* second_value) {
- printer.PrintOnNewLine(
- StringPrintf("*** Failure in %s:%d\n%s\nLeft: %s\nRight: %s\n", file, line, error, first_value, second_value));
- failed_ = true;
- return false;
-}
-
-int main(int argc, char **argv) {
- int tests_started = 0;
-
- const char* test_filter = "*";
- if (!ReadFlags(&argc, &argv, &test_filter))
- return 1;
-
- int nactivetests = 0;
- for (int i = 0; i < ntests; i++)
- if ((tests[i].should_run = TestMatchesFilter(tests[i].name, test_filter)))
- ++nactivetests;
-
- bool passed = true;
- for (int i = 0; i < ntests; i++) {
- if (!tests[i].should_run) continue;
-
- ++tests_started;
- testing::Test* test = tests[i].factory();
- printer.Print(
- StringPrintf("[%d/%d] %s", tests_started, nactivetests, tests[i].name),
- LinePrinter::ELIDE);
- test->SetUp();
- test->Run();
- test->TearDown();
- if (test->Failed())
- passed = false;
- delete test;
- }
-
- printer.PrintOnNewLine(passed ? "passed\n" : "failed\n");
- return passed ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/src/parser.cc b/src/parser.cc
deleted file mode 100644
index 96c90f3..0000000
--- a/src/parser.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 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 "parser.h"
-
-#include "disk_interface.h"
-#include "metrics.h"
-
-using namespace std;
-
-bool Parser::Load(const string& filename, string* err, Lexer* parent) {
- // If |parent| is not NULL, metrics collection has been started by a parent
- // Parser::Load() in our call stack. Do not start a new one here to avoid
- // over-counting parsing times.
- METRIC_RECORD_LOAD_IF(".ninja parse", parent == NULL);
- string contents;
- string read_err;
- if (file_reader_->ReadFile(filename, &contents, &read_err) !=
- FileReader::Okay) {
- *err = "loading '" + filename + "': " + read_err;
- if (parent)
- parent->Error(string(*err), err);
- return false;
- }
-
- return Parse(filename, contents, err);
-}
-
-bool Parser::ExpectToken(Lexer::Token expected, string* err) {
- Lexer::Token token = lexer_.ReadToken();
- if (token != expected) {
- string message = string("expected ") + Lexer::TokenName(expected);
- message += string(", got ") + Lexer::TokenName(token);
- message += Lexer::TokenErrorHint(expected);
- return lexer_.Error(message, err);
- }
- return true;
-}
diff --git a/src/parser.h b/src/parser.h
deleted file mode 100644
index 011fad8..0000000
--- a/src/parser.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 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_PARSER_H_
-#define NINJA_PARSER_H_
-
-#include <string>
-
-#include "lexer.h"
-
-struct FileReader;
-struct State;
-
-/// Base class for parsers.
-struct Parser {
- Parser(State* state, FileReader* file_reader)
- : state_(state), file_reader_(file_reader) {}
-
- /// Load and parse a file.
- 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, std::string* err);
-
- State* state_;
- FileReader* file_reader_;
- Lexer lexer_;
-
-private:
- /// Parse a file, given its contents as a string.
- virtual bool Parse(const std::string& filename, const std::string& input,
- std::string* err) = 0;
-};
-
-#endif // NINJA_PARSER_H_
diff --git a/src/persistent_mode.cc b/src/persistent_mode.cc
deleted file mode 100644
index 16ef3bc..0000000
--- a/src/persistent_mode.cc
+++ /dev/null
@@ -1,593 +0,0 @@
-// Copyright 2023 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 "persistent_mode.h"
-
-#include <assert.h>
-#include <inttypes.h>
-#include <string.h>
-
-#include "debug_flags.h"
-#include "interrupt_handling.h"
-#include "ipc_handle.h"
-#include "ipc_utils.h"
-#include "metrics.h"
-#include "process_utils.h"
-#include "stdio_redirection.h"
-#include "util.h"
-#include "version.h"
-#ifndef _WIN32
-#include <sys/stat.h>
-#include <unistd.h>
-#endif
-
-#define DEBUG 0
-
-#define SERVER_LOG(...) \
- do { \
- fprintf(stderr, "NINJA_SERVER_LOG: "); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } while (0)
-
-#if DEBUG
-#define CLIENT_LOG(...) \
- do { \
- fprintf(stderr, "NINJA_CLIENT_LOG: "); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } while (0)
-#else
-#define CLIENT_LOG(...) (void)0
-#endif
-
-namespace {
-
-// Print error message, then return false
-bool PrintError(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- fputs("ERROR: ", stderr);
- vfprintf(stderr, fmt, args);
- fputc('\n', stderr);
- va_end(args);
- return false;
-}
-
-extern "C" char** environ;
-
-// Return the name of the IPC service for a given current build
-// directory. This allows several servers to co-exist on the same
-// machine, if they are invoked / used from different build
-// directories.
-std::string GetServiceName(const std::string& build_dir) {
- std::string real_dir = build_dir.empty() ? GetCurrentDir() : build_dir;
- return StringFormat("ninja-server-%08zx", std::hash<std::string>()(real_dir));
-}
-
-// Name of the environment variable used to control the feature at runtime.
-const char kPersistentModeEnv[] = "NINJA_PERSISTENT_MODE";
-
-// Name of the environment variable used to control the server timeout.
-const char kPersistentTimeoutSecondsEnv[] = "NINJA_PERSISTENT_TIMEOUT_SECONDS";
-
-} // namespace
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-/////
-///// C O M P A T I B I L I T Y
-/////
-/////
-
-PersistentMode::Compatibility::Compatibility()
- : ninja_version_(GetDefaultNinjaVersion()), build_dir_(GetCurrentDir()),
- input_file_("build.ninja") {}
-
-PersistentMode::Compatibility& PersistentMode::Compatibility::SetInputFile(
- const std::string& input_file) {
- input_file_ = input_file;
- return *this;
-}
-
-// static
-std::string PersistentMode::Compatibility::GetDefaultNinjaVersion() {
- // Ignore the error returned by GetFileTimestamp() since the executable
- // is known to exist.
- std::string err;
- return StringFormat("%s-%" PRId64, kNinjaVersion,
- ::GetFileTimestamp(GetCurrentExecutable(), &err));
-}
-
-PersistentMode::Compatibility&
-PersistentMode::Compatibility::SetNinjaVersionForTest(
- const std::string& ninja_version) {
- ninja_version_ = ninja_version;
- return *this;
-}
-
-PersistentMode::Compatibility& PersistentMode::Compatibility::SetBuildDir(
- const std::string& build_dir) {
- build_dir_ = build_dir;
- return *this;
-}
-
-PersistentMode::Compatibility&
-PersistentMode::Compatibility::SetFlagDupeEdgesShouldErr(bool enabled) {
- dupe_edges_should_err_ = enabled;
- return *this;
-}
-
-PersistentMode::Compatibility&
-PersistentMode::Compatibility::SetFlagPhonyCycleShouldErr(bool enabled) {
- phony_cycle_should_err_ = enabled;
- return *this;
-}
-
-bool PersistentMode::Compatibility::CheckBuildDir(std::string* err) const {
- if (build_dir_.empty())
- return true;
-
- if (build_dir_[0] != '/' && build_dir_[0] != '\\') {
- *err = StringFormat("build directory path is not absolute: %s",
- build_dir_.c_str());
- return false;
- }
-
- // TODO(digit): What about Win32 drive letters?
- return true;
-}
-
-// static
-PersistentMode::Compatibility PersistentMode::Compatibility::FromEncodedString(
- const std::string& str, std::string* error) {
- error->clear();
- WireDecoder decoder(str);
- Compatibility result = {};
- decoder.Read(result.ninja_version_);
- decoder.Read(result.build_dir_);
- decoder.Read(result.input_file_);
- decoder.Read(result.dupe_edges_should_err_);
- decoder.Read(result.phony_cycle_should_err_);
-
- if (decoder.has_error()) {
- *error = "Truncated PersistentMode::Compatibility encoded string";
- result = {};
- }
- return result;
-}
-
-std::string PersistentMode::Compatibility::ToEncodedString() const {
- WireEncoder encoder;
- encoder.Write(ninja_version_);
- encoder.Write(build_dir_);
- encoder.Write(input_file_);
- encoder.Write(dupe_edges_should_err_);
- encoder.Write(phony_cycle_should_err_);
- return encoder.TakeResult();
-}
-
-std::string PersistentMode::Compatibility::ToString() const {
- return StringFormat(
- "build_dir=%s input_file=%s dupe_edges_should_err=%s "
- "phony_cycle_shoud_err=%s ninja_version=%s",
- build_dir_.c_str(), input_file_.c_str(),
- dupe_edges_should_err_ ? "true" : "false",
- phony_cycle_should_err_ ? "true" : "false", ninja_version_.c_str());
-}
-
-bool PersistentMode::Compatibility::IsCompatibleWith(
- const PersistentMode::Compatibility& other, std::string* reason) const {
- if (ninja_version_ != other.ninja_version_) {
- *reason =
- StringFormat("Ninja version mismatch, expected [%s] vs [%s]",
- ninja_version_.c_str(), other.ninja_version_.c_str());
- return false;
- }
- if (build_dir_ != other.build_dir_) {
- *reason = StringFormat("Working dir mismatch, expected [%s] vs [%s]",
- build_dir_.c_str(), other.build_dir_.c_str());
- return false;
- }
- if (input_file_ != other.input_file_) {
- *reason = StringFormat("Input file mismatch, expected [%s] vs [%s]",
- input_file_.c_str(), other.input_file_.c_str());
- return false;
- }
- if (dupe_edges_should_err_ != other.dupe_edges_should_err_) {
- *reason =
- StringFormat("Flag dupe_edges_should_err mismatch, expected %s vs %s",
- dupe_edges_should_err_ ? "true" : "false",
- other.dupe_edges_should_err_ ? "true" : "false");
- return false;
- }
- if (phony_cycle_should_err_ != other.phony_cycle_should_err_) {
- *reason =
- StringFormat("Flag phony_cycle_should_err mismatch, expected %s vs %s",
- phony_cycle_should_err_ ? "true" : "false",
- other.phony_cycle_should_err_ ? "true" : "false");
- return false;
- }
- return true;
-}
-
-std::vector<std::string> PersistentMode::Compatibility::ToServerArgs(
- const std::string& server_executable) const {
- std::vector<std::string> result;
- result.push_back(server_executable);
- result.push_back("-C");
- result.push_back(build_dir_);
-
- if (input_file_ != "build.ninja") {
- result.push_back("-f");
- result.push_back(input_file_);
- }
- if (dupe_edges_should_err_) {
- result.push_back("-wdupbuild=err");
- } else {
- result.push_back("-wdupbuild=warn");
- }
- if (phony_cycle_should_err_) {
- result.push_back("-wphonycycle=err");
- } else {
- result.push_back("-wphonycycle=warn");
- }
- return result;
-}
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-/////
-///// B U I L D Q U E R Y
-/////
-/////
-
-// static
-PersistentMode::BuildQuery PersistentMode::BuildQuery::FromEncodedString(
- const std::string& str, std::string* error) {
- BuildQuery result;
- std::string encoded;
- WireDecoder decoder(str);
-
- // Read BuildConfig.
- decoder.Read(encoded);
- result.config = BuildConfig::FromEncodedString(encoded, error);
- if (!error->empty())
- return {};
-
- decoder.Read(result.debug_explaining);
- decoder.Read(result.debug_keep_depfile);
- decoder.Read(result.debug_keep_rsp);
- decoder.Read(result.debug_experimental_statcache);
- decoder.Read(result.dump_metrics);
- decoder.Read(result.tool);
- size_t num_args = 0;
- decoder.Read(num_args);
- result.args.resize(num_args);
- for (size_t n = 0; n < num_args; ++n)
- decoder.Read(result.args[n]);
-
- if (decoder.has_error()) {
- *error = "Truncated BuildQuery encoded string";
- result = {};
- }
-
- return result;
-}
-
-std::string PersistentMode::BuildQuery::ToEncodedString() const {
- WireEncoder encoder;
- encoder.Write(config.ToEncodedString());
- encoder.Write(debug_explaining);
- encoder.Write(debug_keep_depfile);
- encoder.Write(debug_keep_rsp);
- encoder.Write(debug_experimental_statcache);
- encoder.Write(dump_metrics);
- encoder.Write(tool);
- encoder.Write(args.size());
- for (const auto& arg : args)
- encoder.Write(arg);
-
- return encoder.TakeResult();
-}
-
-std::string PersistentMode::BuildQuery::ToString() const {
- std::string result = config.ToString();
- StringAppendFormat(result,
- " explain=%s keep_depfile=%s keep_rsp=%s "
- "experimental_statcache=%s dump_metrics=%s",
- debug_explaining ? "true" : "false",
- debug_keep_depfile ? "true" : "false",
- debug_keep_rsp ? "true" : "false",
- debug_experimental_statcache ? "true" : "false",
- dump_metrics ? "true" : "false");
-
- if (!tool.empty())
- StringAppendFormat(result, " tool=%s", tool.c_str());
-
- for (const auto& arg : args) {
- result += " ";
- result += arg;
- }
- return result;
-}
-
-void PersistentMode::BuildQuery::WriteGlobalVariables() const {
- g_explaining = debug_explaining;
- g_keep_depfile = debug_keep_depfile;
- g_keep_rsp = debug_keep_rsp;
- g_experimental_statcache = debug_experimental_statcache;
-}
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-/////
-///// P E R S I S T E N T M O D E
-/////
-/////
-
-// static
-PersistentMode::Status PersistentMode::GetCurrentProcessStatus() {
- const char* env = getenv(kPersistentModeEnv);
- std::string mode(env ? env : "0");
- if (mode == "0" || mode == "off")
- return PersistentMode::Disabled;
-
- if (mode == "1" || mode == "on" || mode == "client")
- return PersistentMode::IsClient;
-
- if (mode == "server") // Used internally when spawning the server.
- return PersistentMode::IsServer;
-
- fprintf(stderr,
- "WARNING: Unknown %s value '%s', must be one of: 0, 1, on, off, "
- "client, server\n",
- kPersistentModeEnv, mode.c_str());
- return PersistentMode::Disabled;
-}
-
-PersistentMode::Client::Client() : server_executable_(GetCurrentExecutable()) {}
-
-void PersistentMode::Client::SetServerExecutable(
- const std::string& server_executable) {
- server_executable_ = server_executable;
-}
-
-// static
-PersistentService::Client PersistentMode::Client::GetService(
- const std::string& build_dir) {
- CLIENT_LOG("GetService(%s)", build_dir.c_str());
- return PersistentService::Client(GetServiceName(build_dir));
-}
-
-bool PersistentMode::Client::IsServerRunning(const std::string& build_dir) {
- return GetService(build_dir).HasServer();
-}
-
-int PersistentMode::Client::GetServerPidFor(const std::string& build_dir) {
- return GetService(build_dir).GetServerPid();
-}
-
-bool PersistentMode::Client::StopServer(const std::string& build_dir,
- std::string* err) {
- return GetService(build_dir).StopServer(err);
-}
-
-bool PersistentMode::Client::RunQuery(const Compatibility& compatibility,
- const BuildQuery& query,
- int* exit_code_ptr, std::string* error) {
- // Return an error early if the build directory is not valid.
- if (!compatibility.CheckBuildDir(error))
- return false;
-
- // Ensure the persistent mode status is set to 'server' in spawned servers.
- PersistentService::Config server_config =
- PersistentService::Config(compatibility.ToServerArgs(server_executable_))
- .SetWorkingDir(compatibility.build_dir())
- .SetVersionInfo(compatibility.ToEncodedString())
- .AddEnvVariable(kPersistentModeEnv, "server");
- // Set log file path from env. This is a debugging aid.
- {
- const char* env = getenv("NINJA_PERSISTENT_LOG_FILE");
- if (env && env[0]) {
- server_config.SetLogFile(std::string(env));
- }
- }
-
- // Ensure that the persistent timeout value from the client environment
- // is passed to new server instances.
- {
- const char* env = getenv(kPersistentTimeoutSecondsEnv);
- if (env)
- server_config.AddEnvVariable(kPersistentTimeoutSecondsEnv, env);
- }
-
- CLIENT_LOG("Connecting to server.");
- PersistentService::Client client = GetService(compatibility.build_dir());
- IpcHandle connection = client.Connect(server_config, error);
- if (!connection) {
- CLIENT_LOG("Could not connect to server: %s", error->c_str());
- return false;
- }
-
- // Send the query now.
- CLIENT_LOG("Sending query to server.");
- if (!RemoteWrite(query.ToEncodedString(), connection, error)) {
- *error = "Could not send build query: " + *error;
- return false;
- }
-
- // Receive the server PID, to redirect signals to it.
-#ifdef _WIN32
- DWORD server_pid = 0;
-#else // !_WIN32
- pid_t server_pid = -1;
-#endif // !_WIN32
- if (!RemoteRead(server_pid, connection, error)) {
- *error = "Could not receive server pid: " + *error;
- return false;
- }
-
- CLIENT_LOG("Received remote server pid: %d\n", server_pid);
-
- StdioRedirector stdio_redirect(connection);
- if (!stdio_redirect.SendStandardDescriptors(error))
- return false;
-
- InterruptForwarder interrupt_forwarder(server_pid);
-
- // Wait for the corresponding exit code.
- CLIENT_LOG("Waiting for query exit code from server.");
- int exit_code = -1;
- if (!RemoteRead(exit_code, connection, error)) {
- *error = "Could not receive build request exit code: " + *error;
- return false;
- }
- CLIENT_LOG("Query ended with exit code %d", exit_code);
- *exit_code_ptr = exit_code;
- return true;
-}
-
-PersistentMode::Server::Server(const Compatibility& compatibility)
- : compatibility_(compatibility),
- server_(GetServiceName(compatibility_.build_dir())) {
- SERVER_LOG("Server for %s", compatibility_.build_dir().c_str());
-
- // Compute the server connection timeout, default value is 5 minutes
- // and can be overriden with kPersistentTimeoutSecondsEnv in the
- // environment.
- int64_t connection_timeout_ms = 5 * 60 * 1000;
- const char* env = getenv(kPersistentTimeoutSecondsEnv);
- if (env) {
- int64_t timeout_secs = static_cast<int64_t>(atoll(env));
- if (timeout_secs < 0) {
- SERVER_LOG(
- "Ignoring invalid timeout value (%s): must be strictly positive!",
- env);
- } else {
- connection_timeout_ms = timeout_secs * 1000;
- }
- }
- server_.SetConnectionTimeoutMs(connection_timeout_ms);
-}
-
-bool PersistentMode::Server::StartLocalServer(std::string* error) {
- // Return an error early if the build directory is not valid.
- if (!compatibility_.CheckBuildDir(error))
- return false;
-
- return server_.BindService(error);
-}
-
-void PersistentMode::Server::RunServerThenExit(
- RestartChecker ninja_restart_check, BuildQueryHandler query_handler) {
- // PersistentService::Server::VersionCheckHandler used to verify compatibility
- // and invoke the restart check callback before each query.
- auto version_check_handler =
- [this, &ninja_restart_check](const std::string& version) -> std::string {
- std::string error;
- auto client_compatibility =
- Compatibility::FromEncodedString(version, &error);
- if (!error.empty())
- return error;
- std::string reason;
- if (!compatibility_.IsCompatibleWith(client_compatibility, &reason))
- return StringFormat("Incompatible build plan: %s", reason.c_str());
-
- if (ninja_restart_check())
- return "Build manifest files updated!";
-
- return {};
- };
-
- // A Persistent::Server::RequestHandler instance to run queries on the
- // server.
- auto request_handler = [this, &query_handler](IpcHandle connection) -> bool {
- return RunQueryOnServer(std::move(connection), query_handler);
- };
-
- // Ensure that NINJA_PERSISTENT_MODE is disabled by default when
- // running queries, since Ninja commands can themselves invoke Ninja
- // (e.g. to perform sub-builds in other directories).
- //
- // Persistent mode for these sub-builds is disabled by default, but can
- // be enabled by the user by adding NINJA_PERSISTENT_MODE to the list in
- // NINJA_PERSISTENT_PASS_VARIABLES.
- ScopedEnvironmentVariable no_persistent_mode(kPersistentModeEnv, "0");
-
- server_.RunServerThenExit(version_check_handler, request_handler);
-}
-
-bool PersistentMode::Server::RunQueryOnServer(
- IpcHandle connection, const BuildQueryHandler& query_handler) {
- int64_t query_start_ms = AsyncLoop::NowMs();
- printf("Starting client request\n");
- std::string error;
- SERVER_LOG("New client request from handle %s", connection.display().c_str());
- // Receive build query.
- std::string encoded_query;
- if (!RemoteRead(encoded_query, connection, &error)) {
- return PrintError("Could not receive build query: %s", error.c_str());
- }
-
- auto query =
- PersistentMode::BuildQuery::FromEncodedString(encoded_query, &error);
- if (!error.empty())
- return PrintError("Could not receive build query: %s\n", error.c_str());
-
- query.WriteGlobalVariables();
-
- // Send our pid to allow the client to redirect signals to us.
-#ifdef _WIN32
- DWORD pid = GetCurrentProcessId();
-#else // !_WIN32
- pid_t pid = getpid();
-#endif // !_WIN32
- if (!RemoteWrite(pid, connection, &error))
- return PrintError("Could not send server pid: %s", error.c_str());
-
- // Temporarily redirect stdin/stdout/stderr from client-provided handles.
- int exit_code;
- {
- StdioRedirector stdio_redirect(connection);
- if (!stdio_redirect.ReceiveStandardDescriptors(&error))
- return PrintError("Could not receive standard descriptors: %s",
- error.c_str());
-
- exit_code = query_handler(query);
- }
-
- if (!RemoteWrite(exit_code, connection, &error)) {
- return PrintError("Could not send exit_code=%d back to client: %s",
- error.c_str());
- }
-
- // Keep server running until next client request.
- SERVER_LOG("Request completed with exit_code=%d", exit_code);
-
- // Print statistics about the request.
- int64_t query_time_ms = AsyncLoop::NowMs() - query_start_ms;
- printf("Request took %s to complete\n",
- StringFormatDurationMs(query_time_ms).c_str());
-
- if (exit_code == kServerExit) {
- printf("Server exiting after query.");
- return false;
- }
-
- return true;
-}
-
-// Avoid compiler warnings in debug mode.
-constexpr int PersistentMode::kServerExit;
diff --git a/src/persistent_mode.h b/src/persistent_mode.h
deleted file mode 100644
index e55a075..0000000
--- a/src/persistent_mode.h
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright 2023 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_PERSISTENT_MODE_H_
-#define NINJA_PERSISTENT_MODE_H_
-
-#include <functional>
-#include <string>
-#include <vector>
-
-#include "build_config.h"
-#include "ipc_utils.h"
-#include "persistent_service.h"
-
-/// Implementation for Ninja's persistent mode.
-///
-/// This class wraps Ninja's persistent mode implementation on top of the
-/// generic PersistentService class. In particular:
-///
-/// - Provides static methods to determine the role of the current process
-/// (based on the NNINJA_PERSISTENT_MODE environment variable).
-///
-/// - Provides a way to check that the client and the server correspond to
-/// the same build plan (see Compatibility).
-///
-/// - Provides client methods to check the status of the server, or stop it
-/// (to implement the `-t server` tool commands).
-///
-/// - Provides a client method to run a query on the server, automatically
-/// starting a new instance if none is available, if the available one
-/// is not compatible with the current build plan, or if any of the input
-/// .ninja manifest files has been updated.
-///
-/// - Provides a way to start the server early, and to process Ninja build
-/// or tool requests on the server.
-///
-/// Example Usage:
-///
-/// // build plan configuration data used to match client/server
-/// // compatibility.
-/// auto compatibility = PersistentMode::Compatibility();
-/// compatibility.SetBuildDir(<absolute_build_dir>);
-///
-/// // An object describing the current Ninja build query or tool
-/// // invocation to perform.
-/// PersistentMode::BuildQuery query;
-/// query.config = build_config;
-/// query.args = RemoteArguments(argc, argv).args();
-/// ...
-///
-/// // A callable used to run the query, either on the server, or in the
-/// // client if launching a new server was not possible.
-/// auto handle_query = [](const PersistentMode::BuildQuery& query) -> int {
-/// ... // perform build or tool query.
-/// return 0;
-/// };
-///
-/// auto persistent_status = PersistentMode::GetProcessStatus();
-/// is (status == PersistentMode::IsServer) {
-/// PersistentMode::Server server(compatibility);
-///
-/// if (!server.StartLocalServer(&err))
-/// Fatal("Could not start server: %s", err.c_str());
-///
-/// ... load build plan
-///
-/// // A callable used by the server before running each
-/// // query to verify if the build plan changed (e.g. an
-/// // input build.ninja file was updated on the filesystem).
-/// auto restart_check = []() -> bool {
-/// return false;
-/// };
-///
-/// // Run the server loop till the end.
-/// server.RunServerThenExit(std::move(restart_check),
-/// std::move(handle_query));
-/// }
-///
-/// if (persistent_status == PersistentMode::Client) {
-/// PersistentMode::Client client;
-///
-/// // Run the query, this takes care of redirecting stdio
-/// // and ensuring Ctrl-C will be handled by the server
-/// // properly (i.e. to stop current query, and not abort
-/// // the server).
-/// int exit_code = -1;
-/// if (!client.RunQuery(compatibility, query, &exit_code, &error)) {
-/// Fatal("Could not run Ninja query: %s", error.c_str());
-/// }
-///
-/// // query was run on the server.
-/// exit(exit_code);
-/// }
-///
-/// assert(persistent_status == PersistentMode::Disabled) {
-/// ... load build plan
-/// ... do a local build as usual.
-/// }
-///
-struct PersistentMode {
- /// Status of persistent mode for the current process
- enum Status {
- Disabled = 0,
- IsClient,
- IsServer,
- };
-
- /// Return the status of the current process.
- static Status GetCurrentProcessStatus();
-
- /// The configuration information that each persistent server must compare
- /// with each client query to verify that they are compatible, i.e. correspond
- /// to the same Ninja build plan. Note that some flags change the way
- /// manifests are loaded, and thus introduce incompatibilities when they
- /// are different.
- struct Compatibility {
- /// Create new instance from command-line options.
- ///
- /// |input_file| is an optional pointer to the top-level Ninja manifest
- /// file name. The nullptr value defaults to "build.ninja".
- ///
- /// |build_dir| is an optional pointer to the build directory, the nullptr
- /// value defaults to the current working directory.
- ///
- /// |dupe_edges_should_err| corresponds to the `-w dupbuild=err` option,
- /// and |phony_cycle_should_err| corresponds to the `-w phonycycle=err` one,
- /// and both change the way manifest files are loaded.
- static Compatibility FromOptions(const char* input_file,
- const char* build_dir,
- bool dupe_edges_should_err,
- bool phony_cycle_should_err);
-
- Compatibility();
-
- /// Override the ninja_version() string. Only use this for tests.
- Compatibility& SetNinjaVersionForTest(const std::string& new_version);
-
- /// Change the input file, default value if "build.ninja"
- Compatibility& SetInputFile(const std::string& input_file);
-
- /// Change the build directory, and empty value matches the current working
- /// directory. WARNING: It is critically important for the client and server
- /// to use the same value consistenly. Outside of tests, ensure that a
- /// non-empty value is always an absolute path!
- Compatibility& SetBuildDir(const std::string& build_dir);
-
- /// Set the value of the `-w dupedges` flag, which changes the way build
- /// manifests are loaded. Default value is true.
- Compatibility& SetFlagDupeEdgesShouldErr(bool enabled);
-
- /// Set the value of the `-w phonycycle` flag, which changes the way
- /// build manifests are loaded. Default value is false.
- Compatibility& SetFlagPhonyCycleShouldErr(bool enabled);
-
- /// Create new instance from an encoded string.
- /// On success return new instance after clearing |*error|.
- /// On failure return empty instance after setting |*error| to non-empty
- /// string.
- static Compatibility FromEncodedString(const std::string& str,
- std::string* error);
-
- /// Convert to string for sending through IPC. Not human-readable.
- std::string ToEncodedString() const;
-
- /// Convert to human-readable string for debugging.
- std::string ToString() const;
-
- /// Return true if this instance is compatiable with |other|.
- /// On failure, return false after setting |*reason|.
- bool IsCompatibleWith(const Compatibility& other,
- std::string* reason) const;
-
- /// Return working directory.
- const std::string& build_dir() const { return build_dir_; }
-
- /// Return a string trying to identify the Ninja version being used.
- /// Note that this may include a timestamp or content hash of the current
- /// executable as well. The default value is computed by the constructor
- /// automatically.
- const std::string& ninja_version() const { return ninja_version_; };
-
- /// Return a command-line used to start a server compatible with this
- /// instance.
- std::vector<std::string> ToServerArgs(
- const std::string& server_executable) const;
-
- /// Returns true if the build directory is absolute (or empty, in which
- /// case this will be interpreted as the absolute path of the current
- /// directory). On failure, set |*err| then return false.
- bool CheckBuildDir(std::string* err) const;
-
- private:
- /// Return the default Ninja version, called from constructor.
- static std::string GetDefaultNinjaVersion();
-
- /// Ninja version. This includes a timestamp of the Ninja executable
- /// as well. Must be the first field in this struct.
- std::string ninja_version_;
-
- /// Current build directory.
- std::string build_dir_;
-
- /// Input file name, cannot be empty, relative paths are always
- /// relative to |build_dir|.
- std::string input_file_ = "build.ninja";
-
- /// Options controlling how the manifest is loaded.
- bool dupe_edges_should_err_ = true;
- bool phony_cycle_should_err_ = false;
- };
-
- /// A callable object that returns true if any condition invalidated
- /// the content of the in-memory build graph. In practice this would be
- /// when any of the input .ninja files has changed. A return value of true
- /// indicates that the server must be stopped, and another one restarted.
- using RestartChecker = std::function<bool(void)>;
-
- /// Describe a query from the client to the server. This can be used to
- /// run either a build or a tool (when the |tool| field is not empty).
- struct BuildQuery {
- /// Build configuration
- BuildConfig config;
-
- /// Mirror the set of debug flags in "debug_flags.h" that can be set
- /// individually with `-d <flag>` on the command line that do affect
- /// each build. These are currently stored in global variables instead
- /// of BuildConfig :-/
- bool debug_explaining = false;
- bool debug_keep_depfile = false;
- bool debug_keep_rsp = false;
- bool debug_experimental_statcache = false;
-
- /// Set to true to dump metrics after a build.
- bool dump_metrics = false;
-
- /// Optional tool name, empty means a build is requested, instead of a
- /// tool run.
- std::string tool;
-
- /// List of command-line arguments. If |tool| is empty, this is a list
- /// of targets and should not contain any option. If |tool| is not empty
- /// then this is a list of arguments for the tool.
- std::vector<std::string> args;
-
- std::string ToEncodedString() const;
- std::string ToString() const;
-
- void WriteGlobalVariables() const;
-
- static BuildQuery FromEncodedString(const std::string& str,
- std::string* error);
- };
-
- /// A special value that can be returned by a BuildQueryHandler to
- /// indicate that the server should exit immediately after
- /// serving the query.
- static constexpr int kServerExit = -2;
-
- /// A callable object that implements a query. The result value can
- /// be the special kServerExit value to indicate that the server
- /// should stop immediately. Otherwise, it must be a process exit
- /// code that is >= 0, corresponding to the result of the query.
- using BuildQueryHandler = std::function<int(const BuildQuery&)>;
-
- class Client {
- public:
- /// Create new instance.
- Client();
-
- /// Set the path to the server executable. By default, this is the value
- /// returned by GetCurrentExecutable(), but this method allows one to
- /// override it for testing.
- void SetServerExecutable(const std::string& server_path);
-
- /// Return server executable path. Used for testing.
- const std::string server_executable() const { return server_executable_; }
-
- /// Return true if a server is already running, serving a given build
- /// directory. An empty |build_dir| string means the current working
- /// directory.
- bool IsServerRunning(const std::string& build_dir);
-
- /// Retrieve process ID of server, if it exists, for |build_dir|,
- /// or -1 otherwise. NOTE: DOES NOT WORK ON WINDOWS YET.
- int GetServerPidFor(const std::string& build_dir);
-
- /// Stop the server if it exists, return true if a server was running
- /// before the call for a given build directory. An empty |build_dir|
- /// string means the current working directory.
- bool StopServer(const std::string& build_dir, std::string* err);
-
- /// Ask the server to run a query for us.
- ///
- /// |compatibility| is used to connect either launch a new server instance,
- /// or verify that the existing one serving |compatibility.build_dir()| is
- /// compatible (if not, that instance is shut down, and a new one is
- /// launched).
- ///
- /// On success return true and set |*exit_code_ptr| to the result of the
- /// query, on failure to connect to the server, set |*error| and return
- /// false.
- bool RunQuery(const Compatibility& compatibility, const BuildQuery& query,
- int* exit_code_ptr, std::string* error);
-
- private:
- static PersistentService::Client GetService(const std::string& build_dir);
-
- std::string server_executable_;
- };
-
- class Server {
- public:
- /// Create new instance. This does not do anything except recording
- /// the value of |compatibility|.
- explicit Server(const Compatibility& compatibility);
-
- /// Set the timeout, in milliseconds, used by server instances waiting
- /// for new client connections. On expiration, the server will shutdown
- /// gracefully. A negative value (the default) means no timeout.
- /// This must be called before StartLocalServer() or RunServerThenExit().
- void SetConnectionTimeoutMs(int64_t connection_timeout_ms) {
- server_.SetConnectionTimeoutMs(connection_timeout_ms);
- }
-
- /// Start the server in the current process. This can be called before
- /// RunServerThenExit() to quickly acquire the service named pipe and fail
- /// if not possible, before performing expensive operations like loading
- /// the build plan. Called by RunServerThenExit() implicitly otherwise.
- bool StartLocalServer(std::string* error);
-
- /// Run the server main loop, which will handle incoming
- /// build requests by first calling |restart_checker| to verify whether
- /// a restart is needed (e.g. if the build plan changed), then serve
- /// the request with |query_handler|. This function never returns.
- void RunServerThenExit(RestartChecker restart_checker,
- BuildQueryHandler query_handler);
-
- private:
- bool RunQueryOnServer(IpcHandle connection,
- const BuildQueryHandler& query_handler);
-
- Compatibility compatibility_;
- int64_t connection_timeout_ms_ = -1;
- PersistentService::Server server_;
- };
-};
-
-#endif // NINJA_PERSISTENT_MODE_H_
diff --git a/src/persistent_mode_test.cc b/src/persistent_mode_test.cc
deleted file mode 100644
index 80a685d..0000000
--- a/src/persistent_mode_test.cc
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2023 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 "persistent_mode.h"
-
-#include <inttypes.h>
-#include <stdlib.h>
-
-#include "async_loop.h"
-#include "persistent_mode_test_lib.cc"
-#include "process_utils.h"
-#include "stdio_redirection.h"
-#include "test.h"
-#include "util.h"
-
-TEST(PersistentMode, GetCurrentProcessStatus) {
- static const char kStatusVarName[] = "NINJA_PERSISTENT_MODE";
-
- {
- static const char* const kValues[] = { nullptr, "off", "0" };
- for (const char* value : kValues) {
- ScopedEnvironmentVariable env(kStatusVarName, value);
- EXPECT_EQ(PersistentMode::Disabled,
- PersistentMode::GetCurrentProcessStatus());
- }
- }
-
- {
- static const char* const kValues[] = { "on", "1", "client" };
- for (const char* value : kValues) {
- ScopedEnvironmentVariable env(kStatusVarName, value);
- ASSERT_TRUE(getenv(kStatusVarName));
- EXPECT_EQ(std::string(value), getenv(kStatusVarName));
- EXPECT_EQ(PersistentMode::IsClient,
- PersistentMode::GetCurrentProcessStatus());
- }
- }
-
- {
- static const char* const kValues[] = { "server" };
- for (const char* value : kValues) {
- ScopedEnvironmentVariable env(kStatusVarName, value);
- EXPECT_EQ(PersistentMode::IsServer,
- PersistentMode::GetCurrentProcessStatus());
- }
- }
-}
-
-TEST(PersistentMode, Compatibility) {
- // Convenience helper to return a value with default values
- // appropriate for this test.
- auto new_compat = []() -> PersistentMode::Compatibility {
- return PersistentMode::Compatibility()
- .SetNinjaVersionForTest("this_ninja")
- .SetInputFile("input_file")
- .SetBuildDir("build_dir");
- };
- PersistentMode::Compatibility compat = new_compat();
-
- std::string reason;
- ASSERT_TRUE(compat.IsCompatibleWith(compat, &reason));
-
- std::string encoded = compat.ToEncodedString();
- std::string error;
- auto compat2 =
- PersistentMode::Compatibility::FromEncodedString(encoded, &error);
- ASSERT_TRUE(error.empty());
-
- auto to_string = [](const std::vector<std::string>& args) -> std::string {
- std::string result;
- for (const auto& arg : args) {
- if (!result.empty())
- result += " ";
- result += arg;
- }
- return result;
- };
-
- ASSERT_TRUE(compat.IsCompatibleWith(compat2, &reason));
- ASSERT_TRUE(compat2.IsCompatibleWith(compat, &reason));
-
- compat2 = new_compat().SetBuildDir("another_build_dir");
-
- ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
- EXPECT_EQ(
- reason,
- "Working dir mismatch, expected [build_dir] vs [another_build_dir]");
- EXPECT_EQ(
- "ninja -C another_build_dir -f input_file -wdupbuild=err "
- "-wphonycycle=warn",
- to_string(compat2.ToServerArgs("ninja")));
-
- compat2 = new_compat().SetInputFile("another_input_file");
-
- ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
- EXPECT_EQ(
- reason,
- "Input file mismatch, expected [input_file] vs [another_input_file]");
- EXPECT_EQ(
- "ninja -C build_dir -f another_input_file -wdupbuild=err "
- "-wphonycycle=warn",
- to_string(compat2.ToServerArgs("ninja")));
-
- compat2 = new_compat().SetFlagDupeEdgesShouldErr(false);
- ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
- EXPECT_EQ(reason,
- "Flag dupe_edges_should_err mismatch, expected true vs false");
- EXPECT_EQ(
- "ninja -C build_dir -f input_file -wdupbuild=warn -wphonycycle=warn",
- to_string(compat2.ToServerArgs("ninja")));
-
- compat2 = new_compat().SetFlagPhonyCycleShouldErr(true);
- ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
- EXPECT_EQ(reason,
- "Flag phony_cycle_should_err mismatch, expected false vs true");
- EXPECT_EQ("ninja -C build_dir -f input_file -wdupbuild=err -wphonycycle=err",
- to_string(compat2.ToServerArgs("ninja")));
-
- compat2 = compat;
- compat2.SetNinjaVersionForTest("not_this_ninja");
- ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
- EXPECT_EQ(
- reason,
- "Ninja version mismatch, expected [this_ninja] vs [not_this_ninja]");
- EXPECT_EQ("ninja -C build_dir -f input_file -wdupbuild=err -wphonycycle=warn",
- to_string(compat2.ToServerArgs("ninja")));
-}
-
-// Unfortunately, this test, and PersistentMode in general do not work
-// properly on Windows yet. The root of the problem is that StdioRedirector
-// does not work because it is impossible to pass Win32 console handlers
-// to other processes. Solving this requires an alternative implementation
-// that relies on proxying the console handles through anonymous pipes on
-// the client, which will be provided in a future fix.
-#ifndef _WIN32
-TEST(PersistentModeClient, DefaultMode) {
- PersistentMode::Client client;
- client.SetServerExecutable(persistent_mode_test::GetServerExecutable());
-
- ScopedTempDir test_dir;
- test_dir.CreateAndEnter("persistent_mode_test");
- std::string build_dir = GetCurrentDir();
-
- ASSERT_FALSE(client.IsServerRunning(build_dir));
-
- auto compat = persistent_mode_test::GetClientCompatibility(build_dir);
- std::string error;
- int exit_code = -1;
-
- auto query = persistent_mode_test::GetClientTestQuery1(build_dir);
-
- AsyncLoop::ResetForTesting();
- AsyncLoop& async_loop = AsyncLoop::Get();
-
- {
- StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
- bool ret = client.RunQuery(compat, query, &exit_code, &error);
- if (!ret)
- fprintf(stderr, "\nERROR: %s\n", error.c_str());
- std::string result;
- EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
-
- ASSERT_TRUE(ret);
- EXPECT_EQ(0u, exit_code);
- EXPECT_EQ(result, build_dir);
- }
-
- ASSERT_TRUE(client.IsServerRunning(build_dir));
-
- ASSERT_TRUE(client.StopServer(build_dir, &error));
-}
-
-TEST(PersistentModeClient, EnvironmentVariables) {
- PersistentMode::Client client;
- client.SetServerExecutable(persistent_mode_test::GetServerExecutable());
-
- ScopedTempDir test_dir;
- test_dir.CreateAndEnter("persistent_mode_test");
- std::string build_dir = test_dir.temp_dir_name_;
-
- ASSERT_FALSE(client.IsServerRunning(build_dir));
-
- auto compat = persistent_mode_test::GetClientCompatibility(build_dir);
- std::string error;
- int exit_code = -1;
-
- std::vector<std::string> print_varnames;
- print_varnames.emplace_back("bar");
- print_varnames.emplace_back("foo");
- print_varnames.emplace_back("undefined");
-
- auto query = persistent_mode_test::GetClientTestQuery2(print_varnames);
- query.config.environment.Insert("foo", "FOO");
- query.config.environment.Insert("bar", "BAR");
- query.config.environment.Remove("undefined");
-
- AsyncLoop::ResetForTesting();
- AsyncLoop& async_loop = AsyncLoop::Get();
-
- // The first query should retrieve the variable values from the client
- // process that started it.
- {
- StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
- bool ret = client.RunQuery(compat, query, &exit_code, &error);
- if (!ret)
- fprintf(stderr, "\nERROR: %s\n", error.c_str());
- std::string result;
- EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
-
- ASSERT_TRUE(ret);
- EXPECT_EQ(0u, exit_code);
- EXPECT_EQ(result, "bar=BAR\nfoo=FOO\nundefined=<UNSET>\n");
- }
-
- // Change the local values of 'foo' and 'undefined', and verify that only the
- // ones listed by passthrough_varnames were modified on the server.
- {
- query.config.environment.Insert("foo", "NEW_FOO");
- query.config.environment.Insert("undefined", "SURPRISE!");
-
- StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
- bool ret = client.RunQuery(compat, query, &exit_code, &error);
- if (!ret)
- fprintf(stderr, "\nERROR: %s\n", error.c_str());
- std::string result;
- EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
-
- ASSERT_TRUE(ret);
- EXPECT_EQ(0u, exit_code);
- EXPECT_EQ(result, "bar=BAR\nfoo=NEW_FOO\nundefined=SURPRISE!\n");
- }
-
- ASSERT_TRUE(client.IsServerRunning(build_dir));
-
- ASSERT_TRUE(client.StopServer(build_dir, &error));
-}
-
-#endif // !_WIN32
diff --git a/src/persistent_mode_test_helper.cc b/src/persistent_mode_test_helper.cc
deleted file mode 100644
index ca22500..0000000
--- a/src/persistent_mode_test_helper.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2023 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 <stdio.h>
-#include <stdlib.h>
-
-#include "persistent_mode.h"
-#include "persistent_mode_test_lib.cc"
-#include "util.h"
-
-int main(int argc, char** argv) {
- fprintf(stderr, "STARTING SERVER IN %s\n", GetCurrentDir().c_str());
- int64_t connection_timeout_ms = 3000;
- auto compatibility = persistent_mode_test::GetServerCompatibility(
- argc, argv, &connection_timeout_ms);
- persistent_mode_test::RunServerThenExit(compatibility, connection_timeout_ms);
- return 127;
-}
diff --git a/src/persistent_mode_test_lib.cc b/src/persistent_mode_test_lib.cc
deleted file mode 100644
index 3d45355..0000000
--- a/src/persistent_mode_test_lib.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2023 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 "persistent_mode_test_lib.h"
-
-#ifdef _WIN32
-#include "getopt.h"
-#elif defined(_AIX)
-#include "getopt.h"
-#else
-#include <getopt.h>
-#endif
-
-#include <string>
-
-#include "util.h"
-
-namespace persistent_mode_test {
-
-namespace {
-
-struct ServerState {
- ServerState(const PersistentMode::Compatibility& compatibility,
- int64_t connection_timeout_ms)
- : server_(compatibility) {
- server_.SetConnectionTimeoutMs(connection_timeout_ms);
- }
-
- void RunServerThenExit() {
- server_.RunServerThenExit(
- [this]() { return RestartCheck(); },
- [this](const PersistentMode::BuildQuery& query) -> int {
- return HandleQuery(query);
- });
- }
-
- bool RestartCheck() { return false; };
-
- int HandleQuery(const PersistentMode::BuildQuery& query) {
- if (query.tool == "print-current-dir") {
- fprintf(stdout, "%s", GetCurrentDir().c_str());
- return 0;
- }
- if (query.tool == "get-env-vars") {
- for (const auto& varname : query.args) {
- const char* varvalue = query.config.environment.Get(varname.c_str());
- if (!varvalue)
- varvalue = "<UNSET>";
- fprintf(stdout, "%s=%s\n", varname.c_str(), varvalue);
- }
- return 0;
- }
- fprintf(stderr, "UNKNOWN QUERY TYPE!\n");
- return 1;
- }
-
- // Create new query to print the current directory.
- static PersistentMode::BuildQuery MakeQuery1(const std::string& build_dir) {
- PersistentMode::BuildQuery query;
- query.tool = "print-current-dir";
- return query;
- }
-
- // Create a new query to print the environment variables of the server
- // process.
- static PersistentMode::BuildQuery MakeQuery2(
- const std::vector<std::string>& print_variables) {
- PersistentMode::BuildQuery query;
- query.tool = "get-env-vars";
- query.args = print_variables;
- return query;
- }
- PersistentMode::Server server_;
-};
-
-} // namespace
-
-std::string GetServerExecutable() {
- std::string result =
- GetCurrentExecutableDir() + "/persistent_mode_test_helper";
-#ifdef _WIN32
- result += ".exe";
-#endif
- return result;
-}
-
-PersistentMode::Compatibility GetClientCompatibility(
- const std::string& build_dir) {
- PersistentMode::Compatibility result;
- result.SetNinjaVersionForTest("version-1.0")
- .SetInputFile("persistent_mode.input")
- .SetBuildDir(build_dir);
- return result;
-}
-
-PersistentMode::Compatibility GetServerCompatibility(
- int argc, char** argv, int64_t* connection_timeout_ms) {
- const char* progname = argv[0];
-
- auto print_usage = [progname]() {
- fprintf(stderr,
- "Usage: %s [-C BUILD_DIR] [-f INPUT_FILE] [-t TIMEOUT_MILLIS] [-w "
- "WARNING]+\n",
- progname);
- exit(1);
- };
-
- PersistentMode::Compatibility result;
-
- int opt;
- while ((opt = getopt(argc, argv, const_cast<char*>("C:f:w:t:"))) != -1) {
- fprintf(stderr, "OPT %c\n", opt);
- switch (opt) {
- case 'C':
- result.SetBuildDir(optarg);
- break;
- case 'f':
- result.SetInputFile(optarg);
- break;
- case 'w':
- if (!strcmp(optarg, "dupbuild=err")) {
- result.SetFlagDupeEdgesShouldErr(true);
- } else if (!strcmp(optarg, "dupbuild=warn")) {
- result.SetFlagDupeEdgesShouldErr(false);
- } else if (!strcmp(optarg, "phonycycle=err")) {
- result.SetFlagPhonyCycleShouldErr(true);
- } else if (!strcmp(optarg, "phonycycle=warn")) {
- result.SetFlagPhonyCycleShouldErr(false);
- } else {
- Fatal("Unknown -d argument value: %s", optarg);
- }
- break;
- case 't':
- *connection_timeout_ms = static_cast<int64_t>(atoll(optarg));
- break;
- default:
- print_usage();
- }
- }
- fprintf(stderr, "argc=%d optind=%d\n", argc, optind);
- argc -= optind;
- argv += optind;
- if (argc > 0)
- print_usage();
-
- // Must match the version in GetClientCompatibility().
- result.SetNinjaVersionForTest("version-1.0");
- return result;
-}
-
-PersistentMode::BuildQuery GetClientTestQuery1(const std::string& build_dir) {
- return ServerState::MakeQuery1(build_dir);
-}
-
-PersistentMode::BuildQuery GetClientTestQuery2(
- const std::vector<std::string>& print_variables) {
- return ServerState::MakeQuery2(print_variables);
-}
-
-void RunServerThenExit(const PersistentMode::Compatibility& compatibility,
- int64_t connection_timeout_ms) {
- ServerState state(compatibility, connection_timeout_ms);
- state.RunServerThenExit();
-}
-
-} // namespace persistent_mode_test
diff --git a/src/persistent_mode_test_lib.h b/src/persistent_mode_test_lib.h
deleted file mode 100644
index 98b725e..0000000
--- a/src/persistent_mode_test_lib.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2023 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 <vector>
-
-#include "persistent_mode.h"
-
-namespace persistent_mode_test {
-
-// Return path to the server executable for this test.
-extern std::string GetServerExecutable();
-
-// Return a PersistentMode::Compatibility value for the default service
-// implementation.
-extern PersistentMode::Compatibility GetClientCompatibility(
- const std::string& build_dir);
-
-// Return a PersistentMode::Compatibility value to be used on the server,
-// from the command-line content.
-extern PersistentMode::Compatibility GetServerCompatibility(
- int argc, char** argv, int64_t* connection_timeout_ms);
-
-extern PersistentMode::BuildQuery GetClientTestQuery1(
- const std::string& build_dir);
-
-extern PersistentMode::BuildQuery GetClientTestQuery2(
- const std::vector<std::string>& print_variables);
-
-} // namespace persistent_mode_test
diff --git a/src/persistent_service.cc b/src/persistent_service.cc
deleted file mode 100644
index 2a3726d..0000000
--- a/src/persistent_service.cc
+++ /dev/null
@@ -1,624 +0,0 @@
-// Copyright 2023 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 "persistent_service.h"
-
-#include "async_loop.h"
-#include "ipc_utils.h"
-#include "util.h"
-
-#define DEBUG 0
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#endif
-
-#if DEBUG
-#define SERVER_LOG(...) \
- do { \
- fprintf(stderr, "SERVER_LOG: "); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } while (0)
-#define CLIENT_LOG(...) \
- do { \
- fprintf(stderr, "CLIENT_LOG: "); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } while (0)
-#else
-#define CLIENT_LOG(...) (void)0
-#define SERVER_LOG(...) (void)0
-#endif
-
-namespace {
-
-// Sleep for |delay_ms| milliseconds.
-void SleepMilliSeconds(int delay_ms) {
-#ifdef _WIN32
- if (delay_ms > 0)
- ::Sleep(static_cast<DWORD>(delay_ms));
-#else
- usleep(static_cast<useconds_t>(delay_ms) * 1000);
-#endif
-}
-
-struct ProcessInfo {
-#ifdef _WIN32
- HANDLE process_handle;
-#else
- pid_t process_pid;
-#endif
-};
-
-// Start a new process, with command-line |args|.
-// Uses fork()/exec() on Posix, and CreateProcess on Win32.
-// Return true on success, or set |*err| and return false on failure.
-bool SpawnServerProcess(const PersistentService::Config& config,
- ProcessInfo* info, std::string* err) {
- if (config.command.empty()) {
- *err = "Empty command line!";
- return false;
- }
-
-#ifdef _WIN32
- std::string command_string;
- for (const auto& arg : config.command) {
- std::string escaped;
- GetWin32EscapedString(arg, &escaped);
- command_string += escaped;
- command_string += ' ';
- }
- // Remove trailing space if any.
- if (!command_string.empty())
- command_string.resize(command_string.size() - 1u);
-
- SECURITY_ATTRIBUTES security_attributes = {};
- security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
- security_attributes.bInheritHandle = TRUE;
- // Must be inheritable so subprocesses can dup to children.
- HANDLE nul =
- CreateFileA("NUL", GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
- if (nul == INVALID_HANDLE_VALUE)
- Win32Fatal("couldn't open nul");
-
- HANDLE log = nul;
- std::string log_file = config.log_file;
- if (log_file.empty()) {
- // As a debug helper, use the log file from this environment variable.
- const char* env = getenv("DEBUG_PERSISTENT_SERVICE_LOG_FILE");
- if (env)
- log_file = env;
- }
- if (!log_file.empty()) {
- log =
- CreateFileA(log_file.c_str(), STANDARD_RIGHTS_WRITE | FILE_APPEND_DATA,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_ALWAYS, 0, NULL);
- if (log == INVALID_HANDLE_VALUE)
- Win32Fatal("couldn't open log file", log_file.c_str());
- }
-
- STARTUPINFOA startup_info = {};
- startup_info.cb = sizeof(STARTUPINFO);
- startup_info.dwFlags = STARTF_USESTDHANDLES;
- startup_info.hStdInput = nul;
- startup_info.hStdOutput = log;
- startup_info.hStdError = log;
- PROCESS_INFORMATION process_info = {};
-
- // Ninja handles ctrl-c, except for subprocesses in console pools.
- DWORD process_flags = CREATE_NEW_PROCESS_GROUP;
-
- {
- // TODO(digit): Create new environment variable block and add/replace
- // the value in it, without touching the parent env.
- for (const auto& pair : config.env_vars) {
- const char* varname = pair.first.c_str();
- const char* value = pair.second.c_str();
- SetEnvironmentVariable(varname, value);
- }
- }
-
- // Do not prepend 'cmd /c' on Windows, this breaks command
- // lines greater than 8,191 chars.
- if (!CreateProcessA(NULL, (char*)command_string.c_str(), NULL, NULL,
- /* inherit handles */ TRUE, process_flags, NULL, NULL,
- &startup_info, &process_info)) {
- DWORD error = GetLastError();
- if (error == ERROR_FILE_NOT_FOUND) {
- CloseHandle(nul);
- if (log != nul)
- CloseHandle(log);
- // child_ is already NULL;
- *err =
- "CreateProcess failed: The system cannot find the file "
- "specified: [" +
- command_string + "]";
- return false;
- } else {
- fprintf(stderr, "\nCreateProcess failed. Command attempted:\n\"%s\"\n",
- command_string.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) {
- hint = "is the command line too long?";
- }
- Win32Fatal("CreateProcess", hint);
- }
- }
-
- CloseHandle(nul);
- if (log != nul)
- CloseHandle(log);
-
- CloseHandle(process_info.hThread);
- return true;
-#else // !_WIN32
- // Build arguments array for future exec() call.
- std::vector<char*> exec_args;
- exec_args.reserve(config.command.size() + 1);
- for (const auto& arg : config.command) {
- exec_args.push_back(const_cast<char*>(arg.data()));
- CLIENT_LOG("CMD: [%s]", arg.c_str());
- }
- exec_args.push_back(nullptr);
-
- pid_t process = fork();
- if (process < 0) {
- *err = "fork failed()!";
- return false;
- }
-
- if (process == 0) {
- // Create new session to not receive signals from parent process group.
- if (setsid() < 0) {
- fprintf(stderr, "ERROR: setsid() failed: %s\n", strerror(errno));
- exit(1);
- }
-
- // Change current working directory.
- if (!config.working_dir.empty()) {
- const char* work_dir = config.working_dir.c_str();
- if (chdir(work_dir) < 0)
- ErrnoFatal("chdir", work_dir);
- }
-
- // Redirect stdin to /dev/null and stdout/stderr to a log file if
- // PERSISTENT_SERVER_LOG_FILE is set in the environment, or to
- // /dev/null otherwise.
- int null_fd = open("/dev/null", O_RDWR);
- if (null_fd < 0) {
- fprintf(stderr, "ERROR: open(/dev/null) failed: %s\n", strerror(errno));
- exit(1);
- }
- int log_fd = null_fd;
- std::string log_file = config.log_file;
- if (log_file.empty()) {
- // As a debug helper, use the log file from this environment variable.
- const char* env = getenv("DEBUG_PERSISTENT_SERVICE_LOG_FILE");
- if (env)
- log_file = env;
- }
- if (!log_file.empty()) {
- log_fd = open(log_file.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0755);
- if (log_fd < 0) {
- fprintf(stderr, "ERROR: open(%s) failed: %s\n", log_file.c_str(),
- strerror(errno));
- exit(1);
- }
- }
-
- fflush(stdout);
- fflush(stderr);
-
- dup2(null_fd, 0);
- dup2(log_fd, 1);
- dup2(log_fd, 2);
-
- // Set extra environment variables.
- for (const auto& pair : config.env_vars) {
- const char* varname = pair.first.c_str();
- const char* value = pair.second.c_str();
- setenv(varname, value, 1);
- }
-
- fprintf(stderr, "\n\nSTARTING NEW PERSISTENT SERVER: %s\n",
- config.command[0].c_str());
- execv(config.command[0].c_str(), exec_args.data());
- fprintf(stderr, "ERROR: exec() failed: %s\n", strerror(errno));
- exit(1);
- }
- // In parent process, do not do anything.
- info->process_pid = process;
- return true;
-#endif // !_WIN32
-}
-
-// Type of commands received from the server.
-enum ServerCommandType {
- kServerCommandTypeStop,
- kServerCommandTypeGetPid,
- kServerCommandTypeClientQuery,
-};
-
-} // namespace
-
-///////////////////////////////////////////////////////////////////////////
-///
-/// C L I E N T S I D E
-///
-
-PersistentService::Client::Client(const std::string& service_name)
- : service_name_(service_name) {}
-
-PersistentService::Client::~Client() = default;
-
-bool PersistentService::Client::HasServer() const {
- return IpcServiceHandle::IsBound(service_name_);
-}
-
-int PersistentService::Client::GetServerPid() const {
- std::string err;
- IpcHandle client = RawConnect(&err);
- if (!client)
- return -1;
-
- uint8_t query_type = kServerCommandTypeGetPid;
- int server_pid = -1;
- if (!RemoteWrite(query_type, client, &err) ||
- !RemoteRead(server_pid, client, &err)) {
- return -1;
- }
- return server_pid;
-}
-
-bool PersistentService::Client::StopServer(std::string* err) const {
- IpcHandle client = RawConnect(err);
- if (!client)
- return false;
-
- uint8_t query_type = kServerCommandTypeStop;
- if (!client.Write(&query_type, sizeof(query_type), err)) {
- *err = StringFormat("Could not stop server: %s", err->c_str());
- return false;
- }
-
- return true;
-}
-
-bool PersistentService::Client::WaitForServerShutdown() {
- std::string error;
- for (int retry_count = 20; retry_count > 0; --retry_count) {
- if (!HasServer())
- return true;
-
- SleepMilliSeconds(100);
- }
- return false;
-}
-
-IpcHandle PersistentService::Client::Connect(const Config& config,
- std::string* error) {
- bool try_again = true;
-
- while (true) {
- CLIENT_LOG("Trying to connect to server.");
- IpcHandle client = ConnectOrStartServer(config, error);
- if (!client)
- return {};
-
- CLIENT_LOG("Sending version info to server.");
- if (!RemoteWrite(config.version_info, client, error)) {
- *error = "Could not send version info: " + *error;
- return {};
- }
-
- // Receive the |compatible| flag that indicates that the server is
- // compatible with the current build plan.
- std::string version_check;
- if (!RemoteRead(version_check, client, error)) {
- *error = "Could not read version check result: " + *error;
- return {};
- }
-
- if (version_check.empty()) {
- // Good, return the handle now.
- return client;
- }
-
- // The server was not compatible with the current client.
- // Assume it exited, and start another one at least once.
- CLIENT_LOG("Incompatible server version: %s", version_check.c_str());
-
- if (!try_again) {
- // Already tried once, so report failure.
- CLIENT_LOG("Failed to connect to or start server!");
- return {};
- }
-
- try_again = false;
- CLIENT_LOG("Waiting for incompatible server shutdown.");
- if (!WaitForServerShutdown()) {
- *error = "Could not shutdown incompatible server!?!";
- return {};
- }
- }
-}
-
-IpcHandle PersistentService::Client::RawConnect(std::string* err) const {
- return IpcServiceHandle::ConnectTo(service_name_, err);
-}
-
-IpcHandle PersistentService::Client::ConnectOrStartServer(
- const Config& config, std::string* err) const {
- int retry_count = 5;
- int retry_delay_ms = 10;
- bool server_started = false;
- ProcessInfo info = {};
- while (true) {
- // Try to connect to the server first.
- IpcHandle client = RawConnect(err);
- if (client) {
- CLIENT_LOG("Got client connection to server!");
- uint8_t query_type = kServerCommandTypeClientQuery;
- if (!client.Write(&query_type, sizeof(query_type), err)) {
- CLIENT_LOG(
- "ERROR: Could not write query type, did server disconnect?: %s",
- err->c_str());
- client.Close();
- } else {
- CLIENT_LOG("Sent query type");
- }
- return client;
- }
-
- if (!server_started) {
- CLIENT_LOG("No initial connection. Spawning server");
- // Spawn a server if one wasn't already started.
- if (!SpawnServerProcess(config, &info, err)) {
- CLIENT_LOG("Could not spawn server: %s", err->c_str());
- return {};
- }
- server_started = true;
-
- } else if (retry_count == 0) {
- CLIENT_LOG("Failure to connect to server, exiting retry loop: %s",
- err->c_str());
- return {};
- } else {
- --retry_count;
- if (retry_delay_ms < 1024)
- retry_delay_ms *= 2;
- }
-
- CLIENT_LOG("Waiting for %ld milliseconds", (long)retry_delay_ms);
- SleepMilliSeconds(retry_delay_ms);
- }
-}
-
-///////////////////////////////////////////////////////////////////////////
-///
-/// S E R V E R S I D E
-///
-
-PersistentService::Server::Server(const std::string& service_name)
- : service_name_(service_name) {}
-
-bool PersistentService::Server::BindService(std::string* err) {
- if (service_handle_) {
- *err = "Server already started!";
- return false;
- }
- SERVER_LOG("Trying to get service %s", service_name_.c_str());
- service_handle_ = IpcServiceHandle::BindTo(service_name_, err);
- if (!service_handle_)
- SERVER_LOG("Got error %s", err->c_str());
- else
- SERVER_LOG("Got it!");
- return !!service_handle_;
-}
-
-void PersistentService::Server::RunServerThenExit(
- const VersionCheckHandler& version_check_handler,
- const RequestHandler& request_handler) {
- std::string error;
- if (!service_handle_ && !BindService(&error)) {
- Error("Could not acquire service handle: %s", error.c_str());
- exit(1);
- }
- SERVER_LOG("Service handle acquired, starting server loop");
- RunServerThenExitInternal(version_check_handler, std::move(request_handler));
-}
-
-void PersistentService::Server::RunServerThenExitInternal(
- const VersionCheckHandler& version_check_handler,
- const RequestHandler& request_handler) {
- AsyncLoop& async_loop = AsyncLoop::Get();
- std::string error;
-#ifndef _WIN32
- SigPipeBlocker sig_pipe_blocker;
-#endif
-
- /// Convenience class to wait for client connections with a timeout.
- /// Usage is:
- ///
- /// 1) Create class, passing references to an IpcServiceHandle
- /// and an AsyncLoop instance.
- ///
- /// 2) Call Wait() to wait for a new connection.
- ///
- /// 3) Either destroy the instance, or call Close() before that to
- /// ensure the corresponding server handle is closed before
- /// calling exit().
- ///
- class ConnectionWaiter {
- public:
- /// Constructor sets up an internal AsyncHandle to do the wait.
- /// Note that this moves the native service handle into this instance, but
- /// |service_handle| still needs to be closed manually before calling
- /// ::exit(), because on MacOS it does extra work to remove a Unix socket
- /// and pid file.
- ConnectionWaiter(IpcServiceHandle& service_handle, AsyncLoop& async_loop)
- : async_loop_(async_loop), async_(
- AsyncHandle::Create(std::move(service_handle), async_loop, callback())) {}
-
- /// Wait for a new client connection for |timeout_ms| milliseconds.
- /// Return AsyncLoop exit status, on success, set |client| to receive
- /// the new client connection handle.
- AsyncLoop::ExitStatus Wait(int64_t timeout_ms, IpcHandle& client) {
- completed_ = false;
- accepted_ = false;
-
- int64_t now_ms = async_loop_.NowMs();
- (void)now_ms; // silence compiler when SERVER_LOG() expansion is empty.
-
- if (timeout_ms < 0) {
- SERVER_LOG("Waiting for new client connection (no timeout)");
- } else {
- SERVER_LOG("Waiting for new client connection (for up to %.1f seconds)",
- timeout_ms / 1000.);
- }
-
- async_.StartAccept();
- auto status = async_loop_.RunUntil([this]() { return completed_; },
- timeout_ms);
-
- if (accepted_)
- client = async_.TakeAcceptedHandle();
-
- SERVER_LOG("Connection time %s",
- StringFormatDurationMs(async_loop_.NowMs() - now_ms).c_str());
- return status;
- }
-
- /// Close the internal handle explicitly.
- void Close() {
- async_.Close();
- }
-
- private:
- AsyncLoop& async_loop_;
- AsyncHandle async_;
- IpcHandle client_;
- bool completed_ = false;
- bool accepted_ = false;
-
- // Return the AsyncHandle::Callback to be used by |async| in this instance.
- AsyncHandle::Callback callback() {
- return [this](AsyncError error, size_t) {
- completed_ = true;
- accepted_ = (error == 0);
- };
- }
- };
-
- ConnectionWaiter waiter(service_handle_, async_loop);
- while (true) {
- IpcHandle client;
- AsyncLoop::ExitStatus status = waiter.Wait(connection_timeout_ms_, client);
-
- if (status == AsyncLoop::ExitInterrupted)
- break;
-
- if (status == AsyncLoop::ExitTimeout) {
- SERVER_LOG("Timeout waiting for client connection");
- break;
- }
- if (!client) {
- SERVER_LOG("Could not accept new client: %s", error.c_str());
- break;
- }
-
- SERVER_LOG("Reading query type...");
- // Get command, which can be 'kill' or 'query' at the moment.
- uint8_t query_type = 0;
- if (!client.Read(&query_type, sizeof(query_type), &error)) {
- SERVER_LOG("Could not read client query type !?");
- break;
- }
-
- SERVER_LOG("Got query_type %d\n", query_type);
-
- if (query_type == kServerCommandTypeStop) {
- SERVER_LOG("Client asking server to stop!");
- break;
- }
- if (query_type == kServerCommandTypeGetPid) {
- SERVER_LOG("Client asking for server pid!");
-#ifdef _WIN32
- int pid = static_cast<int>(GetCurrentProcessId());
-#else
- int pid = getpid();
-#endif
- if (!RemoteWrite(pid, client, &error)) {
- SERVER_LOG("Could not send pid %d back to client!: %s", pid,
- error.c_str());
- break;
- }
- SERVER_LOG("Sent pid %d to client. Looping", pid);
- continue;
- }
- if (query_type != kServerCommandTypeClientQuery) {
- SERVER_LOG("Unknown client query type: %d", query_type);
- break;
- }
-
- SERVER_LOG("Accepted client connection, checking version info");
- std::string version_info;
- if (!RemoteRead(version_info, client, &error)) {
- SERVER_LOG("Could not client version info: %s", error.c_str());
- break;
- }
-
- std::string version_check = version_check_handler(version_info);
- if (!RemoteWrite(version_check, client, &error)) {
- SERVER_LOG("Could not write version check result to client: %s",
- error.c_str());
- break;
- }
-
- if (!version_check.empty()) {
- SERVER_LOG("Incompatible client version info: %s", version_check.c_str());
- break;
- }
-
- if (!request_handler(std::move(client))) {
- SERVER_LOG("Request handler returned false, exiting server loop");
- break;
- }
- async_loop.ClearInterrupt();
- }
- SERVER_LOG("Exiting!");
-
- // Since calling ::exit() directly here prevents the destructors
- // of the variables defined in this function from running, close the
- // waiter handle explicitly.
- waiter.Close();
-
- // Note: while the file descriptor was moved to waiter, this
- // instance must be destroyed explicitly to remove the pid file and
- // Unix socket filesystem entry on MacOS.
- service_handle_.Close();
-
- ::exit(0);
-}
diff --git a/src/persistent_service.h b/src/persistent_service.h
deleted file mode 100644
index d54e3f7..0000000
--- a/src/persistent_service.h
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright 2023 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_PERSISTENT_SERVICE_H_
-#define NINJA_PERSISTENT_SERVICE_H_
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "ipc_handle.h"
-
-/// PersistentService implements a "persistent server" mode for
-/// command-line executables. The main idea is that a client process
-/// uses an anonymous pipe (on Win32) or a Unix domain socket (on Posix)
-/// to connect to a server process, then ask for work to be done by
-/// the server on its behalf.
-///
-/// If a server is not available, the client process will automatically launch
-/// one, as a detached background process. Supports both Posix and Windows.
-///
-/// After creating an instance, usage on the client side is:
-///
-/// 1) Create a PersistentService::Config object describing how to launch
-/// the server, if needed.
-///
-/// 2) Call CreateClient() which will give an IpcHandle to communicate with
-/// the server from the current process.
-///
-///
-/// While usage on the server is:
-///
-/// 1) Call BindService() to immediately bind the service pipe/socket.
-/// This is optional, but is useful when the server needs to perform
-/// a long setup operation before serving client requests.
-///
-/// 2) Call RunServerThenExit() to enter an infinite loop to serve
-/// client queries. This takes two callable objects that will be invoked
-/// on each query.
-///
-
-class PersistentService {
- public:
- /// Convenience struct to hold the configuration used to spawn
- /// a new server. This includes a command line, optional working directory,
- /// and other options.
- struct Config {
- /// Constructor provides the command used to spawn the new server process.
- explicit Config(const std::vector<std::string>& server_command)
- : command(server_command) {}
-
- /// Set the working directory to be used when running the server process.
- /// If empty (the default), not change will be applied.
- Config& SetWorkingDir(const std::string& dir) {
- working_dir = dir;
- return *this;
- }
-
- /// By default, the server process redirects its stdin/stdout/stderr
- /// to /dev/null. Call this method to specify the path of a file where
- /// server logs will be written to instead.
- Config& SetLogFile(const std::string& log_file_path) {
- log_file = log_file_path;
- return *this;
- }
-
- /// Add an environment variable definition that will be set before
- /// spawning a server process.
- Config& AddEnvVariable(const char* name, const char* value) {
- env_vars.push_back(std::make_pair(name, value));
- return *this;
- }
-
- /// Set a string describing version information that will be matched
- /// against each new client connection. A mistmatch will be reported
- /// then the server will stop automatically.
- Config& SetVersionInfo(const std::string& info) {
- version_info = info;
- return *this;
- }
-
- std::vector<std::string> command;
- std::string working_dir;
- std::string version_info;
- std::string log_file;
- std::vector<std::pair<std::string, std::string>> env_vars;
- };
-
- ///////////////////////////////////////////////////////////////////////////
- ///
- /// C L I E N T S I D E
- ///
-
- class Client {
- public:
- /// Create new instance. This doesn't do anything yet.
- /// |service_name| is a unique service name, used to create a named pipe
- /// on the host system.
- explicit Client(const std::string& service_name);
-
- /// Destructor. Note that this does *not* kill any server instance
- /// started by this client.
- ~Client();
-
- /// Returns true if a server is already running for the service.
- bool HasServer() const;
-
- /// Return server PID if it exists, or -1 otherwise.
- int GetServerPid() const;
-
- /// Stop a server. Return true on success (meaning there was a server, and
- /// that it was asked to stop), and false otherwise. Note that the function
- /// returns immediately after sending the stop command. Use
- /// WaitForServerShutdown() to wait for its complete shutdown if needed.
- bool StopServer(std::string* err) const;
-
- /// Wait until the server is properly shutdown after a StopServer() call.
- /// Returns after 2 seconds max, return true on success, false otherwise.
- bool WaitForServerShutdown();
-
- /// Create new client connection. This will start a server automatically
- /// if one is not already running. If an existing server has incompatible
- /// version_info, it is also stopped and another instance restarted.
- ///
- /// In case of error, return an invalid IpcHandle and sets |*err|.
- /// In case of success, the result can be used to communicate with the
- /// server.
- IpcHandle Connect(const Config& config, std::string* err);
-
- private:
- IpcHandle RawConnect(std::string* err) const;
- IpcHandle ConnectOrStartServer(const Config& config,
- std::string* err) const;
-
- std::string service_name_;
- };
-
- ///////////////////////////////////////////////////////////////////////////
- ///
- /// S E R V E R S I D E
- ///
-
- class Server {
- public:
- /// Create new instance, |service_name| is the unique service name
- /// and |config| is the server configuration that will be used by the
- /// server.
- explicit Server(const std::string& service_name);
-
- /// Change the connection timeout, in milliseconds, used by a server
- /// loop when waiting for new client connections. On expiration, the server
- /// will shut down graciously. The default value (-1) means no timeout.
- void SetConnectionTimeoutMs(int64_t connection_timeout_ms) {
- connection_timeout_ms_ = connection_timeout_ms;
- }
-
- /// Bind to the service named pipe early. This is called implicitly by
- /// RunServerThenExit() but can be called earlier from a server process
- /// before expensive operations, in order to avoid races.
- bool BindService(std::string* err);
-
- /// A callable that receives a version_info string from the client and
- /// compares it to its own version. Returns an empty string when the
- /// versions are compatible, or a failure message otherwise.
- using VersionCheckHandler = std::function<std::string(const std::string&)>;
-
- /// A callable object that receives an IpcHandle corresponding to a new
- /// client request. Returns true to indicate that the server should keep
- /// running, or false to tell it to exit immediately.
- using RequestHandler = std::function<bool(IpcHandle)>;
-
- /// Start a server in the current process, then start a loop that waits for
- /// a client request. This function never returns (and calls exit() directly
- /// when needed).
- ///
- /// For each request, the client's Config::version_info string is received
- /// and passed to |version_check_handler| to verify that it is compatible
- /// with the client. If this callable returns a non-empty string, the loop
- /// exits after reporting the mismatch to the client.
- ///
- /// Otherwise, |request_handler| is invoked, receiving ownership of the
- /// client connection handle. If this returns true, the function exits.
- ///
- /// This function should only be called when starting a server executable.
- void RunServerThenExit(const VersionCheckHandler& version_check_handler,
- const RequestHandler& request_handler);
-
- private:
- void RunServerThenExitInternal(
- const VersionCheckHandler& version_check_handler,
- const RequestHandler& request_handler);
-
- std::string service_name_;
- int64_t connection_timeout_ms_ = -1;
- IpcServiceHandle service_handle_;
- };
-};
-
-#endif // NINJA_PERSISTENT_SERVICE_H_
diff --git a/src/persistent_service_test.cc b/src/persistent_service_test.cc
deleted file mode 100644
index 5fda0d5..0000000
--- a/src/persistent_service_test.cc
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2023 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 "persistent_service.h"
-
-#include "persistent_service_test_lib.cc"
-#include "test.h"
-#include "util.h"
-
-#ifndef _WIN32
-#include <unistd.h> // For sleep()
-#endif
-
-static std::string kTestServiceName = persistent_service_test::kServiceName;
-
-class PersistentServiceServerTest : public ::testing::Test {
- public:
- PersistentServiceServerTest() : server(kTestServiceName) {}
-
- PersistentService::Server server;
-};
-
-class PersistentServiceClientTest : public ::testing::Test {
- public:
- PersistentServiceClientTest() : client(kTestServiceName) {}
-
- void TearDown() override {
- // Ensure any server process started by the test is stopped.
- if (!client.HasServer())
- return;
-
- std::string err;
- if (!client.StopServer(&err)) {
- fprintf(stderr, "\nERROR: %s", err.c_str());
- AddAssertionFailure();
- } else {
- if (!client.WaitForServerShutdown()) {
- fprintf(stderr, "\nERROR: Could not shutdown server!\n");
- AddAssertionFailure();
- }
- }
- }
-
- PersistentService::Client client;
-};
-
-TEST_F(PersistentServiceServerTest, BindService) {
- std::string error;
- EXPECT_TRUE(server.BindService(&error));
-
- PersistentService::Client client(kTestServiceName);
- EXPECT_TRUE(client.HasServer());
-}
-
-TEST_F(PersistentServiceClientTest, DefaultMode) {
- std::string error;
- auto config = persistent_service_test::GetServerConfig();
- IpcHandle conn = client.Connect(config, &error);
- if (!conn)
- fprintf(stderr, "ERROR [%s]\n", error.c_str());
- ASSERT_TRUE(conn);
-
- EXPECT_TRUE(client.HasServer());
-
- bool success = persistent_service_test::RunTest(conn, false, &error);
- if (!success)
- fprintf(stderr, "ERROR: %s\n", error.c_str());
- ASSERT_TRUE(success);
-
- EXPECT_TRUE(client.HasServer());
-
- success = persistent_service_test::RunTest(conn, true, &error);
- if (!success)
- fprintf(stderr, "ERROR: %s\n", error.c_str());
- ASSERT_TRUE(success);
-
- ASSERT_TRUE(client.WaitForServerShutdown());
- ASSERT_FALSE(client.HasServer());
-}
-
-TEST_F(PersistentServiceClientTest, WithConnectionTimeout) {
- std::string error;
- auto config = persistent_service_test::GetServerConfig();
- persistent_service_test::SetServiceConfigTimeoutMs(config, 100);
- IpcHandle conn = client.Connect(config, &error);
- if (!conn)
- fprintf(stderr, "ERROR [%s]\n", error.c_str());
- ASSERT_TRUE(conn);
-
- bool success = persistent_service_test::RunTest(conn, false, &error);
- if (!success)
- fprintf(stderr, "ERROR: %s\n", error.c_str());
- ASSERT_TRUE(success);
-
- conn.Close();
-
-#ifdef _WIN32
- ::Sleep(1000);
-#else
- ::sleep(1);
-#endif
- EXPECT_FALSE(client.HasServer());
-}
-
-TEST_F(PersistentServiceClientTest, WithDifferentVersion) {
- // First start an instance with the default version.
- std::string error;
- auto config = persistent_service_test::GetServerConfig();
- IpcHandle conn = client.Connect(config, &error);
- ASSERT_TRUE(conn);
- bool success = persistent_service_test::RunTest(conn, false, &error);
- ASSERT_TRUE(success);
- EXPECT_TRUE(client.HasServer());
-
- conn.Close();
-
- persistent_service_test::SetServiceConfigVersion(config, "another");
- conn = client.Connect(config, &error);
- if (!conn)
- fprintf(stderr, "ERROR: %s\n", error.c_str());
- EXPECT_TRUE(conn);
- EXPECT_TRUE(client.HasServer());
-}
diff --git a/src/persistent_service_test_helper.cc b/src/persistent_service_test_helper.cc
deleted file mode 100644
index b1e4eca..0000000
--- a/src/persistent_service_test_helper.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2023 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 <stdio.h>
-#include <stdlib.h>
-
-#include "persistent_service.h"
-#include "persistent_service_test_lib.cc"
-
-void PrintUsage(const char* progname) {
- fprintf(stderr, "Usage: %s [-t<timeout>] [-v<version>]\n", progname);
-}
-
-int main(int argc, char** argv) {
- const char* progname = argv[0];
- PersistentService::Server server(persistent_service_test::kServiceName);
-
- std::string version = persistent_service_test::kDefaultVersion;
-
- for (; argc > 1; --argc, argv++) {
- const char* arg = argv[1];
- if (arg[0] != '-') {
- PrintUsage(progname);
- return 1;
- }
-
- switch (arg[1]) {
- case 't': // -t<value> Server timeout in milliseconds.
- server.SetConnectionTimeoutMs(static_cast<int64_t>(atoll(arg + 2)));
- break;
- case 'v': // -v<version> is server version.
- version = arg + 2;
- break;
- default:
- PrintUsage(progname);
- return 1;
- }
- }
-
- server.RunServerThenExit(
- persistent_service_test::GetVersionCheckHandler(version),
- persistent_service_test::RequestHandler);
-
- return 0;
-}
diff --git a/src/persistent_service_test_lib.cc b/src/persistent_service_test_lib.cc
deleted file mode 100644
index 30735d4..0000000
--- a/src/persistent_service_test_lib.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2023 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 "persistent_service_test_lib.h"
-
-#include <inttypes.h>
-
-#include "ipc_utils.h"
-#include "persistent_service.h"
-#include "util.h"
-
-#define DEBUG 0
-
-// Common macros used to print a prefix then an message to stderr.
-#define STDERR_PRINTF(prefix, ...) \
- do { \
- fputs(prefix, stderr); \
- fprintf(stderr, __VA_ARGS__); \
- fputs("\n", stderr); \
- } while (0)
-
-#if DEBUG
-#define SERVER_LOG(...) STDERR_PRINTF("TEST_SERVER: ", __VA_ARGS__)
-#define CLIENT_LOG(...) STDERR_PRINTF("TEST_CLIENT: ", __VA_ARGS__)
-#else
-#define SERVER_LOG(...) (void)0
-#define CLIENT_LOG(...) (void)0
-#endif
-
-#define SERVER_ERR(...) STDERR_PRINTF("TEST_SERVER: ERROR: ", __VA_ARGS__)
-#define SERVER_WARN(...) STDERR_PRINTF("TEST_SERVER: WARNING: ", __VA_ARGS__)
-
-#define CLIENT_ERR(...) STDERR_PRINTF("TEST_CLIENT: ERROR: ", __VA_ARGS__)
-#define CLIENT_WARN(...) STDERR_PRINTF("TEST_CLIENT: WARNING: ", __VA_ARGS__)
-
-namespace {
-
-std::vector<std::string> GetServerArgs(const std::string* version) {
- std::vector<std::string> result;
- std::string program =
- GetCurrentExecutableDir() + "/persistent_service_test_helper";
-#ifdef _WIN32
- program.append(".exe");
-#endif
- result.push_back(std::move(program));
- if (version)
- result.push_back(*version);
- return result;
-}
-
-// This is necessary because RemoteWrite("ping", ...) will send a 5-byte
-// character sequence (including the terminating zero), instead of a
-// string size + its content.
-bool SendString(const char* str, IpcHandle& con, std::string* err) {
- return RemoteWrite(std::string(str), con, err);
-}
-
-} // namespace
-
-namespace persistent_service_test {
-
-const char kServiceName[] = "persistent-server-test";
-
-const char kDefaultVersion[] = "default-version";
-
-PersistentService::Config GetServerConfig() {
- PersistentService::Config config(GetServerArgs(nullptr));
- return config.SetVersionInfo(kDefaultVersion);
-}
-
-void SetServiceConfigVersion(PersistentService::Config& config,
- const std::string& version) {
- config.SetVersionInfo(version);
- config.command.push_back(StringFormat("-v%s", version.c_str()));
-}
-
-void SetServiceConfigTimeoutMs(PersistentService::Config& config,
- int64_t connection_timeout_ms) {
- config.command.push_back(StringFormat("-t%" PRId64, connection_timeout_ms));
-}
-
-PersistentService::Server::VersionCheckHandler GetVersionCheckHandler(
- const std::string& server_version_info) {
- return [server_version_info](
- const std::string& client_version_info) -> std::string {
- if (client_version_info != server_version_info) {
- return StringFormat("Version mismatch, expected [%s] got [%s]",
- server_version_info.c_str(),
- client_version_info.c_str());
- }
- return {};
- };
-}
-
-bool RequestHandler(IpcHandle connection) {
- std::string error;
- std::string request;
- bool is_first = true;
- while (true) {
- SERVER_LOG("Waiting for request");
- if (!RemoteRead(request, connection, &error)) {
- if (!is_first) {
- SERVER_LOG("Client connection closed");
- return true;
- }
- SERVER_ERR("Could not read request: %s\n", error.c_str());
- return false;
- }
- SERVER_LOG("Received request [%.*s]", (int)request.size(), request.c_str());
-
- is_first = false;
-
- if (request == "kill-server") {
- SERVER_LOG("TEST_SERVER: kill-server command received, exiting");
- return false;
- }
-
- if (request == "ping") {
- SERVER_LOG("TEST_SERVER: ping request received, sending reply");
- if (!SendString("pong", connection, &error)) {
- SERVER_ERR("Could not send reply: %s\n", error.c_str());
- return false;
- }
- SERVER_LOG("pong sent");
- continue;
- }
-
- SERVER_ERR("Unknown request [%s]\n", request.c_str());
- return false;
- }
-}
-
-bool RunTest(IpcHandle& client, bool kill_server, std::string* err) {
- if (!SendString("ping", client, err)) {
- CLIENT_ERR("Could not send test request: %s\n", err->c_str());
- return false;
- }
- std::string reply;
- if (!RemoteRead(reply, client, err)) {
- CLIENT_ERR("Could not receive test reply: %s\n", err->c_str());
- return false;
- }
-
- bool success = (reply == "pong");
- if (!success) {
- CLIENT_ERR("Invalid reply [%s] expected [pong]\n", reply.c_str());
- // Do not return here to ensure the server is killed.
- }
-
- if (kill_server) {
- if (!SendString("kill-server", client, err)) {
- CLIENT_ERR("Could not kill server: %s\n", err->c_str());
- }
-
- PersistentService::Client persistent(kServiceName);
- if (!persistent.WaitForServerShutdown()) {
- CLIENT_WARN("Could not wait for server shutdown\n");
- }
- }
- return success;
-}
-
-} // namespace persistent_service_test
diff --git a/src/persistent_service_test_lib.h b/src/persistent_service_test_lib.h
deleted file mode 100644
index 87431e1..0000000
--- a/src/persistent_service_test_lib.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2023 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_PERSISTENT_SERVICE_TEST_LIB_H_
-#define NINJA_PERSISTENT_SERVICE_TEST_LIB_H_
-
-#include <string>
-#include <vector>
-
-#include "ipc_handle.h"
-
-namespace persistent_service_test {
-
-/// Name of persistent service name for the test.
-extern const char kServiceName[];
-
-/// Default PersistentService::Config::version_info as a C string.
-extern const char kDefaultVersion[];
-
-/// Return the server arguments to pass to PersistentService::Client::Connect()
-/// For this helper, this should only be the name of or path to the executable
-/// itself.
-PersistentService::Config GetServerConfig();
-
-/// Set a non-default service version information.
-/// information for the server.
-void SetServiceConfigVersion(PersistentService::Config& config,
- const std::string& version);
-
-/// Set the server connection timeout in milliseconds.
-void SetServiceConfigTimeoutMs(PersistentService::Config& config,
- int64_t connection_timeout_ms);
-
-/// Retrieve a VersionCheckHandler instance that expects client version info
-/// to match |server_version_info|.
-PersistentService::Server::VersionCheckHandler GetVersionCheckHandler(
- const std::string& server_version_info);
-
-/// Implement the request handler for the test handler.
-bool RequestHandler(IpcHandle connection);
-
-/// Run the test. Return true in case of success. In case of failure,
-/// set |*error| and return false. |client| is the handle returned
-/// PersistentService::CreateClient(). If |kill_server| is true, this also
-/// sends a command to kill the server and wait for its proper shutdown.
-bool RunTest(IpcHandle& client, bool kill_server, std::string* error);
-
-} // namespace persistent_service_test
-
-#endif // NINJA_PERSISTENT_SERVICE_TEST_LIB_H_
diff --git a/src/process_utils.cc b/src/process_utils.cc
deleted file mode 100644
index 81b574f..0000000
--- a/src/process_utils.cc
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2023 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 "process_utils.h"
-
-#include <assert.h>
-
-#include <algorithm>
-
-#include "ipc_utils.h"
-#include "util.h"
-
-///////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////
-/////
-///// ScopedEnvironmentVariable
-/////
-
-ScopedEnvironmentVariable::ScopedEnvironmentVariable(const char* varname,
- const char* value)
- : varname_(varname) {
- const char* env = getenv(varname);
- if (env) {
- has_prev_value_ = true;
- prev_value_ = env;
- }
-#ifdef _WIN32
- // On Windows, setting the value to the empty string removes the variable.
- // The following check is to ensure that UnsetEnv() is always called when
- // trying to set an undefined |varname| to an empty |value|.
- if (value && value[0]) {
-#else
- if (value) {
-#endif
- if (SetEnv(varname_.c_str(), value) < 0)
- ErrnoFatal("setenv");
- } else {
- UnsetEnv(varname_.c_str());
- }
-}
-
-ScopedEnvironmentVariable::~ScopedEnvironmentVariable() {
- if (has_prev_value_) {
- SetEnv(varname_.c_str(), prev_value_.c_str());
- } else {
- UnsetEnv(varname_.c_str());
- }
-}
-
-ScopedEnvironmentVariable::ScopedEnvironmentVariable(
- ScopedEnvironmentVariable&&) noexcept = default;
-ScopedEnvironmentVariable& ScopedEnvironmentVariable::operator=(
- ScopedEnvironmentVariable&&) noexcept = default;
-
-int ScopedEnvironmentVariable::SetEnv(const char* varname, const char* value) {
-#ifdef _WIN32
- // There is no setenv() on Win32!?
- // _putenv_s() returns EINVAL in case of error, but also
- // sets errno, so convert errors to -1 here.
- return (_putenv_s(varname, value) == 0) ? 0 : -1;
-#else // !_WIN32
- return ::setenv(varname, value, 1);
-#endif // !_WIN32
-}
-
-void ScopedEnvironmentVariable::UnsetEnv(const char* varname) {
-#ifdef _WIN32
- _putenv_s(varname, "");
-#else // !_WIN32
- ::unsetenv(varname);
-#endif // !_WIN32
-}
-
-///////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////
-/////
-///// EnvironmentBlock
-/////
-
-EnvironmentBlock::EnvironmentBlock() = default;
-
-extern char** environ;
-
-// static
-EnvironmentBlock EnvironmentBlock::CreateFromCurrentEnvironment() {
- EnvironmentBlock result;
-
- for (const char* const* env = environ; *env; ++env) {
- std::string definition = *env;
- size_t pos = definition.find('=');
- if (pos == std::string::npos) {
- // Should not happen, ignore it.
- continue;
- }
-
- std::string varname = definition.substr(0, pos);
- std::string varvalue = definition.substr(pos + 1);
-
-#ifdef _WIN32
- // Empty values are not possible on Win32, so skip them on this platform
- // if we ever find them in the environ block.
- if (varvalue.empty())
- continue;
-#endif
- result.map_[varname] = varvalue;
- }
- return result;
-}
-
-void EnvironmentBlock::MergeWith(const EnvironmentBlock& other) {
- for (const auto& pair : other.map_)
- map_.emplace(pair.first, pair.second);
-}
-
-void EnvironmentBlock::Insert(const char* varname, const char* value) {
- if (!value)
- value = "";
- map_[std::string(varname)] = value;
-}
-
-void EnvironmentBlock::Remove(const char* varname) {
- map_.erase(std::string(varname));
-}
-
-const char* EnvironmentBlock::Get(const char* varname) const {
- auto it = map_.find(std::string(varname));
- if (it == map_.end())
- return nullptr;
- return it->second.c_str();
-}
-
-std::string EnvironmentBlock::ToEncodedString() const {
- auto definitions = GetDefinitions();
- WireEncoder encoder;
- encoder.Write(map_.size());
- for (const auto& pair : map_) {
- encoder.Write(pair.first);
- encoder.Write(pair.second);
- }
- return encoder.TakeResult();
-}
-
-// static
-EnvironmentBlock EnvironmentBlock::FromEncodedString(const std::string& encoded,
- std::string* error) {
- error->clear();
- EnvironmentBlock::MapType map;
- WireDecoder decoder(encoded);
- size_t count = 0;
- decoder.Read(count);
- for (; count > 0; --count) {
- std::string varname, varvalue;
- decoder.Read(varname);
- decoder.Read(varvalue);
- map.emplace(varname, std::move(varvalue));
- }
- EnvironmentBlock result;
- if (!decoder.has_error()) {
- result.map_ = std::move(map);
- } else {
- *error = "Truncated encoded EnvironmentBlock string";
- }
- return result;
-}
-
-std::string EnvironmentBlock::AsString() const {
- auto defs = GetDefinitions();
- std::string result = StringFormat("Environment(%zu)[\n", defs.list.size());
- for (const auto& def : defs.list)
- result += StringFormat(" %s\n", def.c_str());
- result += "]";
- return result;
-}
-
-bool EnvironmentBlock::operator==(const EnvironmentBlock& other) const {
- auto defs = GetDefinitions();
- auto other_defs = other.GetDefinitions();
- if (defs.total_size != other_defs.total_size)
- return false;
- if (defs.list.size() != other_defs.list.size())
- return false;
- auto other_it = other_defs.list.begin();
- for (const auto& def : defs.list) {
- if (def != *other_it)
- return false;
- ++other_it;
- }
- return true;
-}
-
-EnvironmentBlock::Definitions EnvironmentBlock::GetDefinitions() const {
- Definitions result;
- for (const auto& pair : map_) {
- result.list.push_back(pair.first + "=" + pair.second);
- result.total_size += result.list.back().size() + 1;
- }
- std::sort(result.list.begin(), result.list.end());
- return result;
-}
-
-#ifdef _WIN32
-char* EnvironmentBlock::AsAnsiEnvironmentBlock() const {
- auto definitions = GetDefinitions();
-
- block_.clear();
- block_.reserve(definitions.total_size + 1);
- for (const auto& def : definitions.list) {
- block_.append(def.c_str(), def.size() + 1);
- }
- block_ += '\0';
-
- return &block_[0];
-}
-
-wchar_t* EnvironmentBlock::AsUnicodeEnvironmentBlock() const {
- auto definitions = GetDefinitions();
-
- block_.clear();
- block_.reserve(definitions.total_size * 2 + 2);
- for (const auto& def : definitions.list) {
- std::wstring wdef = ConvertToUnicodeString(def);
- block_.append(reinterpret_cast<const char*>(wdef.c_str()),
- wdef.size() * 2 + 2);
- }
- block_ += '\0';
- block_ += '\0';
-
- return reinterpret_cast<wchar_t*>(&block_[0]);
-}
-
-#else // !_WIN32
-char** EnvironmentBlock::AsExecEnvironmentBlock() const {
- auto definitions = GetDefinitions();
- block_.clear();
- block_.reserve(definitions.total_size);
-
- env_.clear();
- env_.reserve(definitions.list.size() + 1);
- for (const auto& def : definitions.list) {
- char* dst = const_cast<char*>(block_.data()) + block_.size();
- env_.push_back(dst);
- block_.append(def.c_str(), def.size() + 1);
- }
- assert(block_.size() == definitions.total_size);
- env_.push_back(nullptr);
- return &env_.front();
-}
-#endif // !_WIN32
-
-// static
-std::vector<StringPiece> SplitCommaOrSpaceSeparatedList(StringPiece input) {
- std::vector<StringPiece> result;
- const char* p = input.str_;
- const char* end = p + input.len_;
-
- while (p < end) {
- // Find next variable name length.
- const char* q = p;
- while (q < end && *q != ' ' && *q != ',')
- q++;
-
- size_t len = q - p;
- if (len > 0)
- result.emplace_back(p, len);
- if (q == end)
- break;
-
- p = q + 1;
- }
- return result;
-}
diff --git a/src/process_utils.h b/src/process_utils.h
deleted file mode 100644
index 6723363..0000000
--- a/src/process_utils.h
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2023 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_PROCESS_UTILS_H
-#define NINJA_PROCESS_UTILS_H
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <sys/types.h>
-#endif
-
-#include "string_piece.h"
-
-/// Convenience class to temporarily set the value of an environment variable
-/// in the current process.
-class ScopedEnvironmentVariable {
- public:
- /// Constructor sets the value of a given environment variable.
- /// Set |value| to nullptr to remove the variable from the environment.
- /// Note that on Windows, setting |value| to an empty string will also
- /// remove the value, while it will set it to the empty string on Posix.
- ScopedEnvironmentVariable(const char* varname, const char* value);
-
- /// Destructor restores the previous value.
- ~ScopedEnvironmentVariable();
-
- /// Allow move operations.
- ScopedEnvironmentVariable(ScopedEnvironmentVariable&&) noexcept;
- ScopedEnvironmentVariable& operator=(ScopedEnvironmentVariable&&) noexcept;
-
- private:
- int SetEnv(const char* varname, const char* value);
- void UnsetEnv(const char* varname);
-
- std::string varname_;
- std::string prev_value_;
-
- // On Posix, it is possible to set an environment variable to the empty value
- // while on Windows, this simply unsets / removes the variable. This flag is
- // only used on Posix to distinguish between these two cases.
- bool has_prev_value_ = false;
-};
-
-/// A convenience wrapper for a set of environment variables.
-class EnvironmentBlock {
- public:
- /// Default constructor creates an empty set.
- EnvironmentBlock();
-
- /// Create new instance from current process environment.
- static EnvironmentBlock CreateFromCurrentEnvironment();
-
- /// Merge with another instance. This only gets variables from |other|
- /// which were not set
- void MergeWith(const EnvironmentBlock& other);
-
- /// Add a given variable to the set, with its associated value.
- /// If the variable is already in the set, replace its value.
- void Insert(const char* varname, const char* value);
-
- /// Remove |varname| and its associated value from the set.
- void Remove(const char* varname);
-
- /// Return the value associated with |varname| in the set, or nullptr
- /// if it is not in it.
- const char* Get(const char* varname) const;
-
- /// Convert this instance to an encoded string, useful to send it
- /// to another process.
- std::string ToEncodedString() const;
-
- /// Decode a new instance from an encoded string. In case of
- /// failure, return an empty environment block, then set |*err|
- /// with an error message. On success |*err| is cleared to ease
- /// error handling on the client side.
- static EnvironmentBlock FromEncodedString(const std::string& encoded,
- std::string* err);
-
- /// Convert instance to string for debugging and testing only.
- std::string AsString() const;
-
- /// Compare for equality.
- bool operator==(const EnvironmentBlock& other) const;
-
- /// Compare for inequality.
- bool operator!=(const EnvironmentBlock& other) const {
- return !(*this == other);
- }
-
-#ifdef _WIN32
- /// Return the current instance as a Win32 environment block, which is
- /// a sequence of zero-terminated C strings, followed by a single zero
- /// (as in `FOO=foo<0>BAR=bar<0><0>`, this is the value expected by
- /// the `lpEnvironment` parameter of CreateProcessA.
- ///
- /// NOTE: The returned address belongs to the instance, but its content
- /// may become invalid after calling any other methods.
- char* AsAnsiEnvironmentBlock() const;
-
- /// Same as AsWin32AnsiEnvironmentBlock(), but uses Unicode characters
- /// instead. This is the format expected by CreateProcessW.
- ///
- /// NOTE: The returned address belongs to the instance, but its content
- /// may become invalid after calling any other methods.
- wchar_t* AsUnicodeEnvironmentBlock() const;
-#else // !_WIN32
- /// A pointer to an array of zero-terminated C string pointers, followed
- /// by a NULL pointer. This is the value expected by the execve() function.
- ///
- /// NOTE: The returned address belongs to the instance, but its content
- /// may become invalid after calling any other methods.
- char** AsExecEnvironmentBlock() const;
-#endif // !_WIN32
-
- private:
- struct Definitions {
- std::vector<std::string> list;
- size_t total_size = 0;
- };
- Definitions GetDefinitions() const;
-
- using MapType = std::unordered_map<std::string, std::string>;
- MapType map_;
- mutable std::string block_;
-#ifndef _WIN32
- mutable std::vector<char*> env_;
-#endif // !_WIN32
-};
-
-/// Split an input list separated by commas or spaces into separate items.
-/// Trailing, leading or duplicate separators are ignored.
-std::vector<StringPiece> SplitCommaOrSpaceSeparatedList(StringPiece list);
-
-#endif // NINJA_PROCESS_UTILS_H
diff --git a/src/process_utils_test.cc b/src/process_utils_test.cc
deleted file mode 100644
index 54ae84e..0000000
--- a/src/process_utils_test.cc
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2023 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 "process_utils.h"
-
-#include <stdlib.h>
-
-#include <string>
-
-#include "test.h"
-
-TEST(ProcessUtils, ScopedEnvironmentVariable) {
- const char kVarName[] = "FOO_FOR_TEST";
- const char* prev_value = getenv(kVarName);
- ASSERT_FALSE(prev_value);
-
- {
- ScopedEnvironmentVariable scoped_var(kVarName, "");
-#ifdef _WIN32
- // On Windows, the variable was forcefully removed.
- ASSERT_FALSE(getenv(kVarName));
-#else
- // On Posix, the variable was set to the empty string.
- ASSERT_TRUE(getenv(kVarName));
- EXPECT_EQ(std::string(""), getenv(kVarName));
-#endif
- }
-
- {
- ScopedEnvironmentVariable scoped_var(kVarName, "value1");
- EXPECT_EQ(std::string("value1"), getenv(kVarName));
-
- {
- ScopedEnvironmentVariable scoped_var2(kVarName, "VALUE2");
- EXPECT_EQ(std::string("VALUE2"), getenv(kVarName));
- }
-
- {
- ScopedEnvironmentVariable scoped_var2(kVarName, "");
-#ifdef _WIN32
- ASSERT_FALSE(getenv(kVarName));
-#else
- ASSERT_TRUE(getenv(kVarName));
- EXPECT_EQ(std::string(""), getenv(kVarName));
-#endif
- }
-
- EXPECT_EQ(std::string("value1"), getenv(kVarName));
- }
-
- ASSERT_FALSE(getenv(kVarName));
-}
-
-TEST(ProcessUtils, EnvironmentBlockDefault) {
- EnvironmentBlock empty_block;
-#ifdef _WIN32
- char* b = empty_block.AsAnsiEnvironmentBlock();
- ASSERT_EQ(*b, '\0');
- ASSERT_EQ(empty_block, empty_block);
-
- wchar_t* wb = empty_block.AsUnicodeEnvironmentBlock();
- ASSERT_EQ(*wb, L'\0');
-#else // !_WIN32
- char** vec = empty_block.AsExecEnvironmentBlock();
- ASSERT_FALSE(vec[0]);
-#endif // !_WIN32
-
- EXPECT_EQ(empty_block.AsString(), "Environment(0)[\n]");
-}
-
-TEST(ProcessUtils, EnvironmentBlockFromCurrentEnvironment) {
- const char kDefinedVarName[] = "DEFINED_VARIABLE_FOR_NINJA_TEST";
- const char kUndefinedVarName[] = "UNDEFINED_VARIABLE_FOR_NINJA_TEST";
- ASSERT_FALSE(getenv(kDefinedVarName));
- ASSERT_FALSE(getenv(kUndefinedVarName));
-
- {
- ScopedEnvironmentVariable scoped_var(kDefinedVarName, "");
- EnvironmentBlock block = EnvironmentBlock::CreateFromCurrentEnvironment();
- EXPECT_FALSE(block.Get(kUndefinedVarName));
-#ifdef _WIN32
- ASSERT_FALSE(block.Get(kDefinedVarName));
-#else
- ASSERT_TRUE(block.Get(kDefinedVarName));
- EXPECT_EQ('\0', block.Get(kDefinedVarName)[0]);
-#endif
- }
-
- {
- EnvironmentBlock block = EnvironmentBlock::CreateFromCurrentEnvironment();
- EXPECT_FALSE(block.Get(kUndefinedVarName));
- EXPECT_FALSE(block.Get(kDefinedVarName));
- }
-}
-
-TEST(ProcessUtils, EnvironmentBlockMergeWith) {
- EnvironmentBlock block;
- block.Insert("foo", "FOO");
- block.Insert("bar", "BAR");
- block.Insert("qux", "");
- EXPECT_EQ(block.AsString(),
- "Environment(3)[\n bar=BAR\n foo=FOO\n qux=\n]");
-
- EXPECT_EQ(block, block);
-
- EnvironmentBlock block2;
- block2.Insert("foo", "NOT_FOO");
- block2.Insert("qux", "QUX");
- block2.Insert("zoo", "ZOO");
- EXPECT_EQ(block2.AsString(),
- "Environment(3)[\n foo=NOT_FOO\n qux=QUX\n zoo=ZOO\n]");
-
- EXPECT_EQ(block, block);
- EXPECT_NE(block, block2);
-
- block.MergeWith(block2);
- EXPECT_EQ(block.Get("foo"), std::string("FOO"));
- EXPECT_EQ(block.Get("bar"), std::string("BAR"));
- EXPECT_EQ(block.Get("qux"), std::string(""));
- ASSERT_TRUE(block.Get("zoo"));
- EXPECT_EQ(block.Get("zoo"), std::string("ZOO"));
-
- EXPECT_EQ(block.AsString(),
- "Environment(4)[\n bar=BAR\n foo=FOO\n qux=\n zoo=ZOO\n]");
-}
-
-#ifdef _WIN32
-
-TEST(ProcessUtils, EnvironmentBlockAsAnsiEnvironmentBlock) {
- EnvironmentBlock block;
- block.Insert("foo", "FOO");
- block.Insert("bar", "BARBAR");
- block.Insert("zoo", "");
-
- ASSERT_TRUE(block.Get("foo"));
- EXPECT_EQ(std::string("FOO"), block.Get("foo"));
- EXPECT_EQ(std::string("BARBAR"), block.Get("bar"));
- EXPECT_EQ(std::string(""), block.Get("zoo"));
-
- char* b = block.AsAnsiEnvironmentBlock();
- ASSERT_FALSE(::memcmp(b, "bar=BARBAR\0foo=FOO\0zoo=\0\0", 25));
-}
-
-TEST(ProcessUtils, EnvironmentBlockAsUnicodeEnvironmentBlock) {
- EnvironmentBlock block;
- block.Insert("foo", "FOO");
- block.Insert("bar", "BARBAR");
- block.Insert("zoo", "");
-
- ASSERT_TRUE(block.Get("foo"));
- EXPECT_EQ(std::string("FOO"), block.Get("foo"));
- EXPECT_EQ(std::string("BARBAR"), block.Get("bar"));
- EXPECT_EQ(std::string(""), block.Get("zoo"));
-
- wchar_t* wb = block.AsUnicodeEnvironmentBlock();
- ASSERT_FALSE(::wmemcmp(wb, L"bar=BARBAR\0foo=FOO\0zoo=\0\0", 25));
-}
-#else // !_WIN32
-TEST(ProcessUtils, EnvironmentBlockAsExecEnvironmentBlock) {
- EnvironmentBlock block;
- block.Insert("foo", "FOO");
- block.Insert("bar", "BARBAR");
- block.Insert("zoo", "");
-
- ASSERT_TRUE(block.Get("foo"));
- EXPECT_EQ(std::string("FOO"), block.Get("foo"));
- EXPECT_EQ(std::string("BARBAR"), block.Get("bar"));
- EXPECT_EQ(std::string(""), block.Get("zoo"));
-
- char** vec = block.AsExecEnvironmentBlock();
- ASSERT_TRUE(vec[0]);
- ASSERT_TRUE(vec[1]);
- ASSERT_TRUE(vec[2]);
- ASSERT_FALSE(vec[3]);
- EXPECT_EQ(std::string(vec[0]), "bar=BARBAR");
- EXPECT_EQ(std::string(vec[1]), "foo=FOO");
- EXPECT_EQ(std::string(vec[2]), "zoo=");
-}
-#endif // !_WIN32
-
-TEST(ProcessUtils, EnvironmentBlockToEncodedString) {
- EnvironmentBlock block;
- block.Insert("foo", "FOO");
- block.Insert("bar", "BARBAR");
- block.Insert("zoo", "");
- EXPECT_EQ(block.AsString(),
- "Environment(3)[\n bar=BARBAR\n foo=FOO\n zoo=\n]");
-
- std::string encoded = block.ToEncodedString();
- ASSERT_FALSE(encoded.empty());
-
- std::string error;
- auto block2 = EnvironmentBlock::FromEncodedString(encoded, &error);
- EXPECT_TRUE(error.empty());
- EXPECT_EQ(block.AsString(), block2.AsString());
-
- // Detect errors and return empty instance when decoding truncated string.
- auto block3 =
- EnvironmentBlock::FromEncodedString(encoded.substr(0, 10), &error);
- EXPECT_FALSE(error.empty());
- EXPECT_EQ(block3.AsString(), "Environment(0)[\n]");
-}
-
-TEST(ProcessUtils, SplitCommaOrSpaceSeparatedList) {
- std::vector<StringPiece> result;
-
- // Verify empty inputs.
- result = SplitCommaOrSpaceSeparatedList("");
- EXPECT_EQ(result.size(), 0u);
-
- result = SplitCommaOrSpaceSeparatedList(" ");
- EXPECT_EQ(result.size(), 0u);
-
- result = SplitCommaOrSpaceSeparatedList(" ,,, ");
- EXPECT_EQ(result.size(), 0u);
-
- result = SplitCommaOrSpaceSeparatedList(" foo,");
- EXPECT_EQ(result.size(), 1u);
- EXPECT_EQ(result[0].AsString(), "foo");
-
- result = SplitCommaOrSpaceSeparatedList("foo bar zoo");
- EXPECT_EQ(result.size(), 3u);
- EXPECT_EQ(result[0].AsString(), "foo");
- EXPECT_EQ(result[1].AsString(), "bar");
- EXPECT_EQ(result[2].AsString(), "zoo");
-
- result = SplitCommaOrSpaceSeparatedList(" foo,,bar zoo,");
- EXPECT_EQ(result.size(), 3u);
- EXPECT_EQ(result[0].AsString(), "foo");
- EXPECT_EQ(result[1].AsString(), "bar");
- EXPECT_EQ(result[2].AsString(), "zoo");
-}
diff --git a/src/stat_cache-posix.cc b/src/stat_cache-posix.cc
deleted file mode 100644
index 2855df3..0000000
--- a/src/stat_cache-posix.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2023 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 <errno.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "stat_cache.h"
-#include "util.h"
-
-// This implementation calls stat() directly and never caches anything.
-class StatCache::Impl {
- public:
- void Enable(bool) {}
-
- TimeStamp Stat(const std::string& path, std::string* err) const {
- return ::GetFileTimestamp(path, err);
- }
-};
-
-StatCache::StatCache() : impl_(new StatCache::Impl()) {}
-
-StatCache::~StatCache() = default;
-
-void StatCache::Enable(bool enabled) {
- impl_->Enable(enabled);
-}
-
-void StatCache::Invalidate(const std::string&) {
- // Nothing to do here.
-}
-
-TimeStamp StatCache::Stat(const std::string& path, std::string* err) const {
- return impl_->Stat(path, err);
-}
-
-void StatCache::Sync() {}
-
-void StatCache::Flush() {}
diff --git a/src/stat_cache-win32.cc b/src/stat_cache-win32.cc
deleted file mode 100644
index 87bc053..0000000
--- a/src/stat_cache-win32.cc
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2023 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 <windows.h>
-
-#include <algorithm>
-#include <map>
-
-#include "stat_cache.h"
-#include "util.h"
-
-namespace {
-
-bool IsPathSeparator(char ch) {
- return (ch == '/' || ch == '\\');
-}
-
-// Compute the dirname and basename of a given path.
-// If there is no directory separator, set |*dir| to ".".
-void DecomposePath(const std::string& path, std::string* dir,
- std::string* base) {
- size_t pos = path.size();
-
- // Find first directory separator before base name.
- while (pos > 0 && !IsPathSeparator(path[pos - 1]))
- --pos;
-
- *base = path.substr(pos);
-
- // Skip over separator(s) to find directory name, might be empty
- while (pos > 1 && IsPathSeparator(path[pos - 2]))
- --pos;
-
- *dir = path.substr(0, pos);
-}
-
-bool IsWindows7OrLater() {
- OSVERSIONINFOEX version_info = {
- sizeof(OSVERSIONINFOEX), 6, 1, 0, 0, { 0 }, 0, 0, 0, 0, 0
- };
- DWORDLONG comparison = 0;
- VER_SET_CONDITION(comparison, VER_MAJORVERSION, VER_GREATER_EQUAL);
- VER_SET_CONDITION(comparison, VER_MINORVERSION, VER_GREATER_EQUAL);
- return VerifyVersionInfo(&version_info, VER_MAJORVERSION | VER_MINORVERSION,
- comparison);
-}
-
-using DirCache = std::map<std::string, TimeStamp>;
-
-// TODO: Neither a map nor a hashmap seems ideal here. If the statcache
-// works out, come up with a better data structure.
-using Cache = std::map<std::string, DirCache>;
-
-bool StatAllFilesInDir(const std::string& dir, DirCache* stamps,
- std::string* err) {
- // FindExInfoBasic is 30% faster than FindExInfoStandard.
- static bool can_use_basic_info = IsWindows7OrLater();
- // This is not in earlier SDKs.
- const FINDEX_INFO_LEVELS kFindExInfoBasic =
- static_cast<FINDEX_INFO_LEVELS>(1);
- FINDEX_INFO_LEVELS level =
- can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard;
- WIN32_FIND_DATAA ffd;
- HANDLE find_handle = FindFirstFileExA((dir + "\\*").c_str(), level, &ffd,
- FindExSearchNameMatch, NULL, 0);
-
- if (find_handle == INVALID_HANDLE_VALUE) {
- DWORD win_err = GetLastError();
- if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND ||
- win_err == ERROR_DIRECTORY)
- return true;
- *err = StringFormat("FindFirstFileExA(%s): %s", dir.c_str(),
- GetLastErrorString().c_str());
- return false;
- }
- do {
- std::string lowername = ffd.cFileName;
- if (lowername == "..") {
- // Seems to just copy the timestamp for ".." from ".", which is wrong.
- // This is the case at least on NTFS under Windows 7.
- continue;
- }
- std::transform(lowername.begin(), lowername.end(), lowername.begin(),
- ::tolower);
- // C++11 equivalent to
- // stamps->try_emplace(std::move(lowername), TimeStampFromFile(...))
- stamps->emplace(
- std::piecewise_construct, std::forward_as_tuple(std::move(lowername)),
- std::forward_as_tuple(TimeStampFromFileTime(ffd.ftLastWriteTime)));
- } while (FindNextFileA(find_handle, &ffd));
- FindClose(find_handle);
- return true;
-}
-
-} // namespace
-
-class StatCache::Impl {
- public:
- void Enable(bool enabled) {
- if (!enabled)
- cache_.clear();
-
- enabled_ = enabled;
- }
-
- TimeStamp Stat(const std::string& path, std::string* err) const {
- // MSDN: "Naming Files, Paths, and Namespaces"
- // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
- if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) {
- *err = StringFormat("Stat(%s): Filename longer than %u characters",
- path.c_str(), MAX_PATH);
- return -1;
- }
- if (!enabled_)
- return ::GetFileTimestamp(path, err);
-
- std::string dir, base;
- DecomposePath(path, &dir, &base);
- if (base == "..") {
- // StatAllFilesInDir does not report any information for base = "..".
- base = ".";
- dir = path;
- }
-
- std::string dir_lowercase = dir;
- std::transform(dir.begin(), dir.end(), dir_lowercase.begin(), ::tolower);
- std::transform(base.begin(), base.end(), base.begin(), ::tolower);
-
- // NOTE: The following is the C++11 equivalent of
- // cache_.try_emplace(std::move(dir_lowercase))
- auto ret = cache_.emplace(std::piecewise_construct,
- std::forward_as_tuple(std::move(dir_lowercase)),
- std::forward_as_tuple());
- Cache::iterator ci = ret.first;
- if (ret.second) {
- if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) {
- cache_.erase(ci);
- return -1;
- }
- }
- DirCache::iterator di = ci->second.find(base);
- return di != ci->second.end() ? di->second : 0;
- }
-
- void Sync() {
- // TODO(digit): Implement this properly!
- // FOR NOW, drop everything from the cache to pass the unit-tests!
- cache_.clear();
- }
-
- void Flush() { cache_.clear(); }
-
- private:
- /// Whether stat information can be cached.
- bool enabled_ = false;
-
- /// The cache itself.
- mutable Cache cache_;
-};
-
-StatCache::StatCache() : impl_(new StatCache::Impl()) {}
-
-StatCache::~StatCache() = default;
-
-void StatCache::Enable(bool enabled) {
- impl_->Enable(enabled);
-}
-
-TimeStamp StatCache::Stat(const std::string& path, std::string* err) const {
- return impl_->Stat(path, err);
-}
-
-void StatCache::Invalidate(const std::string& path) {
- // TODO(digit): Only remove entries from the path's parent directory.
- impl_->Sync();
-}
-
-void StatCache::Sync() {
- impl_->Sync();
-}
-
-void StatCache::Flush() {
- impl_->Flush();
-}
diff --git a/src/stat_cache.h b/src/stat_cache.h
deleted file mode 100644
index 7d6322d..0000000
--- a/src/stat_cache.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2023 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_STAT_CACHE_H_
-#define NINJA_STAT_CACHE_H_
-
-#include <memory>
-#include <string>
-
-#include "timestamp.h"
-
-/// Implement a cache for file path timestamps.
-class StatCache {
- public:
- /// Constructor
- StatCache();
-
- /// Destructor
- ~StatCache();
-
- /// Enable caching of timestamps. If false (the default), the Stat() performs
- /// a single stat() operation per file and no caching is performed.
- void Enable(bool enabled);
-
- /// Mark a path as invalid.
- void Invalidate(const std::string& path);
-
- /// Return the timestamp of a given file path. On error, set |*err| then
- /// return -1. Otherwise, return 0 if the file is missing, or a strictly
- /// positive value if it exists.
- TimeStamp Stat(const std::string& path, std::string* err) const;
-
- /// Synchronize the cache state with recent filesystem events.
- /// Call this before one or more Stat() calls.
- void Sync();
-
- /// Remove all entries from the cache.
- void Flush();
-
- private:
- class Impl;
- std::unique_ptr<Impl> impl_;
-};
-
-#endif // NINJA_STAT_CACHE_H_
diff --git a/src/stat_cache_test.cc b/src/stat_cache_test.cc
deleted file mode 100644
index f6b8455..0000000
--- a/src/stat_cache_test.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2023 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 "stat_cache.h"
-
-#include <stdio.h>
-
-#include "test.h"
-#include "util.h"
-
-namespace {
-
-void WriteFile(const std::string& path, const std::string& content) {
- FILE* f = fopen(path.c_str(), "wb");
- if (!f)
- ErrnoFatal("fopen", path.c_str());
-
- size_t ret = fwrite(content.c_str(), content.size(), 1, f);
- if (ret != 1)
- ErrnoFatal("Could not write to %s", path.c_str());
- fclose(f);
-}
-
-void RemoveFile(const std::string& path) {
- ::remove(path.c_str());
-}
-
-} // namespace
-
-TEST(StatCache, CheckMissingTimestamp) {
- ScopedTempDir temp_dir;
- temp_dir.CreateAndEnter("StatCacheTest");
- auto top_dir = GetCurrentDir();
-
- StatCache cache;
- cache.Enable(true);
-
- std::string err;
- TimeStamp t = cache.Stat(top_dir + "/foo", &err);
- EXPECT_EQ(0, t);
- EXPECT_TRUE(err.empty());
-
- t = cache.Stat(top_dir + "/bar", &err);
- EXPECT_EQ(0, t);
- EXPECT_TRUE(err.empty());
-
- // Try foo again, just in case.
- t = cache.Stat(top_dir + "/foo", &err);
- EXPECT_EQ(0, t);
- EXPECT_TRUE(err.empty());
-}
-
-TEST(StatCache, CheckExistingTimestamp) {
- ScopedTempDir temp_dir;
- temp_dir.CreateAndEnter("StatCacheTest");
- auto top_dir = GetCurrentDir();
-
- StatCache cache;
- cache.Enable(true);
-
- std::string foo_path = top_dir + "/foo";
-
- WriteFile(foo_path, "foo!!");
-
- std::string err;
- TimeStamp t = cache.Stat(foo_path, &err);
- EXPECT_GE(t, 0);
- EXPECT_TRUE(err.empty());
-}
-
-TEST(StatCache, CheckCreatedTimestamp) {
- ScopedTempDir temp_dir;
- temp_dir.CreateAndEnter("StatCacheTest");
- auto top_dir = GetCurrentDir();
-
- StatCache cache;
- cache.Enable(true);
-
- std::string foo_path = top_dir + "/foo";
-
- std::string err;
- TimeStamp t = cache.Stat(foo_path, &err);
- EXPECT_EQ(0, t);
- EXPECT_TRUE(err.empty());
-
- WriteFile(foo_path, "foo!!");
- cache.Sync();
-
- TimeStamp t2 = cache.Stat(foo_path, &err);
- EXPECT_NE(0, t2);
- EXPECT_TRUE(err.empty());
-}
-
-TEST(StatCache, CheckDeletedTimestamp) {
- ScopedTempDir temp_dir;
- temp_dir.CreateAndEnter("StatCacheTest");
- auto top_dir = GetCurrentDir();
-
- StatCache cache;
- cache.Enable(true);
-
- std::string foo_path = top_dir + "/foo";
- WriteFile(foo_path, "foo!!");
-
- std::string err;
- TimeStamp t = cache.Stat(foo_path, &err);
- EXPECT_NE(0, t);
- EXPECT_TRUE(err.empty());
-
- RemoveFile(foo_path);
-
- cache.Sync();
-
- TimeStamp t2 = cache.Stat(foo_path, &err);
- EXPECT_EQ(0, t2);
- EXPECT_TRUE(err.empty());
-}
diff --git a/src/state.cc b/src/state.cc
deleted file mode 100644
index e163599..0000000
--- a/src/state.cc
+++ /dev/null
@@ -1,238 +0,0 @@
-// 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.
-
-#include "state.h"
-
-#include <assert.h>
-#include <stdio.h>
-
-#include "edit_distance.h"
-#include "graph.h"
-#include "metrics.h"
-#include "util.h"
-
-using namespace std;
-
-void Pool::EdgeScheduled(const Edge& edge) {
- if (depth_ != 0)
- current_use_ += edge.weight();
-}
-
-void Pool::EdgeFinished(const Edge& edge) {
- if (depth_ != 0)
- current_use_ -= edge.weight();
-}
-
-void Pool::DelayEdge(Edge* edge) {
- assert(depth_ != 0);
- delayed_.insert(edge);
-}
-
-void Pool::RetrieveReadyEdges(EdgeSet* ready_queue) {
- DelayedEdges::iterator it = delayed_.begin();
- while (it != delayed_.end()) {
- Edge* edge = *it;
- if (current_use_ + edge->weight() > depth_)
- break;
- ready_queue->insert(edge);
- EdgeScheduled(*edge);
- ++it;
- }
- delayed_.erase(delayed_.begin(), it);
-}
-
-void Pool::Dump() const {
- printf("%s (%d/%d) ->\n", name_.c_str(), current_use_, depth_);
- for (DelayedEdges::const_iterator it = delayed_.begin();
- it != delayed_.end(); ++it)
- {
- printf("\t");
- (*it)->Dump();
- }
-}
-
-void Pool::Reset() {
- current_use_ = 0;
- delayed_.clear();
-}
-
-State::State() {
- // Create top-level scope.
- bindings_.emplace_back(new BindingEnv());
-
- // Add the phony rule to it.
- phony_rule_ = new Rule("phony");
- bindings_[0]->AddRule(phony_rule_);
-
- // Create default and console pools.
- default_pool_ = new Pool("", 0);
- console_pool_ = new Pool("console", 1);
- AddPool(default_pool_);
- AddPool(console_pool_);
-}
-
-State::~State() {
- // Delete Edge instances.
- for (Edge* edge : edges_)
- delete edge;
-
- // Delete Node instances.
- for (auto& pair : paths_)
- delete pair.second;
-}
-
-void State::AddPool(Pool* pool) {
- auto ret = pools_.emplace(pool->name(), pool);
- assert(ret.second && "Duplicate pool insertion!");
-}
-
-Pool* State::LookupPool(const string& pool_name) {
- auto it = pools_.find(pool_name);
- if (it == pools_.end())
- return NULL;
- return it->second.get();
-}
-
-Edge* State::AddEdge(const Rule* rule) {
- Edge* edge = new Edge();
- edge->rule_ = rule;
- edge->pool_ = default_pool_;
- edge->env_ = bindings_[0].get();
- edge->id_ = edges_.size();
- edges_.push_back(edge);
- return edge;
-}
-
-Node* State::GetNode(StringPiece path, uint64_t slash_bits) {
- Node* node = LookupNode(path);
- if (node)
- return node;
- node = new Node(path.AsString(), slash_bits);
- paths_[node->path()] = node;
- return node;
-}
-
-Node* State::LookupNode(StringPiece path) const {
- Paths::const_iterator i = paths_.find(path);
- if (i != paths_.end())
- return i->second;
- return NULL;
-}
-
-Node* State::SpellcheckNode(const string& path) {
- const bool kAllowReplacements = true;
- const int kMaxValidEditDistance = 3;
-
- int min_distance = kMaxValidEditDistance + 1;
- Node* result = NULL;
- for (Paths::iterator i = paths_.begin(); i != paths_.end(); ++i) {
- int distance = EditDistance(
- i->first, path, kAllowReplacements, kMaxValidEditDistance);
- if (distance < min_distance && i->second) {
- min_distance = distance;
- result = i->second;
- }
- }
- return result;
-}
-
-void State::AddIn(Edge* edge, StringPiece path, uint64_t slash_bits) {
- Node* node = GetNode(path, slash_bits);
- node->set_generated_by_dep_loader(false);
- edge->inputs_.push_back(node);
- node->AddOutEdge(edge);
-}
-
-bool State::AddOut(Edge* edge, StringPiece path, uint64_t slash_bits) {
- Node* node = GetNode(path, slash_bits);
- if (node->in_edge())
- return false;
- edge->outputs_.push_back(node);
- node->set_in_edge(edge);
- node->set_generated_by_dep_loader(false);
- return true;
-}
-
-void State::AddValidation(Edge* edge, StringPiece path, uint64_t slash_bits) {
- Node* node = GetNode(path, slash_bits);
- edge->validations_.push_back(node);
- node->AddValidationOutEdge(edge);
- node->set_generated_by_dep_loader(false);
-}
-
-bool State::AddDefault(StringPiece path, string* err) {
- Node* node = LookupNode(path);
- if (!node) {
- *err = "unknown target '" + path.AsString() + "'";
- return false;
- }
- defaults_.push_back(node);
- return true;
-}
-
-vector<Node*> State::RootNodes(string* err) const {
- vector<Node*> root_nodes;
- // Search for nodes with no output.
- for (vector<Edge*>::const_iterator e = edges_.begin();
- e != edges_.end(); ++e) {
- for (vector<Node*>::const_iterator out = (*e)->outputs_.begin();
- out != (*e)->outputs_.end(); ++out) {
- if ((*out)->out_edges().empty())
- root_nodes.push_back(*out);
- }
- }
-
- if (!edges_.empty() && root_nodes.empty())
- *err = "could not determine root nodes of build graph";
-
- return root_nodes;
-}
-
-vector<Node*> State::DefaultNodes(string* err) const {
- return defaults_.empty() ? RootNodes(err) : defaults_;
-}
-
-BindingEnv* State::CreateNewEnv(BindingEnv* parent) {
- bindings_.emplace_back(new BindingEnv(parent));
- return bindings_.back().get();
-}
-
-void State::Reset() {
- METRIC_RECORD("state reset");
- for (auto& pair : pools_)
- pair.second->Reset();
- for (auto& pair : paths_)
- pair.second->ResetState();
- for (Edge* edge : edges_)
- edge->ResetState();
-}
-
-void State::Dump() {
- for (Paths::iterator i = paths_.begin(); i != paths_.end(); ++i) {
- Node* node = i->second;
- printf("%s %s [id:%d]\n",
- node->path().c_str(),
- node->status_known() ? (node->dirty() ? "dirty" : "clean")
- : "unknown",
- node->id());
- }
- if (!pools_.empty()) {
- printf("resource_pools:\n");
- for (const auto& pair : pools_) {
- if (!pair.second->name().empty()) {
- pair.second->Dump();
- }
- }
- }
-}
diff --git a/src/state.h b/src/state.h
deleted file mode 100644
index f965805..0000000
--- a/src/state.h
+++ /dev/null
@@ -1,190 +0,0 @@
-// 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.
-
-#ifndef NINJA_STATE_H_
-#define NINJA_STATE_H_
-
-#include <map>
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "eval_env.h"
-#include "graph.h"
-#include "hash_map.h"
-#include "util.h"
-
-struct Edge;
-struct Node;
-struct Rule;
-
-/// A pool for delayed edges.
-/// Pools are scoped to a State. Edges within a State will share Pools. A Pool
-/// will keep a count of the total 'weight' of the currently scheduled edges. If
-/// a Plan attempts to schedule an Edge which would cause the total weight to
-/// exceed the depth of the Pool, the Pool will enqueue the Edge instead of
-/// allowing the Plan to schedule it. The Pool will relinquish queued Edges when
-/// the total scheduled weight diminishes enough (i.e. when a scheduled edge
-/// completes).
-struct Pool {
- Pool(const std::string& name, int depth)
- : name_(name), current_use_(0), depth_(depth),
- is_console_(name == "console"), delayed_() {}
-
- /// A depth of 0 is infinite
- bool is_valid() const { return depth_ >= 0; }
-
- /// Return true if this is the console pool.
- bool is_console() const { return is_console_; }
-
- int depth() const { return depth_; }
- const std::string& name() const { return name_; }
- int current_use() const { return current_use_; }
-
- /// true if the Pool might delay this edge
- bool ShouldDelayEdge() const { return depth_ != 0; }
-
- /// informs this Pool that the given edge is committed to be run.
- /// Pool will count this edge as using resources from this pool.
- void EdgeScheduled(const Edge& edge);
-
- /// informs this Pool that the given edge is no longer runnable, and should
- /// relinquish its resources back to the pool
- void EdgeFinished(const Edge& edge);
-
- /// adds the given edge to this Pool to be delayed.
- void DelayEdge(Edge* edge);
-
- /// Pool will add zero or more edges to the ready_queue
- void RetrieveReadyEdges(EdgeSet* ready_queue);
-
- /// Dump the Pool and its edges (useful for debugging).
- void Dump() const;
-
- /// Reset state.
- void Reset();
-
- private:
- 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_;
- bool is_console_;
-
- 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 std::set<Edge*, WeightedEdgeCmp> DelayedEdges;
- DelayedEdges delayed_;
-};
-
-/// Global state (file status) for a single run.
-struct State {
- State();
-
- ~State();
-
- State(State&&) noexcept = default;
- State& operator=(State&&) noexcept = default;
-
- State(const State&) = delete;
- State& operator=(const State&) = delete;
-
- /// Add pool instance, takes ownership.
- void AddPool(Pool* pool);
-
- Pool* LookupPool(const std::string& pool_name);
-
- /// Return pointer to the default pool. This one is always created implicitly.
- Pool* default_pool() const;
-
- /// Return pointer to the console_pool(). This one is always created
- /// implicitly.
- Pool* console_pool() const;
-
- /// Return pointer to phony rule. This one is always created implicitly.
- const Rule* phony_rule() const { return phony_rule_; }
-
- /// Allocate new Rule instance.
- Rule* NewRule(StringPiece name);
-
- Edge* AddEdge(const Rule* rule);
-
- Node* GetNode(StringPiece path, uint64_t slash_bits);
- Node* LookupNode(StringPiece path) const;
- Node* SpellcheckNode(const std::string& path);
-
- /// Add input / output / validation nodes to a given edge. This also
- /// ensure that the generated_by_dep_loader() flag for all these nodes
- /// is set to false, to indicate that they come from the input manifest.
- void AddIn(Edge* edge, StringPiece path, uint64_t slash_bits);
- bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits);
- void AddValidation(Edge* edge, StringPiece path, uint64_t slash_bits);
- 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.
- void Reset();
-
- /// Dump the nodes and Pools (useful for debugging).
- void Dump();
-
- /// @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.
- std::vector<Node*> RootNodes(std::string* error) const;
- std::vector<Node*> DefaultNodes(std::string* error) const;
-
- /// Create new BindingEnv instance.
- BindingEnv* CreateNewEnv(BindingEnv* parent);
-
- /// Return top-level BindingEnv instance.
- BindingEnv& bindings() const { return *bindings_[0]; }
-
- /// Mapping of path -> Node.
- /// NOTE: This owns all Node instances, but a DepsLog instance contains
- /// pointers to them as well.
- typedef ExternalStringHashMap<Node*>::Type Paths;
- Paths paths_;
-
- /// All the pools used in the graph.
- std::map<std::string, std::unique_ptr<Pool>> pools_;
-
- /// The default and console pools. These points to elements of pools_.
- Pool* default_pool_ = nullptr;
- Pool* console_pool_ = nullptr;
-
- /// All the edges of the graph.
- /// NOTE: This owns all Edge instances.
- std::vector<Edge*> edges_;
-
- Rule* phony_rule_ = nullptr;
-
- /// Note: BindingEnv need pointer stability (as they all include a parent
- /// pointer)
- std::vector<std::unique_ptr<BindingEnv>> bindings_;
-
- /// Points to elements of paths_.
- std::vector<Node*> defaults_;
-};
-
-#endif // NINJA_STATE_H_
diff --git a/src/state_test.cc b/src/state_test.cc
deleted file mode 100644
index 0bc1519..0000000
--- a/src/state_test.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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.
-
-#include "graph.h"
-#include "state.h"
-#include "test.h"
-
-using namespace std;
-
-namespace {
-
-TEST(State, Basic) {
- State state;
-
- EvalString command;
- command.AddText("cat ");
- command.AddSpecial("in");
- command.AddText(" > ");
- command.AddSpecial("out");
-
- Rule* rule = new Rule("cat");
- rule->AddBinding("command", command);
- state.bindings().AddRule(rule);
-
- Edge* edge = state.AddEdge(rule);
- state.AddIn(edge, "in1", 0);
- state.AddIn(edge, "in2", 0);
- state.AddOut(edge, "out", 0);
-
- EXPECT_EQ("cat in1 in2 > out", edge->EvaluateCommand());
-
- EXPECT_FALSE(state.GetNode("in1", 0)->dirty());
- EXPECT_FALSE(state.GetNode("in2", 0)->dirty());
- EXPECT_FALSE(state.GetNode("out", 0)->dirty());
-}
-
-} // namespace
diff --git a/src/status.cc b/src/status.cc
deleted file mode 100644
index 378edc7..0000000
--- a/src/status.cc
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright 2016 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 "status.h"
-
-#include <assert.h>
-#include <stdarg.h>
-#include <stdlib.h>
-
-#ifdef _WIN32
-#include <fcntl.h>
-#include <io.h>
-#undef min // min() is defined as a macro in minwindef.h
-#endif
-
-#include "async_loop.h"
-#include "build_config.h"
-#include "debug_flags.h"
-
-using namespace std;
-
-namespace {
-
-void StringAppendRate(std::string& str, double rate) {
- if (rate == -1)
- str.push_back('?');
- else
- StringAppendFormat(str, "%1.f", rate);
-}
-
-} // namespace
-
-StatusPrinter::StatusPrinter(const BuildConfig& config)
- : config_(config), started_edges_(0),
- finished_edges_(0), total_edges_(0), running_edges_(0), time_millis_(0),
- current_rate_(config.parallelism), format_(config_.status_format()) {
- // LinePrinter constructor uses these environment variables to determine
- // properties of the current terminal.
- printer_.Reset(config.environment.Get("TERM"),
- config.environment.Get("CLICOLOR_FORCE"));
-
- // Don't do anything fancy in verbose mode.
- if (config_.verbosity != BuildConfig::NORMAL)
- printer_.set_smart_terminal(false);
-
- if (printer_.is_smart_terminal() && !config_.dry_run) {
- max_pending_height_ = config_.status_max_commands();
-
- // Since elapsed times are displayed to the first decimal digit
- // only, there is no point in using a value smaller than 0.1 seconds
- // for the refresh timeout.
- const int64_t minimal_refresh_timeout_ms = 100;
- int64_t config_refresh = config_.status_refresh_millis();
- if (config_refresh && config_refresh > minimal_refresh_timeout_ms)
- refresh_timeout_ms_ = config_refresh;
- else
- refresh_timeout_ms_ = minimal_refresh_timeout_ms;
- }
-}
-
-StatusPrinter::~StatusPrinter() = default;
-
-void StatusPrinter::PlanHasTotalEdges(int total) {
- total_edges_ = total;
-}
-
-void StatusPrinter::BuildEdgeStarted(const Edge* edge) {
- int64_t start_time_millis = GetCurrentBuildTimeMs();
- ++started_edges_;
- ++running_edges_;
- time_millis_ = start_time_millis;
-
- pending_edges_[edge] = start_time_millis;
-
- if (edge->use_console()) {
- // This command will print its output directly to stdout, so
- // clear the pending edges to let Ninja update the status and
- // lock the line printer.
- DisableTimer();
- ClearPendingEdges();
- PrintStatus(edge, start_time_millis);
- printer_.SetConsoleLocked(true);
- } else if (printer_.is_smart_terminal()) {
- PrintStatus(edge, start_time_millis);
- BuildRefresh(start_time_millis);
- }
-}
-
-void StatusPrinter::BuildEdgeFinished(Edge* edge, bool success,
- const string& output) {
- int64_t end_time_millis = GetCurrentBuildTimeMs();
- time_millis_ = end_time_millis;
- ++finished_edges_;
-
- auto it = pending_edges_.find(edge);
- assert(it != pending_edges_.end());
- pending_edges_.erase(it);
-
- --running_edges_;
-
- if (edge->use_console()) {
- printer_.SetConsoleLocked(false);
- EnableTimer();
- }
-
- if (config_.verbosity == BuildConfig::QUIET)
- return;
-
- if (!edge->use_console()) {
- PrintStatus(edge, end_time_millis);
- if (success && output.empty()) {
- BuildRefresh(end_time_millis);
- } else {
- // Ninja is going to print something so clear
- // any pending edges first.
- ClearPendingEdges();
- }
- }
-
- // Print the command that is spewing before printing its output.
- if (!success) {
- string outputs;
- for (vector<Node*>::const_iterator o = edge->outputs_.begin();
- o != edge->outputs_.end(); ++o)
- outputs += (*o)->path() + " ";
-
- if (printer_.supports_color()) {
- printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
- } else {
- printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
- }
- printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
- }
-
- if (!output.empty()) {
- // ninja sets stdout and stderr of subprocesses to a pipe, to be able to
- // check if the output is empty. Some compilers, e.g. clang, check
- // isatty(stderr) to decide if they should print colored output.
- // To make it possible to use colored output with ninja, subprocesses should
- // be run with a flag that forces them to always print color escape codes.
- // To make sure these escape codes don't show up in a file if ninja's output
- // is piped to a file, ninja strips ansi escape codes again if it's not
- // writing to a |smart_terminal_|.
- // (Launching subprocesses in pseudo ttys doesn't work because there are
- // only a few hundred available on some systems, and ninja can launch
- // thousands of parallel compile commands.)
- string final_output;
- if (!printer_.supports_color())
- final_output = StripAnsiEscapeCodes(output);
- else
- final_output = output;
-
-#ifdef _WIN32
- // Fix extra CR being added on Windows, writing out CR CR LF (#773)
- _setmode(_fileno(stdout), _O_BINARY); // Begin Windows extra CR fix
-#endif
-
- printer_.PrintOnNewLine(final_output);
-
-#ifdef _WIN32
- _setmode(_fileno(stdout), _O_TEXT); // End Windows extra CR fix
-#endif
- }
-}
-
-void StatusPrinter::BuildLoadDyndeps() {
- // The DependencyScan calls EXPLAIN() to print lines explaining why
- // 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
- // 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
- // new build status line will appear.
- if (g_explaining) {
- ClearPendingEdges();
- printer_.PrintOnNewLine("");
- }
-}
-
-void StatusPrinter::DisableTimer() {
- if (timer_)
- timer_.Cancel();
-}
-
-void StatusPrinter::EnableTimer() {
- if (timer_)
- timer_.SetDurationMs(refresh_timeout_ms_);
-}
-
-int64_t StatusPrinter::GetCurrentBuildTimeMs() const {
- return AsyncLoop::NowMs() - start_build_time_ms_;
-}
-
-void StatusPrinter::BuildStarted() {
- started_edges_ = 0;
- finished_edges_ = 0;
- running_edges_ = 0;
-
- if (refresh_timeout_ms_ > 0) {
- if (!timer_) {
- start_build_time_ms_ = AsyncLoop::NowMs();
- timer_ = AsyncTimer::CreateWithDuration(
- refresh_timeout_ms_, AsyncLoop::Get(), [this] {
- int64_t cur_time_ms = GetCurrentBuildTimeMs();
- BuildRefresh(cur_time_ms);
- timer_.SetDurationMs(refresh_timeout_ms_);
- });
- }
- EnableTimer();
- }
-
- // Reset last_update_time_ms_ because this function can be called
- // multiple times, but with different starting epochs, which can
- // confuse the logic in this code. As an example: Ninja starts
- // a build then decides to launch the regenerator, which takes
- // about 5 seconds to generate a new build plan. Ninja then starts
- // another build. Without the reset, pending commands will not be
- // displayed for about 5 seconds after the start of the second
- // build!
- last_update_time_ms_ = -1;
-}
-
-void StatusPrinter::BuildFinished() {
- if (timer_) {
- timer_.Close();
- }
- start_build_time_ms_ = 0;
-
- printer_.SetConsoleLocked(false);
- ClearPendingEdges();
- printer_.PrintOnNewLine("");
-}
-
-string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
- int64_t time_millis) const {
- string out;
- for (const char* s = progress_status_format; *s != '\0'; ++s) {
- if (*s == '%') {
- ++s;
- switch (*s) {
- case '%':
- out.push_back('%');
- break;
-
- // Started edges.
- case 's':
- StringAppendFormat(out, "%d", started_edges_);
- break;
-
- // Total edges.
- case 't':
- StringAppendFormat(out, "%d", total_edges_);
- break;
-
- // Running edges.
- case 'r':
- StringAppendFormat(out, "%d", running_edges_);
- break;
-
- // Unstarted edges.
- case 'u':
- StringAppendFormat(out, "%d", total_edges_ - started_edges_);
- break;
-
- // Finished edges.
- case 'f':
- StringAppendFormat(out, "%d", finished_edges_);
- break;
-
- // Overall finished edges per second.
- case 'o':
- StringAppendRate(out, finished_edges_ / (time_millis_ / 1e3));
- break;
-
- // Current rate, average over the last '-j' jobs.
- case 'c':
- current_rate_.UpdateRate(finished_edges_, time_millis_);
- StringAppendRate(out, current_rate_.rate());
- break;
-
- // Percentage
- case 'p': {
- int percent = (100 * finished_edges_) / total_edges_;
- StringAppendFormat(out, "%3i%%", percent);
- break;
- }
-
- case 'e':
- StringAppendFormat(out, "%.3f", time_millis_ / 1e3);
- break;
-
- default:
- Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
- return "";
- }
- } else {
- out.push_back(*s);
- }
- }
-
- return out;
-}
-
-void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
- if (config_.verbosity == BuildConfig::QUIET
- || config_.verbosity == BuildConfig::NO_STATUS_UPDATE)
- return;
-
- bool force_full_command = config_.verbosity == BuildConfig::VERBOSE;
-
- string to_print = edge->GetBinding("description");
- if (to_print.empty() || force_full_command)
- to_print = edge->GetBinding("command");
-
- to_print = FormatProgressStatus(format_.c_str(), time_millis) +
- to_print;
-
- last_status_ = std::move(to_print);
-
- printer_.Print(last_status_,
- force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
-}
-
-void StatusPrinter::BuildRefresh(int64_t cur_time_ms) {
- if (last_update_time_ms_ >= 0) {
- int64_t since_last_ms = cur_time_ms - last_update_time_ms_;
- if (since_last_ms < refresh_timeout_ms_) {
- // No need to update more than necessary when tasks complete
- // really really fast.
- return;
- }
- }
- last_update_time_ms_ = cur_time_ms;
- PrintPendingEdges(cur_time_ms);
-}
-
-void StatusPrinter::ClearPendingEdges() {
- if (last_pending_height_ == 0)
- return;
-
- // repeat "go down 1 line; erase whole line" |last_height_| times.
- for (size_t n = 0; n < last_pending_height_; ++n) {
- printf("\x1B[1B\x1B[2K");
- }
- // move up |last_height_| lines.
- printf("\x1B[%dA", static_cast<int>(last_pending_height_));
- fflush(stdout);
-
- last_pending_height_ = 0;
-}
-
-void StatusPrinter::PrintPendingEdges(int64_t cur_time_ms) {
- if (!max_pending_height_)
- return;
-
- // Find the N-th older running edges, where N is max_height_.
- // Reuse the sorted_pending_edges_ vector between calls.
- auto& sorted_edges = sorted_pending_edges_;
- sorted_edges.assign(pending_edges_.begin(), pending_edges_.end());
-
- auto less = [](const EdgeInfo& a, const EdgeInfo& b) -> bool {
- return a.second < b.second;
- };
- size_t count = std::min(sorted_edges.size(), max_pending_height_);
-
- std::partial_sort(sorted_edges.begin(), sorted_edges.begin() + count,
- sorted_edges.end(), less);
-
- std::string pending_line;
- for (size_t n = 0; n < count; ++n) {
- EdgeInfo& pair = sorted_edges[n];
-
- // Format the elapsed time in a human friendly format.
- std::string elapsed;
- int64_t elapsed_ms = cur_time_ms - pair.second;
- if (elapsed_ms < 0) {
- elapsed = "??????";
- } else {
- if (elapsed_ms < 60000) {
- StringAppendFormat(elapsed, "%d.%ds",
- static_cast<int>((elapsed_ms / 1000)),
- static_cast<int>((elapsed_ms % 1000) / 100));
- } else {
- StringAppendFormat(elapsed, "%dm%ds",
- static_cast<int>((elapsed_ms / 60000)),
- static_cast<int>((elapsed_ms % 60000) / 1000));
- }
- }
-
- // Get edge description or command.
- std::string description = pair.first->GetBinding("description");
- if (description.empty())
- description = pair.first->GetBinding("command");
-
- // Format '<elapsed> | <description>' where <elapsed> is
- // right-justified.
- size_t justification_width = 6;
- size_t justified_elapsed_width =
- std::min(justification_width, elapsed.size());
- size_t needed_capacity = justified_elapsed_width + 3 + description.size();
- if (needed_capacity > pending_line.capacity())
- pending_line.reserve(needed_capacity);
- if (elapsed.size() < justification_width) {
- pending_line.assign(justification_width - elapsed.size(), ' ');
- } else {
- pending_line.clear();
- }
- pending_line.append(elapsed);
- pending_line.append(" | ", 3);
- pending_line.append(description);
-
- printf("\n");
- printer_.Print(pending_line, LinePrinter::ELIDE);
- }
-
- // Clear previous lines that are not needed anymore.
- size_t next_height = count;
- for (; count < last_pending_height_; ++count) {
- // Go to next line + clear entire line.
- printf("\n\x1B[2K");
- }
-
- if (count > 0) {
- // Move up to the top status line. Then print the status
- // again to reposition the cursor to the right position.
- // Note that using ASCII sequences to save/restore the
- // cursor position does not work reliably in all terminals
- // (and terminal emulators like mosh or asciinema).
- printf("\x1B[%dA", static_cast<int>(count));
- printer_.Print(last_status_, LinePrinter::ELIDE);
- }
-
- last_pending_height_ = next_height;
-}
-
-void StatusPrinter::Warning(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- ::Warning(msg, ap);
- va_end(ap);
-}
-
-void StatusPrinter::Error(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- ::Error(msg, ap);
- va_end(ap);
-}
-
-void StatusPrinter::Info(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- ::Info(msg, ap);
- va_end(ap);
-}
diff --git a/src/status.h b/src/status.h
deleted file mode 100644
index 3c6119a..0000000
--- a/src/status.h
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2016 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_STATUS_H_
-#define NINJA_STATUS_H_
-
-#include <memory>
-#include <queue>
-#include <string>
-#include <unordered_map>
-
-#include "async_loop.h"
-#include "build.h"
-#include "line_printer.h"
-
-/// Abstract interface to object that tracks the status of a build:
-/// completion fraction, printing updates.
-struct Status {
- virtual void PlanHasTotalEdges(int total) = 0;
- virtual void BuildEdgeStarted(const Edge* edge) = 0;
- virtual void BuildEdgeFinished(Edge* edge, bool success,
- const std::string& output) = 0;
- virtual void BuildLoadDyndeps() = 0;
- virtual void BuildStarted() = 0;
- virtual void BuildFinished() = 0;
-
- virtual void Info(const char* msg, ...) = 0;
- virtual void Warning(const char* msg, ...) = 0;
- virtual void Error(const char* msg, ...) = 0;
-
- virtual ~Status() { }
-};
-
-/// Implementation of the Status interface that prints the status as
-/// human-readable strings to stdout
-struct StatusPrinter : Status {
- explicit StatusPrinter(const BuildConfig& config);
- void PlanHasTotalEdges(int total) override;
- void BuildEdgeStarted(const Edge* edge) override;
- void BuildEdgeFinished(Edge* edge, bool success,
- const std::string& output) override;
- void BuildLoadDyndeps() override;
- void BuildStarted() override;
- void BuildFinished() override;
-
- void Info(const char* msg, ...) override;
- void Warning(const char* msg, ...) override;
- void Error(const char* msg, ...) override;
-
- virtual ~StatusPrinter();
-
- /// Format the progress status string by replacing the placeholders.
- /// See the user manual for more information about the available
- /// placeholders.
- /// @param progress_status_format The format of the progress status.
- /// @param status The status of the edge.
- std::string FormatProgressStatus(const char* progress_status_format,
- int64_t time_millis) const;
-
- private:
- void PrintStatus(const Edge* edge, int64_t time_millis);
-
- void BuildRefresh(int64_t current_time_ms);
- void EnableTimer();
- void DisableTimer();
-
- int64_t GetCurrentBuildTimeMs() const;
-
- const BuildConfig& config_;
-
- int started_edges_, finished_edges_, total_edges_, running_edges_;
- int64_t time_millis_;
-
- /// Prints progress output.
- LinePrinter printer_;
-
- /// The custom progress status format to use.
- const char* progress_status_format_;
-
- struct SlidingRateInfo {
- SlidingRateInfo(int n) : rate_(-1), N(n), last_update_(-1) {}
-
- double rate() { return rate_; }
-
- void UpdateRate(int update_hint, int64_t time_millis) {
- if (update_hint == last_update_)
- return;
- last_update_ = update_hint;
-
- if (times_.size() == N)
- times_.pop();
- times_.push(time_millis);
- if (times_.back() != times_.front())
- rate_ = times_.size() / ((times_.back() - times_.front()) / 1e3);
- }
-
- private:
- double rate_;
- const size_t N;
- std::queue<double> times_;
- int last_update_;
- };
-
- /// Called to refresh the pending list if needed.
- void BuildRefresh();
-
- mutable SlidingRateInfo current_rate_;
-
- /// Support for printing pending edges below the status on smart terminals.
- void PrintPendingEdges(int64_t cur_time_millis);
- void ClearPendingEdges();
-
- std::string format_;
- int64_t refresh_timeout_ms_ = -1;
- size_t max_pending_height_ = 0;
- size_t last_pending_height_ = 0;
- int64_t start_build_time_ms_ = 0;
- int64_t last_update_time_ms_ = -1;
- std::string last_status_;
-
- AsyncLoop* async_loop_ = nullptr;
- AsyncTimer timer_;
-
- // Record pending edges
- using EdgeMap = std::unordered_map<const Edge*, int64_t>;
- EdgeMap pending_edges_;
-
- // Used on each PrintPendingEdges() call to minimize heap allocations.
- // Note that EdgeMap::value_type.first has type |const Edge* const| and
- // thus EdgeMap::value_type is not copyable and cannot be used as an
- // std::vector<> item type.
- using EdgeInfo = std::pair<const Edge*, int64_t>;
- std::vector<EdgeInfo> sorted_pending_edges_;
-};
-
-#endif // NINJA_STATUS_H_
diff --git a/src/status_test.cc b/src/status_test.cc
deleted file mode 100644
index 9d94e8b..0000000
--- a/src/status_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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.
-
-#include "status.h"
-
-#include "build_config.h"
-#include "test.h"
-
-TEST(StatusTest, StatusFormatElapsed) {
- BuildConfig config;
- StatusPrinter status(config);
-
- status.BuildStarted();
- // Before any task is done, the elapsed time must be zero.
- EXPECT_EQ("[%/e0.000]",
- status.FormatProgressStatus("[%%/e%e]", 0));
-}
-
-TEST(StatusTest, StatusFormatReplacePlaceholder) {
- BuildConfig config;
- StatusPrinter status(config);
-
- EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
- status.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", 0));
-}
diff --git a/src/stdio_redirection.cc b/src/stdio_redirection.cc
deleted file mode 100644
index 246a9bb..0000000
--- a/src/stdio_redirection.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2023 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 "stdio_redirection.h"
-
-#include <thread>
-
-#include "util.h"
-
-StdioRedirector::StdioRedirector(IpcHandle& connection)
- : connection_(connection) {}
-
-StdioRedirector::~StdioRedirector() {
- auto do_close = [](IpcHandle& old_handle, FILE* stream) {
- if (old_handle) {
- old_handle.CloneIntoStdio(stream);
- old_handle.Close();
- };
- };
- fflush(stderr);
- do_close(old_stderr_, stderr);
-
- fflush(stdout);
- do_close(old_stdout_, stdout);
-
- do_close(old_stdin_, stdin);
-}
-
-bool StdioRedirector::SendStandardDescriptors(std::string* err) {
- auto do_send = [this, err](FILE* stream) -> bool {
- return connection_.SendNativeHandle(IpcHandle::NativeForStdio(stream), err);
- };
- return do_send(stdin) && do_send(stdout) && do_send(stderr);
-}
-
-bool StdioRedirector::ReceiveStandardDescriptors(std::string* err) {
- auto do_receive = [this, err](FILE* stream, IpcHandle& old_handle) {
- IpcHandle new_handle;
- if (!connection_.ReceiveNativeHandle(&new_handle, err))
- return false;
- if (!new_handle) {
- *err = "Received invalid standard descriptor";
- return false;
- }
- old_handle = IpcHandle::CloneFromStdio(stream);
- if (!old_handle) {
- *err = "Could not save current standard descriptor";
- return false;
- }
- if (!new_handle.CloneIntoStdio(stream)) {
- *err = "Could not redirect standard descriptor";
- return false;
- }
- return true;
- };
- return do_receive(stdin, old_stdin_) && do_receive(stdout, old_stdout_) &&
- do_receive(stderr, old_stderr_);
-}
-
-StdioAsyncStringRedirector::StdioAsyncStringRedirector(AsyncLoop& async_loop,
- FILE* stream)
- : stream_(stream), saved_handle_(IpcHandle::CloneFromStdio(stream)),
- async_loop_(async_loop) {
- fflush(stream_);
- IpcHandle read_handle, write_handle;
- std::string error;
- if (!IpcHandle::CreateAsyncPipe(&read_handle, &write_handle, &error))
- Fatal("ScopedBufferingStdio::CreatePipe: %s", error.c_str());
-
- // Ensure the read handle is never copied to remote processes.
- // IpcHandle::Clone() returns a new instance with CLOEXEC on Posix.
- read_handle.SetInheritable(false);
-
- read_handle_ = AsyncHandle::Create(std::move(read_handle), async_loop_,
- [this](AsyncError error, size_t size) {
- if (error || size == 0) {
- Close();
- } else {
- result_.append(buffer_, size);
- read_handle_.StartRead(buffer_, sizeof(buffer_));
- }
- });
- read_handle_.StartRead(buffer_, sizeof(buffer_));
-
-#ifndef _WIN32
- // Ensure the write handle is in blocking mode since it will be
- // used as the new stdio descriptor.
- write_handle.SetNonBlocking(false);
-#endif
- write_handle.CloneIntoStdio(stream_);
- write_handle.Close();
-}
-
-StdioAsyncStringRedirector::~StdioAsyncStringRedirector() {
- Close();
- Restore();
-}
-
-void StdioAsyncStringRedirector::Restore() {
- fflush(stream_);
- if (saved_handle_) {
- saved_handle_.CloneIntoStdio(stream_);
- saved_handle_.Close();
- }
-}
-
-bool StdioAsyncStringRedirector::IsActive() const {
- if (!read_handle_.IsRunning())
- return false;
-
- async_loop_.RunOnce(0);
- return read_handle_.IsRunning();
-}
-
-bool StdioAsyncStringRedirector::WaitForResult(std::string* result,
- int64_t timeout_ms) {
- Restore();
- auto status =
- async_loop_.RunUntil([this]() { return !read_handle_; }, timeout_ms);
- *result = result_;
- return status != AsyncLoop::ExitTimeout;
-}
-
-void StdioAsyncStringRedirector::Close() {
- read_handle_.Close();
-}
diff --git a/src/stdio_redirection.h b/src/stdio_redirection.h
deleted file mode 100644
index 631f76e..0000000
--- a/src/stdio_redirection.h
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2023 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_STDIO_REDIRECTION_H_
-#define NINJA_STDIO_REDIRECTION_H_
-
-#include <stdio.h>
-
-#include <string>
-
-#include "async_loop.h"
-#include "ipc_handle.h"
-
-/// Multiple classes related to redirecting stdio streams.
-
-/// A class used to manage redirection of stdin/stdout/stderr
-/// between two processes, using an IpcHandle for communication.
-///
-/// IMPORTANT: THIS DOES NOT WORK ON WINDOWS. Due to Win32
-/// technical limitations (i.e. DuplicateHandle() will always
-/// fail to duplicate console handlers from another process,
-/// which happens during ReceiveStandardDescriptors).
-///
-/// Usage is:
-/// 1) Create instance, passing a reference to the handle.
-///
-/// 2) On the client, call SendStandardDescriptors() to send
-/// standard descriptors to the server. This does not change
-/// the current process.
-///
-/// 3) On the server, call ReceiveStandardDescriptors() to
-/// receive them from the handle, and modify the process'
-/// current standard descriptors to use them.
-///
-/// 4) The destructor will restore the previous standard
-/// descriptors, in the case where ReceiveStandardDescriptors()
-/// was called.
-///
-class StdioRedirector {
- public:
- StdioRedirector(IpcHandle& connection);
- ~StdioRedirector();
-
- bool SendStandardDescriptors(std::string* err);
- bool ReceiveStandardDescriptors(std::string* err);
-
- protected:
- IpcHandle& connection_;
- IpcHandle old_stdin_;
- IpcHandle old_stdout_;
- IpcHandle old_stderr_;
-};
-
-/// A class used to redirect an stdout or stderr to a string buffer.
-/// temporarily. Only use this for testing for small outputs, as
-/// its implementation relies on async i/o on an anonymous pipe
-/// for properly reading the data. In practice, a blocking printf()
-/// call will deadlock if the pipe fills up entirely!
-///
-/// Usage is:
-/// - Create instance, passing an AsyncLoop reference and
-/// the stdout or stderr FILE pointer.
-///
-/// - Do small fprintf() or fputs() of the stream, the data
-/// will be sent to the pipe, if this happens on the same
-/// thread than the one running the AsyncLoop, a deadlock
-/// will happen when the pipe is full!
-///
-/// - Call Restore() to restore the stdio stream to its
-/// previous state. This does not destroyed data in the
-/// pipe or in the buffer.
-///
-/// - Call WaitForResult() to receive the result after
-/// draining the pipe completely. This calls Restore()
-/// implicitly.
-///
-class StdioAsyncStringRedirector {
- public:
- /// Constructor flushes |stream|, which must be stdout or stderr,
- /// then redirects its underlying file descriptor / handle to the
- /// write end of a pipe, whose read end is managed by this instance
- /// using asynchronous read operations. |async_loop| is a reference
- /// to the global AsyncLoop instance.
- ///
- /// On Posix, the write end is in blocking mode and does _not_
- /// close on exec(), intentionally, allowing it to be passed
- /// to other processes during fork() + exec().
- StdioAsyncStringRedirector(AsyncLoop& async_loop, FILE* stream);
-
- /// Destructor restores previous stream handle.
- ~StdioAsyncStringRedirector();
-
- /// Flush the stream, close it, then restore its previous handle.
- /// Can be called multiple times safely. Invoked implicitly by
- /// the destructor and by WaitForResult().
- void Restore();
-
- /// Return true if the write end of the pipe has not been closed.
- /// Note that normally Restore() closes it, unless another handle
- /// exists on the system pointing to the pipe's write end
- /// (e.g. when stdout was passed to another process).
- bool IsActive() const;
-
- /// Call Restore() then return buffered result after waiting
- /// at most |timeout_ms| for the pipe's write end to be closed
- /// (which may be prevented by another active file descriptor
- /// pointing to it, e.g. when stdout was passed to another
- /// process before this call).
- ///
- /// Set |*result| to the buffered output. Return true in case of
- /// success, meaning the pipe is closed and the result is
- /// complete, or false in case of timeout, meaning the result
- /// might be incomplete, since there is no guarantee that the
- /// peer has properly flushed its stdout.
- bool WaitForResult(std::string* result, int64_t timeout_ms = 1000);
-
- private:
- void StartRead();
- void Close();
-
- FILE* stream_;
- int stream_fd_;
- IpcHandle saved_handle_;
- AsyncLoop& async_loop_;
- AsyncHandle read_handle_;
- std::string result_;
- char buffer_[256]; // TODO(digit): Read directly into |result_|.
-};
-
-#endif // NINJA_STDIO_REDIRECTION_H_
diff --git a/src/stdio_redirection_test.cc b/src/stdio_redirection_test.cc
deleted file mode 100644
index 80fb941..0000000
--- a/src/stdio_redirection_test.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2023 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 "stdio_redirection.h"
-
-#include "async_loop.h"
-#include "test.h"
-
-TEST(StdioAsyncStringRedirector, Test) {
- auto async_loop = AsyncLoop::CreateLocal();
- std::string result;
- {
- StdioAsyncStringRedirector async_stdout(*async_loop, stdout);
- // IMPORTANT: Do not include \n in the input because fprintf()
- // may or may not translate that into \r\n depending on too many
- // factors to handle here.
- fprintf(stdout, "Hello!");
- EXPECT_TRUE(async_stdout.WaitForResult(&result));
- }
- EXPECT_EQ(result, "Hello!");
-}
-
-TEST(StdioAsyncStringRedirector, TestWithTimeout) {
- auto async_loop = AsyncLoop::CreateLocal();
- std::string result;
- bool ret;
- IpcHandle duplicate_stdout_fd;
- {
- StdioAsyncStringRedirector async_stdout(*async_loop, stdout);
- fprintf(stdout, "Hello!");
- fflush(stdout);
-
- // Duplicate stdout handle, which will prevent the internal
- // pipe to be closed properly by WaitForResult() which will
- // timeout after 10ms.
- duplicate_stdout_fd = IpcHandle::CloneFromStdio(stdout);
- ret = async_stdout.WaitForResult(&result, 10LL);
- }
- EXPECT_FALSE(ret);
- EXPECT_TRUE(duplicate_stdout_fd);
- EXPECT_EQ(result, "Hello!");
-}
diff --git a/src/string_piece.h b/src/string_piece.h
deleted file mode 100644
index 1c0bee6..0000000
--- a/src/string_piece.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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.
-
-#ifndef NINJA_STRINGPIECE_H_
-#define NINJA_STRINGPIECE_H_
-
-#include <string>
-
-#include <string.h>
-
-/// StringPiece represents a slice of a string whose memory is managed
-/// externally. It is useful for reducing the number of std::strings
-/// we need to allocate.
-struct StringPiece {
- typedef const char* const_iterator;
-
- StringPiece() : str_(NULL), len_(0) {}
-
- /// The constructors intentionally allow for implicit conversions.
- 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) {}
-
- 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.
- std::string AsString() const {
- return len_ ? std::string(str_, len_) : std::string();
- }
-
- const_iterator begin() const {
- return str_;
- }
-
- const_iterator end() const {
- return str_ + len_;
- }
-
- char operator[](size_t pos) const {
- return str_[pos];
- }
-
- size_t size() const {
- return len_;
- }
-
- const char* str_;
- size_t len_;
-};
-
-#endif // NINJA_STRINGPIECE_H_
diff --git a/src/string_piece_util.cc b/src/string_piece_util.cc
deleted file mode 100644
index 69513f5..0000000
--- a/src/string_piece_util.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2017 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 "string_piece_util.h"
-
-#include <algorithm>
-#include <string>
-#include <vector>
-using namespace std;
-
-vector<StringPiece> SplitStringPiece(StringPiece input, char sep) {
- vector<StringPiece> elems;
- elems.reserve(count(input.begin(), input.end(), sep) + 1);
-
- StringPiece::const_iterator pos = input.begin();
-
- for (;;) {
- const char* next_pos = find(pos, input.end(), sep);
- if (next_pos == input.end()) {
- elems.push_back(StringPiece(pos, input.end() - pos));
- break;
- }
- elems.push_back(StringPiece(pos, next_pos - pos));
- pos = next_pos + 1;
- }
-
- return elems;
-}
-
-string JoinStringPiece(const vector<StringPiece>& list, char sep) {
- if (list.empty()) {
- return "";
- }
-
- string ret;
-
- {
- size_t cap = list.size() - 1;
- for (size_t i = 0; i < list.size(); ++i) {
- cap += list[i].len_;
- }
- ret.reserve(cap);
- }
-
- for (size_t i = 0; i < list.size(); ++i) {
- if (i != 0) {
- ret += sep;
- }
- ret.append(list[i].str_, list[i].len_);
- }
-
- return ret;
-}
-
-bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b) {
- if (a.len_ != b.len_) {
- return false;
- }
-
- for (size_t i = 0; i < a.len_; ++i) {
- if (ToLowerASCII(a.str_[i]) != ToLowerASCII(b.str_[i])) {
- return false;
- }
- }
-
- return true;
-}
diff --git a/src/string_piece_util.h b/src/string_piece_util.h
deleted file mode 100644
index 28470f1..0000000
--- a/src/string_piece_util.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 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_STRINGPIECE_UTIL_H_
-#define NINJA_STRINGPIECE_UTIL_H_
-
-#include <string>
-#include <vector>
-
-#include "string_piece.h"
-
-std::vector<StringPiece> SplitStringPiece(StringPiece input, 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;
-}
-
-bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b);
-
-#endif // NINJA_STRINGPIECE_UTIL_H_
diff --git a/src/string_piece_util_test.cc b/src/string_piece_util_test.cc
deleted file mode 100644
index 61586dd..0000000
--- a/src/string_piece_util_test.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2017 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 "string_piece_util.h"
-
-#include "test.h"
-
-using namespace std;
-
-TEST(StringPieceUtilTest, SplitStringPiece) {
- {
- string input("a:b:c");
- vector<StringPiece> list = SplitStringPiece(input, ':');
-
- EXPECT_EQ(list.size(), 3);
-
- EXPECT_EQ(list[0], "a");
- EXPECT_EQ(list[1], "b");
- EXPECT_EQ(list[2], "c");
- }
-
- {
- string empty;
- vector<StringPiece> list = SplitStringPiece(empty, ':');
-
- EXPECT_EQ(list.size(), 1);
-
- EXPECT_EQ(list[0], "");
- }
-
- {
- string one("a");
- vector<StringPiece> list = SplitStringPiece(one, ':');
-
- EXPECT_EQ(list.size(), 1);
-
- EXPECT_EQ(list[0], "a");
- }
-
- {
- string sep_only(":");
- vector<StringPiece> list = SplitStringPiece(sep_only, ':');
-
- EXPECT_EQ(list.size(), 2);
-
- EXPECT_EQ(list[0], "");
- EXPECT_EQ(list[1], "");
- }
-
- {
- string sep(":a:b:c:");
- vector<StringPiece> list = SplitStringPiece(sep, ':');
-
- EXPECT_EQ(list.size(), 5);
-
- EXPECT_EQ(list[0], "");
- EXPECT_EQ(list[1], "a");
- EXPECT_EQ(list[2], "b");
- EXPECT_EQ(list[3], "c");
- EXPECT_EQ(list[4], "");
- }
-}
-
-TEST(StringPieceUtilTest, JoinStringPiece) {
- {
- string input("a:b:c");
- vector<StringPiece> list = SplitStringPiece(input, ':');
-
- EXPECT_EQ("a:b:c", JoinStringPiece(list, ':'));
- EXPECT_EQ("a/b/c", JoinStringPiece(list, '/'));
- }
-
- {
- string empty;
- vector<StringPiece> list = SplitStringPiece(empty, ':');
-
- EXPECT_EQ("", JoinStringPiece(list, ':'));
- }
-
- {
- vector<StringPiece> empty_list;
-
- EXPECT_EQ("", JoinStringPiece(empty_list, ':'));
- }
-
- {
- string one("a");
- vector<StringPiece> single_list = SplitStringPiece(one, ':');
-
- EXPECT_EQ("a", JoinStringPiece(single_list, ':'));
- }
-
- {
- string sep(":a:b:c:");
- vector<StringPiece> list = SplitStringPiece(sep, ':');
-
- EXPECT_EQ(":a:b:c:", JoinStringPiece(list, ':'));
- }
-}
-
-TEST(StringPieceUtilTest, ToLowerASCII) {
- EXPECT_EQ('a', ToLowerASCII('A'));
- EXPECT_EQ('z', ToLowerASCII('Z'));
- EXPECT_EQ('a', ToLowerASCII('a'));
- EXPECT_EQ('z', ToLowerASCII('z'));
- EXPECT_EQ('/', ToLowerASCII('/'));
- EXPECT_EQ('1', ToLowerASCII('1'));
-}
-
-TEST(StringPieceUtilTest, EqualsCaseInsensitiveASCII) {
- EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "abc"));
- EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "ABC"));
- EXPECT_TRUE(EqualsCaseInsensitiveASCII("abc", "aBc"));
- EXPECT_TRUE(EqualsCaseInsensitiveASCII("AbC", "aBc"));
- EXPECT_TRUE(EqualsCaseInsensitiveASCII("", ""));
-
- EXPECT_FALSE(EqualsCaseInsensitiveASCII("a", "ac"));
- EXPECT_FALSE(EqualsCaseInsensitiveASCII("/", "\\"));
- EXPECT_FALSE(EqualsCaseInsensitiveASCII("1", "10"));
-}
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
deleted file mode 100644
index 90a5a57..0000000
--- a/src/subprocess-posix.cc
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright 2012 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 <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <spawn.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/select.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "process_utils.h"
-#include "subprocess.h"
-
-extern char** environ;
-
-#include "util.h"
-
-using namespace std;
-
-Subprocess::Subprocess(SubprocessSet& subprocess_set, bool use_console)
- : subprocess_set_(subprocess_set), pid_(-1), use_console_(use_console) {}
-
-Subprocess::~Subprocess() {
- Stop();
- // Reap child if forgotten.
- if (pid_ != -1)
- Finish();
-}
-
-void Subprocess::Stop() {
- async_fd_.Close();
-}
-
-bool Subprocess::Start(const string& command) {
- SubprocessSet* set = &subprocess_set_;
- int output_pipe[2];
- if (pipe(output_pipe) < 0)
- ErrnoFatal("pipe");
- IpcHandle fd = IpcHandle(output_pipe[0]);
- fd.SetInheritable(false);
-
- posix_spawn_file_actions_t action;
- int err = posix_spawn_file_actions_init(&action);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_init", err);
-
- err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_addclose", err);
-
- posix_spawnattr_t attr;
- err = posix_spawnattr_init(&attr);
- if (err != 0)
- ErrnoFatal("posix_spawnattr_init", err);
-
- short flags = 0;
-
- flags |= POSIX_SPAWN_SETSIGMASK;
- sigset_t old_mask = set->async_loop_.GetOldSignalMask();
-
-#ifndef _NDEBUG
- // Consistency check, ensure that SIGINT/SIGHUP/SIGTERM can reach
- // spawned processes.
- if (sigismember(&old_mask, SIGINT))
- Fatal("SubprocessSet: SIGINT is blocked in current signal mask");
- if (sigismember(&old_mask, SIGHUP))
- Fatal("SubprocessSet: SIGHUP is blocked in current signal mask");
- if (sigismember(&old_mask, SIGTERM))
- Fatal("SubprocessSet: SIGTERM is blocked in current signal mask");
-#endif
-
- err = posix_spawnattr_setsigmask(&attr, &old_mask);
- if (err != 0)
- ErrnoFatal("posix_spawnattr_setsigmask", err);
- // Signals which are set to be caught in the calling process image are set to
- // default action in the new process image, so no explicit
- // POSIX_SPAWN_SETSIGDEF parameter is needed.
-
- if (!use_console_) {
- // Put the child in its own process group, so ctrl-c won't reach it.
- flags |= POSIX_SPAWN_SETPGROUP;
- // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
-
- // Open /dev/null over stdin.
- err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
- 0);
- if (err != 0) {
- ErrnoFatal("posix_spawn_file_actions_addopen", err);
- }
-
- err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_adddup2", err);
- err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_adddup2", err);
- err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_addclose", err);
- // In the console case, output_pipe is still inherited by the child and
- // closed when the subprocess finishes, which then notifies ninja.
- }
-#ifdef POSIX_SPAWN_USEVFORK
- flags |= POSIX_SPAWN_USEVFORK;
-#endif
-
- err = posix_spawnattr_setflags(&attr, flags);
- if (err != 0)
- ErrnoFatal("posix_spawnattr_setflags", err);
-
- char** env = environ;
- if (set->environment_)
- env = set->environment_->AsExecEnvironmentBlock();
-
- const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
- err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
- const_cast<char**>(spawned_args), env);
- if (err != 0)
- ErrnoFatal("posix_spawn", err);
-
- err = posix_spawnattr_destroy(&attr);
- if (err != 0)
- ErrnoFatal("posix_spawnattr_destroy", err);
- err = posix_spawn_file_actions_destroy(&action);
- if (err != 0)
- ErrnoFatal("posix_spawn_file_actions_destroy", err);
-
- close(output_pipe[1]);
-
- async_fd_ = AsyncHandle::Create(std::move(fd), subprocess_set_.async_loop_,
- [this](AsyncError error, size_t size) {
- if (error)
- Fatal("read: %s", strerror(error));
-
- if (size == 0) {
- Stop();
- subprocess_set_.OnProcessCompletion(this);
- return;
- }
- // Append result, continue reading.
- buf_.append(read_buf_, size);
- async_fd_.StartRead(read_buf_, sizeof(read_buf_));
- });
-
- async_fd_.StartRead(read_buf_, sizeof(read_buf_));
- return true;
-}
-
-ExitStatus Subprocess::Finish() {
- assert(pid_ != -1);
- int status;
- if (waitpid(pid_, &status, 0) < 0)
- 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)
- return ExitSuccess;
- } else if (WIFSIGNALED(status)) {
- if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM
- || WTERMSIG(status) == SIGHUP)
- return ExitInterrupted;
- }
- return ExitFailure;
-}
-
-bool Subprocess::Done() const {
- return !async_fd_;
-}
-
-const string& Subprocess::GetOutput() const {
- return buf_;
-}
-
-SubprocessSet::SubprocessSet()
- : async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_) {}
-
-SubprocessSet::SubprocessSet(const EnvironmentBlock& environment)
- : async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_),
- environment_(&environment) {}
-
-SubprocessSet::~SubprocessSet() {
- Clear();
-}
-
-Subprocess* SubprocessSet::Add(const string& command, bool use_console) {
- auto subprocess =
- std::unique_ptr<Subprocess>(new Subprocess(*this, use_console));
- if (!subprocess->Start(command)) {
- return 0;
- }
-
- Subprocess* result = subprocess.get();
- running_.push_back(std::move(subprocess));
- return result;
-}
-
-void SubprocessSet::OnProcessCompletion(Subprocess* subprocess) {
- // Move the subprocess from the running_ vector to the finished_ queue.
- for (auto it = running_.begin(); it != running_.end(); ++it) {
- if (it->get() == subprocess) {
- finished_.emplace(it->release());
- running_.erase(it);
- return;
- }
- }
-}
-
-bool SubprocessSet::DoWork() {
- size_t running_count = running_.size();
- if (!running_count)
- return false;
-
- AsyncLoop::ExitStatus loop_status =
- async_loop_.RunUntil([this, &running_count]() -> bool {
- return running_.size() != running_count;
- });
-
- return loop_status == AsyncLoop::ExitInterrupted;
-}
-
-std::unique_ptr<Subprocess> SubprocessSet::NextFinished() {
- if (finished_.empty())
- return {};
-
- std::unique_ptr<Subprocess> subproc = std::move(finished_.front());
- finished_.pop();
- return subproc;
-}
-
-void SubprocessSet::Clear() {
- int int_signal = async_loop_.GetInterruptSignal();
- for (auto& subproc : running_) {
- if (!subproc->use_console_)
- kill(-subproc->pid_, int_signal);
- }
- running_.clear();
-}
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
deleted file mode 100644
index 859873a..0000000
--- a/src/subprocess-win32.cc
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2012 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 <assert.h>
-#include <stdio.h>
-
-#include <algorithm>
-
-#include "process_utils.h"
-#include "subprocess.h"
-#include "util.h"
-
-using namespace std;
-
-Subprocess::Subprocess(SubprocessSet& subprocess_set, bool use_console)
- : subprocess_set_(subprocess_set), use_console_(use_console) {}
-
-Subprocess::~Subprocess() {
- Stop();
- // Reap child if forgotten.
- if (child_)
- Finish();
-}
-
-void Subprocess::Stop() {
- pipe_.Close();
-}
-
-HANDLE Subprocess::SetupPipe() {
- char pipe_name[100];
- snprintf(pipe_name, sizeof(pipe_name),
- "\\\\.\\pipe\\ninja_pid%lu_sp%p", GetCurrentProcessId(), this);
-
- IpcHandle pipe_handle = ::CreateNamedPipeA(
- pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE,
- PIPE_UNLIMITED_INSTANCES, 0, 0, INFINITE, NULL);
- if (!pipe_handle)
- Win32Fatal("CreateNamedPipe");
-
- AsyncLoop& async_loop = subprocess_set_.async_loop_;
-
- pipe_ = AsyncHandle::Create(
- std::move(pipe_handle), subprocess_set_.async_loop_,
- [this](AsyncError error, size_t size) {
- if (error) {
- if (error != ERROR_BROKEN_PIPE)
- Win32Fatal("GetOverlappedResult", error);
- Stop();
- subprocess_set_.OnProcessCompletion(this);
- return;
- }
- if (!is_reading_) {
- IpcHandle pipe_handle = pipe_.TakeAcceptedHandle();
- pipe_.ResetHandle(std::move(pipe_handle));
- is_reading_ = true;
- } else {
- buf_.append(read_buf_, size);
- }
- pipe_.StartRead(read_buf_, sizeof(read_buf_));
- });
-
- pipe_.StartAccept();
-
- // Get the write end of the pipe as a handle inheritable across processes.
- HANDLE output_write_handle =
- CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
- HANDLE output_write_child;
- if (!DuplicateHandle(GetCurrentProcess(), output_write_handle,
- GetCurrentProcess(), &output_write_child,
- 0, TRUE, DUPLICATE_SAME_ACCESS)) {
- Win32Fatal("DuplicateHandle");
- }
- CloseHandle(output_write_handle);
-
- return output_write_child;
-}
-
-bool Subprocess::Start(const string& command) {
- IpcHandle child_pipe = SetupPipe();
-
- SECURITY_ATTRIBUTES security_attributes = {};
- security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
- security_attributes.bInheritHandle = TRUE;
- // Must be inheritable so subprocesses can dup to children.
- IpcHandle nul =
- CreateFileA("NUL", GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- &security_attributes, OPEN_EXISTING, 0, NULL);
- if (!nul)
- Fatal("couldn't open nul");
-
- STARTUPINFOA startup_info = {};
- startup_info.cb = sizeof(STARTUPINFO);
- if (!use_console_) {
- startup_info.dwFlags = STARTF_USESTDHANDLES;
- startup_info.hStdInput = nul.native_handle();
- startup_info.hStdOutput = child_pipe.native_handle();
- startup_info.hStdError = child_pipe.native_handle();
- }
- // In the console case, child_pipe is still inherited by the child and closed
- // when the subprocess finishes, which then notifies ninja.
-
- PROCESS_INFORMATION process_info = {};
-
- // Ninja handles ctrl-c, except for subprocesses in console pools.
- DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP;
-
- // Compute environment pointer.
- LPVOID environment = NULL;
- if (subprocess_set_.environment_)
- environment = subprocess_set_.environment_->AsAnsiEnvironmentBlock();
-
- // Do not prepend 'cmd /c' on Windows, this breaks command
- // lines greater than 8,191 chars.
- if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL,
- /* inherit handles */ TRUE, process_flags, environment,
- NULL, &startup_info, &process_info)) {
- DWORD error = GetLastError();
- if (error == ERROR_FILE_NOT_FOUND) {
- // File (program) not found error is treated as a normal build
- // action failure.
- Stop();
- // child_ is already NULL;
- buf_ = "CreateProcess failed: The system cannot find the file "
- "specified.\n";
- return true;
- } else {
- 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);
- }
- }
-
- // Close pipe channel only used by the child.
- CloseHandle(process_info.hThread);
- child_ = IpcHandle(process_info.hProcess);
-
- return true;
-}
-
-ExitStatus Subprocess::Finish() {
- if (!child_)
- return ExitFailure;
-
- // TODO: add error handling for all of these.
- WaitForSingleObject(child_.native_handle(), INFINITE);
-
- DWORD exit_code = 0;
- GetExitCodeProcess(child_.native_handle(), &exit_code);
-
- child_.Close();
-
- return exit_code == 0 ? ExitSuccess :
- exit_code == CONTROL_C_EXIT ? ExitInterrupted :
- ExitFailure;
-}
-
-bool Subprocess::Done() const {
- return !pipe_;
-}
-
-const string& Subprocess::GetOutput() const {
- return buf_;
-}
-
-SubprocessSet::SubprocessSet()
- : async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_) {}
-
-SubprocessSet::SubprocessSet(const EnvironmentBlock& environment)
- : async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_),
- environment_(&environment) {}
-
-SubprocessSet::~SubprocessSet() {
- Clear();
-}
-
-Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
- std::unique_ptr<Subprocess> subprocess(new Subprocess(*this, use_console));
- if (!subprocess->Start(command)) {
- return 0;
- }
- Subprocess* result = subprocess.get();
- if (subprocess->child_)
- running_.push_back(std::move(subprocess));
- else
- finished_.push(std::move(subprocess));
- return result;
-}
-
-void SubprocessSet::OnProcessCompletion(Subprocess* subprocess) {
- // Move the subprocess from the running_ vector to the finished_ queue.
- for (auto it = running_.begin(); it != running_.end(); ++it) {
- if (it->get() == subprocess) {
- finished_.emplace(std::move(*it));
- running_.erase(it);
- return;
- }
- }
- assert(false && "Unknown subprocess pointer!");
-}
-
-bool SubprocessSet::DoWork() {
- size_t running_count = running_.size();
- if (!running_count)
- return false;
-
- AsyncLoop::ExitStatus loop_status =
- async_loop_.RunUntil([this, &running_count]() -> bool {
- return running_.size() != running_count;
- });
-
- return loop_status == AsyncLoop::ExitInterrupted;
-}
-
-std::unique_ptr<Subprocess> SubprocessSet::NextFinished() {
- if (finished_.empty())
- return {};
-
- std::unique_ptr<Subprocess> subproc = std::move(finished_.front());
- finished_.pop();
- return subproc;
-}
-
-void SubprocessSet::Clear() {
- for (auto& subproc : running_) {
- // Since the foreground process is in our process group, it will receive a
- // CTRL_C_EVENT or CTRL_BREAK_EVENT at the same time as us.
- if (subproc->child_ && !subproc->use_console_) {
- if (!GenerateConsoleCtrlEvent(
- CTRL_BREAK_EVENT,
- GetProcessId(subproc->child_.native_handle()))) {
- Win32Fatal("GenerateConsoleCtrlEvent");
- }
- }
- }
- running_.clear();
-}
diff --git a/src/subprocess.h b/src/subprocess.h
deleted file mode 100644
index e6490fd..0000000
--- a/src/subprocess.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2012 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_SUBPROCESS_H_
-#define NINJA_SUBPROCESS_H_
-
-#include <memory>
-#include <queue>
-#include <string>
-#include <vector>
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <signal.h>
-#endif
-
-#include "async_loop.h"
-#include "exit_status.h"
-#include "interrupt_handling.h"
-#include "ipc_handle.h"
-
-class EnvironmentBlock;
-
-struct SubprocessSet;
-
-/// Subprocess wraps a single async subprocess. It is entirely
-/// passive: it expects the caller to notify it when its fds are ready
-/// for reading, as well as call Finish() to reap the child once done()
-/// is true.
-struct Subprocess {
- ~Subprocess();
-
- /// Returns ExitSuccess on successful process exit, ExitInterrupted if
- /// the process was interrupted, ExitFailure if it otherwise failed.
- /// Should only be called on an instance where Done() returns true.
- ExitStatus Finish();
-
- /// Return true if the a subprocess has completed. Only used for testing.
- bool Done() const;
-
- /// Retrieve buffered output (combined stdout/stderr) for a given
- /// subprocess. Will be empty for subprocesses belonging to the 'console'
- /// pool. Only call this when Done() returns true, or after a call to
- /// Finish().
- const std::string& GetOutput() const;
-
- private:
- Subprocess(SubprocessSet& subprocess_set, bool use_console);
- bool Start(const std::string& command);
-
- SubprocessSet& subprocess_set_;
- std::string buf_;
-
-#ifdef _WIN32
- /// Set up pipe_ as the parent-side pipe of the subprocess; return the
- /// other end of the pipe, usable in the child process.
- HANDLE SetupPipe();
-
- IpcHandle child_;
- AsyncHandle pipe_;
- bool is_reading_ = false;
-#else
- AsyncHandle async_fd_;
- pid_t pid_;
-#endif
- bool use_console_;
- char read_buf_[4 << 10];
-
- void Stop();
-
- friend struct SubprocessSet;
-};
-
-/// SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
-/// DoWork() waits for any state change in subprocesses; finished_
-/// is a queue of subprocesses as they finish.
-struct SubprocessSet {
- SubprocessSet();
-
- explicit SubprocessSet(const EnvironmentBlock& environment);
-
- ~SubprocessSet();
-
- /// Start a new subprocess, and return a pointer to the corresponding
- /// instance, which is still owned by the SubprocessSet.
- Subprocess* Add(const std::string& command, bool use_console = false);
-
- /// Wait for any subprocess to complete. Returns true if interrupted
- /// or false otherwise.
- bool DoWork();
-
- /// Return next finished process, or null if there is none.
- /// The returned instance must be destroyed before this SubprocessSet.
- std::unique_ptr<Subprocess> NextFinished();
-
- /// Remove all remaining processes forcibly.
- void Clear();
-
- std::vector<std::unique_ptr<Subprocess>> running_;
- std::queue<std::unique_ptr<Subprocess>> finished_;
-
- private:
- friend struct Subprocess;
-
- AsyncLoop& async_loop_;
- AsyncLoop::ScopedInterruptCatcher interrupt_catcher_;
- const EnvironmentBlock* environment_ = nullptr;
-
- // Called from an asynchronous callback when a subprocess completes.
- void OnProcessCompletion(Subprocess* subprocess);
-};
-
-#endif // NINJA_SUBPROCESS_H_
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
deleted file mode 100644
index 3aa4089..0000000
--- a/src/subprocess_test.cc
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2012 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 "subprocess.h"
-
-#include "test.h"
-
-#ifndef _WIN32
-// SetWithLots need setrlimit.
-#include <stdio.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <unistd.h>
-#endif
-
-using namespace std;
-
-namespace {
-
-#ifdef _WIN32
-const char* kSimpleCommand = "cmd /c dir \\";
-#else
-const char* kSimpleCommand = "ls /";
-#endif
-
-struct SubprocessTest : public testing::Test {
- void SetUp() { AsyncLoop::ResetForTesting(); }
-
- SubprocessSet subprocs_;
-};
-
-} // anonymous namespace
-
-// Run a command that fails and emits to stderr.
-TEST_F(SubprocessTest, BadCommandStderr) {
- Subprocess* subproc = subprocs_.Add("cmd /c ninja_no_such_command");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- // Pretend we discovered that stderr was ready for writing.
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitFailure, subproc->Finish());
- EXPECT_NE("", subproc->GetOutput());
-}
-
-// Run a command that does not exist
-TEST_F(SubprocessTest, NoSuchCommand) {
- Subprocess* subproc = subprocs_.Add("ninja_no_such_command");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- // Pretend we discovered that stderr was ready for writing.
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitFailure, subproc->Finish());
- EXPECT_NE("", subproc->GetOutput());
-#ifdef _WIN32
- ASSERT_EQ("CreateProcess failed: The system cannot find the file "
- "specified.\n", subproc->GetOutput());
-#endif
-}
-
-#ifndef _WIN32
-
-TEST_F(SubprocessTest, InterruptChild) {
- Subprocess* subproc = subprocs_.Add("kill -INT $$");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitInterrupted, subproc->Finish());
-}
-
-TEST_F(SubprocessTest, InterruptParent) {
- Subprocess* subproc = subprocs_.Add("kill -INT $PPID ; sleep 1");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
- if (interrupted)
- return;
- }
-
- ASSERT_FALSE("We should have been interrupted");
-}
-
-TEST_F(SubprocessTest, InterruptChildWithSigTerm) {
- Subprocess* subproc = subprocs_.Add("kill -TERM $$");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitInterrupted, subproc->Finish());
-}
-
-TEST_F(SubprocessTest, InterruptParentWithSigTerm) {
- Subprocess* subproc = subprocs_.Add("kill -TERM $PPID ; sleep 1");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
- if (interrupted)
- return;
- }
-
- ASSERT_FALSE("We should have been interrupted");
-}
-
-TEST_F(SubprocessTest, InterruptChildWithSigHup) {
- Subprocess* subproc = subprocs_.Add("kill -HUP $$");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitInterrupted, subproc->Finish());
-}
-
-TEST_F(SubprocessTest, InterruptParentWithSigHup) {
- Subprocess* subproc = subprocs_.Add("kill -HUP $PPID ; sleep 1");
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
- if (interrupted)
- return;
- }
-
- ASSERT_FALSE("We should have been interrupted");
-}
-
-TEST_F(SubprocessTest, Timeout) {
- Subprocess* subproc = subprocs_.Add("sleep 1");
- ASSERT_NE((Subprocess*)0, subproc);
-
- int timeout_counter = 0;
- while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
- if (interrupted) {
- ASSERT_GT(timeout_counter, 0);
- break;
- }
- timeout_counter++;
- }
-}
-
-TEST_F(SubprocessTest, Console) {
- // Skip test if we don't have the console ourselves.
- if (isatty(0) && isatty(1) && isatty(2)) {
- Subprocess* subproc =
- subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*use_console=*/true);
- ASSERT_NE((Subprocess*)0, subproc);
-
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
-
- EXPECT_EQ(ExitSuccess, subproc->Finish());
- }
-}
-
-#endif
-
-TEST_F(SubprocessTest, SetWithSingle) {
- Subprocess* subproc = subprocs_.Add(kSimpleCommand);
- ASSERT_NE((Subprocess *) 0, subproc);
-
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
- ASSERT_EQ(ExitSuccess, subproc->Finish());
- ASSERT_NE("", subproc->GetOutput());
-
- ASSERT_EQ(1u, subprocs_.finished_.size());
-}
-
-TEST_F(SubprocessTest, SetWithMulti) {
- Subprocess* processes[3];
- const char* kCommands[3] = {
- kSimpleCommand,
-#ifdef _WIN32
- "cmd /c echo hi",
- "cmd /c time /t",
-#else
- "id -u",
- "pwd",
-#endif
- };
-
- for (int i = 0; i < 3; ++i) {
- processes[i] = subprocs_.Add(kCommands[i]);
- ASSERT_NE((Subprocess *) 0, processes[i]);
- }
-
- ASSERT_EQ(3u, subprocs_.running_.size());
- for (int i = 0; i < 3; ++i) {
- ASSERT_FALSE(processes[i]->Done());
- ASSERT_EQ("", processes[i]->GetOutput());
- }
-
- while (!processes[0]->Done() || !processes[1]->Done() ||
- !processes[2]->Done()) {
- ASSERT_GT(subprocs_.running_.size(), 0u);
- subprocs_.DoWork();
- }
-
- ASSERT_EQ(0u, subprocs_.running_.size());
- ASSERT_EQ(3u, subprocs_.finished_.size());
-
- for (int i = 0; i < 3; ++i) {
- ASSERT_EQ(ExitSuccess, processes[i]->Finish());
- ASSERT_NE("", processes[i]->GetOutput());
- }
-}
-
-#if defined(USE_KQUEUE) || defined(USE_PPOLL)
-TEST_F(SubprocessTest, SetWithLots) {
- // Arbitrary big number; needs to be over 1024 to confirm we're no longer
- // hostage to pselect.
- const unsigned kNumProcs = 1025;
-
- // Make sure [ulimit -n] isn't going to stop us from working.
- rlimit rlim;
- ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim));
- if (rlim.rlim_cur < kNumProcs) {
- printf("Raise [ulimit -n] above %u (currently %lu) to make this test go\n",
- kNumProcs, static_cast<unsigned long>(rlim.rlim_cur));
- return;
- }
-
- vector<Subprocess*> procs;
- for (size_t i = 0; i < kNumProcs; ++i) {
- Subprocess* subproc = subprocs_.Add("/bin/echo");
- ASSERT_NE((Subprocess *) 0, subproc);
- procs.push_back(subproc);
- }
- while (!subprocs_.running_.empty())
- subprocs_.DoWork();
- for (size_t i = 0; i < procs.size(); ++i) {
- ASSERT_EQ(ExitSuccess, procs[i]->Finish());
- ASSERT_NE("", procs[i]->GetOutput());
- }
- ASSERT_EQ(kNumProcs, subprocs_.finished_.size());
-}
-#endif // USE_KQUEUE || USE_PPOLL
-
-// TODO: this test could work on Windows, just not sure how to simply
-// read stdin.
-#ifndef _WIN32
-// Verify that a command that attempts to read stdin correctly thinks
-// that stdin is closed.
-TEST_F(SubprocessTest, ReadStdin) {
- Subprocess* subproc = subprocs_.Add("cat -");
- while (!subproc->Done()) {
- subprocs_.DoWork();
- }
- ASSERT_EQ(ExitSuccess, subproc->Finish());
- ASSERT_EQ(1u, subprocs_.finished_.size());
-}
-#endif // _WIN32
diff --git a/src/test.cc b/src/test.cc
deleted file mode 100644
index d64743f..0000000
--- a/src/test.cc
+++ /dev/null
@@ -1,306 +0,0 @@
-// 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.
-
-#ifdef _WIN32
-#include <direct.h> // Has to be before util.h is included.
-#endif
-
-#include "test.h"
-
-#include <algorithm>
-
-#include <errno.h>
-#include <stdlib.h>
-#ifdef _WIN32
-#include <io.h>
-#include <sys/stat.h>
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
-#include "build_log.h"
-#include "graph.h"
-#include "manifest_parser.h"
-#include "util.h"
-
-#ifdef _AIX
-extern "C" {
- // GCC "helpfully" strips the definition of mkdtemp out on AIX.
- // The function is still present, so if we define it ourselves
- // it will work perfectly fine.
- extern char* mkdtemp(char* name_template);
-}
-#endif
-
-using namespace std;
-
-namespace {
-
-#ifdef _WIN32
-/// Windows has no mkdtemp. Implement it in terms of _mktemp_s.
-char* mkdtemp(char* name_template) {
- // The mktemp_s() implementation of the Wine C runtime is broken and
- // will consistently fail after 26 calls(!) by returning EEXIST.
- // Reimplement the function in this case.
- const size_t max_tries = 32;
- bool found_one = false;
- size_t len = ::strlen(name_template);
- // Count the number of X's in the template, must be at least 6.
- size_t num_xs = 0;
- while (num_xs < len && name_template[len - 1 - num_xs] == 'X')
- num_xs++;
- if (num_xs < 6) {
- errno = EINVAL;
- perror("_mktemp_s");
- return NULL;
- }
-
- // rand() is really not very random on Wine by default :-/
- // leading to surprising conflicts. Same is true for GetCurrentProcessId()
- // or GetCurrentProcess() so use time
- FILETIME ft = {};
- ::GetSystemTimeAsFileTime(&ft);
- srand(static_cast<unsigned>(ft.dwLowDateTime));
-
- auto get_random_alnum = []() -> char {
- int index = rand() % 62;
- if (index < 26)
- return static_cast<char>('A' + index);
- index -= 26;
- if (index < 26)
- return static_cast<char>('a' + index);
- index -= 26;
- return static_cast<char>('0' + index);
- };
- char* start = name_template + len - num_xs;
- for (size_t tries = 0; tries < max_tries; ++tries) {
- for (size_t n = 0; n < num_xs; ++n)
- start[n] = get_random_alnum();
- struct _stat st = {};
- int ret = _stat(name_template, &st);
- if (ret < 0 && errno == ENOENT) {
- errno = 0;
- found_one = true;
- break;
- }
- }
- if (!found_one) {
- // The Posix mkdtemp() specification does not specify which errno
- // value to use in this case, neither does the Linux manpage, so
- // just select EINVAL, which is what the Win32 _mktemp_s() returns
- // on such failures (note: the Wine version returns EEXIST!).
- errno = EINVAL;
- perror("_mktemp_s");
- return NULL;
- }
-
- int err = _mkdir(name_template);
- if (err < 0) {
- perror("mkdir");
- return NULL;
- }
-
- return name_template;
-}
-#endif // _WIN32
-
-/// Return system temporary directory. Result always has a trailing separator,
-/// except when empty.
-std::string GetSystemTempDir() {
-#ifdef _WIN32
- std::string result;
- result.resize(PATH_MAX);
- DWORD ret = GetTempPath(result.size(), &result[0]);
- result.resize(static_cast<size_t>(ret));
- if (!result.empty()) {
- if (result.back() != '/' && result.back() != '\\')
- result.push_back('\\');
- }
- return result;
-#else
- std::string result = "/tmp/";
- const char* tempdir = getenv("TMPDIR");
- if (tempdir) {
- result = tempdir;
- if (!result.empty() && result.back() != '/')
- result.push_back('/');
- }
- return result;
-#endif
-}
-
-} // anonymous namespace
-
-
-::testing::AsString<void*, void>::AsString(const void* ptr) {
- char temp[32];
- ::snprintf(temp, sizeof(temp), "%p", ptr);
- str_ = temp;
-}
-
-StateTestWithBuiltinRules::StateTestWithBuiltinRules() {
- AddCatRule(&state_);
-}
-
-void StateTestWithBuiltinRules::AddCatRule(State* state) {
- AssertParse(state,
-"rule cat\n"
-" command = cat $in > $out\n");
-}
-
-Node* StateTestWithBuiltinRules::GetNode(const string& path) {
- EXPECT_FALSE(strpbrk(path.c_str(), "/\\"));
- return state_.GetNode(path, 0);
-}
-
-void AssertParse(State* state, const char* input,
- ManifestParserOptions opts) {
- ManifestParser parser(state, NULL, opts);
- string err;
- EXPECT_TRUE(parser.ParseTest(input, &err));
- ASSERT_EQ("", err);
- VerifyGraph(*state);
-}
-
-void AssertHash(const char* expected, uint64_t actual) {
- ASSERT_EQ(BuildLog::LogEntry::HashCommand(expected), actual);
-}
-
-void VerifyGraph(const State& state) {
- for (vector<Edge*>::const_iterator e = state.edges_.begin();
- e != state.edges_.end(); ++e) {
- // All edges need at least one output.
- EXPECT_FALSE((*e)->outputs_.empty());
- // Check that the edge's inputs have the edge as out-edge.
- for (vector<Node*>::const_iterator in_node = (*e)->inputs_.begin();
- in_node != (*e)->inputs_.end(); ++in_node) {
- const vector<Edge*>& out_edges = (*in_node)->out_edges();
- EXPECT_NE(find(out_edges.begin(), out_edges.end(), *e),
- out_edges.end());
- }
- // Check that the edge's outputs have the edge as in-edge.
- for (vector<Node*>::const_iterator out_node = (*e)->outputs_.begin();
- out_node != (*e)->outputs_.end(); ++out_node) {
- EXPECT_EQ((*out_node)->in_edge(), *e);
- }
- }
-
- // The union of all in- and out-edges of each nodes should be exactly edges_.
- set<const Edge*> node_edge_set;
- for (State::Paths::const_iterator p = state.paths_.begin();
- p != state.paths_.end(); ++p) {
- const Node* n = p->second;
- if (n->in_edge())
- node_edge_set.insert(n->in_edge());
- node_edge_set.insert(n->out_edges().begin(), n->out_edges().end());
- }
- set<const Edge*> edge_set(state.edges_.begin(), state.edges_.end());
- EXPECT_EQ(node_edge_set, edge_set);
-}
-
-void VirtualFileSystem::Create(const string& path,
- const string& contents) {
- files_[path].mtime = now_;
- files_[path].contents = contents;
- files_created_.insert(path);
-}
-
-TimeStamp VirtualFileSystem::Stat(const string& path, string* err) const {
- FileMap::const_iterator i = files_.find(path);
- if (i != files_.end()) {
- *err = i->second.stat_error;
- return i->second.mtime;
- }
- return 0;
-}
-
-bool VirtualFileSystem::WriteFile(const string& path, const string& contents) {
- Create(path, contents);
- return true;
-}
-
-bool VirtualFileSystem::MakeDir(const string& path) {
- directories_made_.push_back(path);
- return true; // success
-}
-
-FileReader::Status VirtualFileSystem::ReadFile(const string& path,
- string* contents,
- string* err) {
- files_read_.push_back(path);
- FileMap::iterator i = files_.find(path);
- if (i != files_.end()) {
- *contents = i->second.contents;
- return Okay;
- }
- *err = strerror(ENOENT);
- return NotFound;
-}
-
-int VirtualFileSystem::RemoveFile(const string& path) {
- if (find(directories_made_.begin(), directories_made_.end(), path)
- != directories_made_.end())
- return -1;
- FileMap::iterator i = files_.find(path);
- if (i != files_.end()) {
- files_.erase(i);
- files_removed_.insert(path);
- return 0;
- } else {
- return 1;
- }
-}
-
-ScopedTempDir::ScopedTempDir() : original_dir_(GetCurrentDir()) {}
-
-ScopedTempDir::~ScopedTempDir() {
- Cleanup();
-}
-
-void ScopedTempDir::CreateAndEnter(const string& name) {
- temp_dir_name_ = GetSystemTempDir();
- if (temp_dir_name_.empty())
- Fatal("couldn't get system temp dir");
-
- // Create a temporary subdirectory of that.
- temp_dir_name_ += name;
- temp_dir_name_ += "-XXXXXX";
- char* tempname = mkdtemp(&temp_dir_name_[0]);
- if (!tempname)
- ErrnoFatal("mkdtemp");
-
- // chdir into the new temporary directory.
- if (chdir(temp_dir_name_.c_str()) < 0)
- ErrnoFatal("chdir");
-}
-
-void ScopedTempDir::Cleanup() {
- if (temp_dir_name_.empty())
- return; // Something went wrong earlier or already cleaned.
-
- // Move out of the directory we're about to clobber.
- if (chdir(original_dir_.c_str()) < 0)
- ErrnoFatal("chdir");
-
-#ifdef _WIN32
- string command = "rmdir /s /q " + temp_dir_name_;
-#else
- string command = "rm -rf " + temp_dir_name_;
-#endif
- if (system(command.c_str()) < 0)
- ErrnoFatal("system");
-
- temp_dir_name_.clear();
-}
diff --git a/src/test.h b/src/test.h
deleted file mode 100644
index b03d902..0000000
--- a/src/test.h
+++ /dev/null
@@ -1,384 +0,0 @@
-// 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.
-
-#ifndef NINJA_TEST_H_
-#define NINJA_TEST_H_
-
-#include <stddef.h>
-
-#include "disk_interface.h"
-#include "manifest_parser.h"
-#include "state.h"
-#include "util.h"
-
-// A tiny testing framework inspired by googletest, but much simpler and
-// faster to compile. It supports most things commonly used from googltest. The
-// most noticeable things missing: EXPECT_* and ASSERT_* don't support
-// streaming notes to them with operator<<, and for failing tests the lhs and
-// rhs are not printed. That's so that this header does not have to include
-// sstream, which slows down building ninja_test almost 20%.
-namespace testing {
-class Test {
- bool failed_;
- int assertion_failures_;
- public:
- Test() : failed_(false), assertion_failures_(0) {}
- virtual ~Test() {}
- virtual void SetUp() {}
- virtual void TearDown() {}
- virtual void Run() = 0;
-
- bool Failed() const { return failed_; }
- int AssertionFailures() const { return assertion_failures_; }
- void AddAssertionFailure() { assertion_failures_++; }
- bool NullFailure(bool expected, const char* file, int line, const char* error,
- const char* value);
- bool BooleanFailure(bool expected, const char* file, int line,
- const char* error, const char* value);
- bool BinopFailure(const char* file, int line, const char* error, const char* first_value, const char* second_value);
-};
-
-/// std::void_t<T> is only available since C++17, so use
-/// ::testing::Void<T>::type for the same thing. Used by AsString<> below.
-template <class ...>
-struct Void {
- using type = void;
-};
-
-/// Expand to a type expression that is only valid if |expression| compiles
-/// properly. Only useful for C++ SFINAE.
-#define TESTING_ENABLE_IF_EXPRESSION_COMPILES(expression) \
- typename ::testing::Void<decltype(expression)>::type
-
-/// Expand to a type expression that is only valid if type T provides
-/// an AsString() method. Note: for simplicity, there is no check that
-/// the result is convertible into an std::string here.
-#define TESTING_ENABLE_IF_METHOD_AS_STRING_EXISTS_FOR_TYPE(T) \
- TESTING_ENABLE_IF_EXPRESSION_COMPILES(std::declval<T>().AsString())
-
-/// Expand to a type expression that is only valid if type T is supported
-/// as an std::to_string() argument type.
-#define TESTING_ENABLE_IF_STD_TO_STRING_SUPPORTS_TYPE(T) \
- TESTING_ENABLE_IF_EXPRESSION_COMPILES(std::to_string(std::declval<T>()))
-
-/// RemoveCVRef<T> remove references as well as const and volatile modifier.
-/// Used by AsString<> below.
-template <typename T>
-struct RemoveCVRef {
- using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
-};
-
-/// Expand to the base type of expression value |v|, which means without
-/// any reference, const or volatile qualifier. This can be used as a
-/// type argument when defining template specializations.
-#define TESTING_BASE_TYPE_FOR_VALUE(v) \
- typename ::testing::RemoveCVRef<decltype(v)>::type
-
-/// ConstRefT<T>::type is a const reference type that binds to a value of type
-/// T.
-template <typename T>
-struct ConstRefT {
- using type = const typename std::remove_const<
- typename std::remove_reference<T>::type>::type&;
-};
-
-#define TESTING_CONST_REF_TYPE_FOR_VALUE(v) \
- typename ::testing::ConstRefT<decltype(v)>::type
-
-/// ::testing::AsString<T> is a helper struct used to convert expression
-/// in EXPECT_XXX and ASSERT_XXX macros into the string representation of
-/// their value. This uses C++11 template metadata magic which can be easily
-/// confusing, so as a short technical note:
-///
-/// - An AsString<T> value is an object that provides a `c_str()` method that
-/// will be used to print it in case of test failure.
-///
-/// - By default, AsString<T>::c_str() will return `"<UNPRINTABLE>"`. This
-/// would happen when T is an std::pair<> or something more complex.
-///
-/// - If type T has an AsString() method, it will be used automatically
-/// to retrieve a printable version of the value. E.g. StringPiece!
-///
-/// - If type T is supported by std::to_string(), the latter is used
-/// automatically as well (so all integral and floating point values).
-///
-/// - AsString<T*>::c_str() will return an hexadecimal address (just like %p)
-///
-
-/// The generic / fallback version.
-template <typename T, typename Enable = void>
-struct AsString {
- AsString(const T& value) {}
- const char* c_str() const { return "<UNPRINTABLE>"; }
-};
-
-/// Print booleans as either "true" or "false".
-template <>
-struct AsString<bool, void> {
- AsString(bool value) : value_(value) {}
- const char* c_str() const { return value_ ? "true" : "false"; }
- bool value_;
-};
-
-/// Print pointers with %p by default. See implementation in ninja_test.cc
-template <>
-struct AsString<void*, void> {
- AsString(const void* ptr);
- const char* c_str() const { return str_.c_str(); }
- std::string str_;
-};
-
-template <typename T>
-struct AsString<T*, void> : public AsString<void*, void> {
- AsString(const T* ptr) : AsString<void*, void>(ptr) {}
-};
-
-/// Anything supported by std::to_string() is supported implicitly too.
-template <typename T>
-struct AsString<T, TESTING_ENABLE_IF_STD_TO_STRING_SUPPORTS_TYPE(T)> {
- AsString(const T value) : str_(std::to_string(value)) {}
- const char* c_str() const { return str_.c_str(); }
- std::string str_;
-};
-
-/// Anything that has an AsString() method is supported implicitly too.
-/// For example, StringPiece!
-template <typename T>
-struct AsString<T, TESTING_ENABLE_IF_METHOD_AS_STRING_EXISTS_FOR_TYPE(T)> {
- AsString(const T& value) : str_(value.AsString()) {}
- const char* c_str() const { return str_.c_str(); }
- std::string str_;
-};
-
-/// Support for std::string, avoids a copy.
-template <>
-struct AsString<std::string> {
- AsString(const std::string& value) : str_(value) {}
- const char* c_str() const { return str_.c_str(); }
- const std::string& str_;
-};
-
-/// Support for literal C strings.
-template <size_t N>
-struct AsString<char [N], void> {
- AsString(const char* value) : value_(value) {}
- const char* c_str() const { return value_; }
- const char* value_;
-};
-
-/// Support for C string pointers.
-template <>
-struct AsString<const char*> {
- AsString(const char* value) : value_(value) {}
- const char* c_str() const { return value_; }
- const char* value_;
-};
-
-template <>
-struct AsString<char*> : public AsString<const char*> {
- AsString(const char* value) : AsString<const char*>(value) {}
-};
-
-}
-
-void RegisterTest(testing::Test* (*)(), const char*);
-
-extern testing::Test* g_current_test;
-#define TEST_F_(x, y, name) \
- struct y : public x { \
- static testing::Test* Create() { return g_current_test = new y; } \
- virtual void Run(); \
- }; \
- struct Register##y { \
- Register##y() { RegisterTest(y::Create, name); } \
- }; \
- Register##y g_register_##y; \
- void y::Run()
-
-#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y)
-#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y)
-
-/// Generic EXPECT macro for binary operations. The use of a lambda here
-/// is necessary to ensure that the expressions in (a) and (b) are only
-/// evaluated once at runtime in case of failure.
-#define EXPECT_BINARY_OP(a, b, op) \
- ([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va, \
- TESTING_CONST_REF_TYPE_FOR_VALUE(b) vb) -> bool { \
- return ((va op vb) \
- ? true \
- : g_current_test->BinopFailure( \
- __FILE__, __LINE__, #a " " #op " " #b, \
- ::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
- .c_str(), \
- ::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(vb)>(vb) \
- .c_str())); \
- }(a, b))
-
-#define EXPECT_EQ(a, b) EXPECT_BINARY_OP(a, b, ==)
-#define EXPECT_NE(a, b) EXPECT_BINARY_OP(a, b, !=)
-#define EXPECT_GT(a, b) EXPECT_BINARY_OP(a, b, >)
-#define EXPECT_LT(a, b) EXPECT_BINARY_OP(a, b, <)
-#define EXPECT_GE(a, b) EXPECT_BINARY_OP(a, b, >=)
-#define EXPECT_LE(a, b) EXPECT_BINARY_OP(a, b, <=)
-
-/// Generic EXPECT macro for boolean comparisons.
-#define EXPECT_BOOLEAN_OP(a, expected) \
- ([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va) -> bool { \
- return (static_cast<bool>(va) == expected) \
- ? true \
- : g_current_test->BooleanFailure( \
- expected, __FILE__, __LINE__, #a, \
- ::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
- .c_str()); \
- }(a))
-
-#define EXPECT_TRUE(a) EXPECT_BOOLEAN_OP(a, true)
-#define EXPECT_FALSE(a) EXPECT_BOOLEAN_OP(a, false)
-
-/// Generic EXPECT macro for pointer nullptr comparisons.
-/// Note that boolean comparisons should work too, but the message in case
-/// of failure is slightly clearer when using this macro.
-#define EXPECT_NULL_OP(a, expected) \
- ([](TESTING_CONST_REF_TYPE_FOR_VALUE(a) va) -> bool { \
- return (expected == ((va) == nullptr)) \
- ? true \
- : g_current_test->NullFailure( \
- expected, __FILE__, __LINE__, #a, \
- ::testing::AsString<TESTING_BASE_TYPE_FOR_VALUE(va)>(va) \
- .c_str()); \
- }(a))
-
-#define EXPECT_NULL(a) EXPECT_NULL_OP(a, true)
-#define EXPECT_NOT_NULL(a) EXPECT_NULL_OP(a, false)
-
-#define ASSERT_EQ(a, b) \
- if (!EXPECT_EQ(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_NE(a, b) \
- if (!EXPECT_NE(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_GT(a, b) \
- if (!EXPECT_GT(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_LT(a, b) \
- if (!EXPECT_LT(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_GE(a, b) \
- if (!EXPECT_GE(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_LE(a, b) \
- if (!EXPECT_LE(a, b)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_TRUE(a) \
- if (!EXPECT_TRUE(a)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_FALSE(a) \
- if (!EXPECT_FALSE(a)) { g_current_test->AddAssertionFailure(); return; }
-#define ASSERT_NULL(a) \
- if (!EXPECT_NULL(a)) { \
- g_current_test->AddAssertionFailure(); \
- return; \
- }
-#define ASSERT_NOT_NULL(a) \
- if (!EXPECT_NOT_NULL(a)) { \
- g_current_test->AddAssertionFailure(); \
- return; \
- }
-#define ASSERT_NO_FATAL_FAILURE(a) \
- { \
- int fail_count = g_current_test->AssertionFailures(); \
- a; \
- if (fail_count != g_current_test->AssertionFailures()) { \
- g_current_test->AddAssertionFailure(); \
- return; \
- } \
- }
-
-// Support utilities for tests.
-
-struct Node;
-
-/// A base test fixture that includes a State object with a
-/// builtin "cat" rule.
-struct StateTestWithBuiltinRules : public testing::Test {
- StateTestWithBuiltinRules();
-
- /// Add a "cat" rule to \a state. Used by some tests; it's
- /// otherwise done by the ctor to state_.
- void AddCatRule(State* state);
-
- /// Short way to get a Node by its path from state_.
- Node* GetNode(const std::string& path);
-
- State state_;
-};
-
-void AssertParse(State* state, const char* input,
- ManifestParserOptions = ManifestParserOptions());
-void AssertHash(const char* expected, uint64_t actual);
-void VerifyGraph(const State& state);
-
-/// An implementation of DiskInterface that uses an in-memory representation
-/// of disk state. It also logs file accesses and directory creations
-/// so it can be used by tests to verify disk access patterns.
-struct VirtualFileSystem : public DiskInterface {
- VirtualFileSystem() : now_(1) {}
-
- /// "Create" a file with contents.
- void Create(const std::string& path, const std::string& contents);
-
- /// Tick "time" forwards; subsequent file operations will be newer than
- /// previous ones.
- int Tick() {
- return ++now_;
- }
-
- // DiskInterface
- 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;
- std::string stat_error; // If mtime is -1.
- std::string contents;
- };
-
- std::vector<std::string> directories_made_;
- std::vector<std::string> files_read_;
- typedef std::map<std::string, Entry> FileMap;
- FileMap files_;
- std::set<std::string> files_removed_;
- std::set<std::string> files_created_;
-
- /// A simple fake timestamp for file operations.
- int now_;
-};
-
-struct ScopedTempDir {
- ScopedTempDir();
-
- ~ScopedTempDir();
-
- /// Create a temporary directory and chdir into it.
- void CreateAndEnter(const std::string& name);
-
- /// Clean up the temporary directory.
- void Cleanup();
-
- /// The current directory when creating this instance.
- std::string original_dir_;
-
- /// The subdirectory name for our dir, or empty if it hasn't been set up.
- std::string temp_dir_name_;
-};
-
-#endif // NINJA_TEST_H_
diff --git a/src/timestamp.h b/src/timestamp.h
deleted file mode 100644
index 6a7ccd0..0000000
--- a/src/timestamp.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.
-
-#ifndef NINJA_TIMESTAMP_H_
-#define NINJA_TIMESTAMP_H_
-
-#ifdef _WIN32
-#include "win32port.h"
-#else
-#ifndef __STDC_FORMAT_MACROS
-#define __STDC_FORMAT_MACROS
-#endif
-#include <inttypes.h>
-#endif
-
-// When considering file modification times we only care to compare
-// them against one another -- we never convert them to an absolute
-// real time. On POSIX we use timespec (seconds&nanoseconds since epoch)
-// and on Windows we use a different value. Both fit in an int64.
-typedef int64_t TimeStamp;
-
-#endif // NINJA_TIMESTAMP_H_
diff --git a/src/util.cc b/src/util.cc
deleted file mode 100644
index 5b5fbf9..0000000
--- a/src/util.cc
+++ /dev/null
@@ -1,1176 +0,0 @@
-// 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.
-
-#include "util.h"
-
-#ifdef __CYGWIN__
-#include <windows.h>
-#include <io.h>
-#elif defined(_WIN32)
-#include <windows.h>
-#include <direct.h>
-#include <io.h>
-#include <share.h>
-#endif
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#ifndef _WIN32
-#include <unistd.h>
-#include <sys/time.h>
-#endif
-
-#include <vector>
-
-#if defined(__APPLE__)
-#include <mach-o/dyld.h>
-#include <sys/sysctl.h>
-#elif defined(__FreeBSD__)
-#include <sys/sysctl.h>
-#elif defined(__SVR4) && defined(__sun)
-#include <unistd.h>
-#include <sys/loadavg.h>
-#elif defined(_AIX) && !defined(__PASE__)
-#include <libperfstat.h>
-#elif defined(linux) || defined(__GLIBC__)
-#include <sys/sysinfo.h>
-#include <fstream>
-#include <map>
-#include "string_piece_util.h"
-#endif
-
-#if defined(__FreeBSD__)
-#include <sys/cpuset.h>
-#endif
-
-#include "edit_distance.h"
-
-using namespace std;
-
-void Fatal(const char* msg, ...) {
- va_list ap;
- fprintf(stderr, "ninja: fatal: ");
- va_start(ap, msg);
- vfprintf(stderr, msg, ap);
- va_end(ap);
- fprintf(stderr, "\n");
-#ifdef _WIN32
- // On Windows, some tools may inject extra threads.
- // exit() may block on locks held by those threads, so forcibly exit.
- fflush(stderr);
- fflush(stdout);
- ExitProcess(1);
-#else
- exit(1);
-#endif
-}
-
-void Warning(const char* msg, va_list ap) {
- fprintf(stderr, "ninja: warning: ");
- vfprintf(stderr, msg, ap);
- fprintf(stderr, "\n");
-}
-
-void Warning(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- Warning(msg, ap);
- va_end(ap);
-}
-
-void Error(const char* msg, va_list ap) {
- fprintf(stderr, "ninja: error: ");
- vfprintf(stderr, msg, ap);
- fprintf(stderr, "\n");
-}
-
-void Error(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- Error(msg, ap);
- va_end(ap);
-}
-
-void Info(const char* msg, va_list ap) {
- fprintf(stdout, "ninja: ");
- vfprintf(stdout, msg, ap);
- fprintf(stdout, "\n");
-}
-
-void Info(const char* msg, ...) {
- va_list ap;
- va_start(ap, msg);
- Info(msg, ap);
- va_end(ap);
-}
-
-std::string StringFormat(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- std::string result;
- StringAppendFormat(result, fmt, ap);
- va_end(ap);
- return result;
-}
-
-std::string StringFormat(const char* fmt, va_list ap) {
- std::string result;
- StringAppendFormat(result, fmt, ap);
- return result;
-}
-
-void StringAppendFormat(std::string& str, const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- StringAppendFormat(str, fmt, ap);
- va_end(ap);
-}
-
-void StringAppendFormat(std::string& str, const char* fmt, va_list ap) {
- // First try with a small buffer that should cover most cases without
- // heap allocations.
- char buf[32];
- va_list ap2;
- va_copy(ap2, ap);
- int ret = vsnprintf(buf, sizeof(buf), fmt, ap2);
- va_end(ap2);
- if (ret >= 0 && static_cast<size_t>(ret) < sizeof(buf)) {
- str.append(buf, static_cast<size_t>(ret));
- return;
- }
-
- // This did not work, so try with larger buffers in the string itself.
- size_t org_size = str.size();
- str.resize(org_size + 128);
- const int max_retries = 10;
- for (int retries = 0; retries < max_retries; ++retries) {
- size_t available = str.size() - org_size + 1;
- va_copy(ap2, ap);
- int ret = vsnprintf(&str[org_size], available, fmt, ap2);
- va_end(ap2);
-
- if (ret >= 0 && static_cast<size_t>(ret) < available) {
- // Success, shrink to correct size then return.
- str.resize(org_size + static_cast<size_t>(ret));
- return;
- }
-
- if (ret < 0) {
- // Some versions of the MS runtime returned -1 in case of truncation!
- // For these, simply double the buffer size and retry.
- str.resize(org_size + available * 2);
- } else {
- // Ensure correct size for second try.
- str.resize(org_size + static_cast<size_t>(ret));
- }
- }
- Fatal("Could not format string!");
-}
-
-#ifdef _WIN32
-std::wstring ConvertToUnicodeString(const char* str, size_t size) {
- std::wstring result;
- if (size > 0) {
- int int_size = static_cast<int>(size);
- if (static_cast<size_t>(int_size) != size)
- Fatal("Input UTF-8 string is far too long!");
-
- int wide_size = MultiByteToWideChar(CP_UTF8, 0, str, int_size, nullptr, 0);
- if (wide_size <= 0)
- Win32Fatal("MultiByteToWideChar");
-
- result.resize(static_cast<size_t>(wide_size));
- MultiByteToWideChar(CP_UTF8, 0, str, int_size, &result[0], wide_size);
- }
- return result;
-}
-
-std::wstring ConvertToUnicodeString(const std::string& str) {
- return ConvertToUnicodeString(str.c_str(), str.size());
-}
-
-std::string ConvertFromUnicodeString(const wchar_t* str, size_t size) {
- std::string result;
- if (size > 0) {
- int int_size = static_cast<int>(size);
- if (int_size != size)
- Fatal("Input Unicode string is far too long!");
-
- int utf8_size =
- WideCharToMultiByte(CP_UTF8, 0, str, int_size, NULL, 0, NULL, NULL);
- if (utf8_size <= 0)
- Win32Fatal("WideCharToMultiByte");
-
- result.resize(static_cast<size_t>(utf8_size));
- WideCharToMultiByte(CP_UTF8, 0, str, int_size, &result[0], utf8_size, NULL,
- NULL);
- }
- return result;
-}
-
-std::string ConvertFromUnicodeString(const std::wstring& str) {
- return ConvertFromUnicodeString(str.c_str(), str.size());
-}
-
-#endif // _WIN32
-
-void CanonicalizePath(string* path, uint64_t* slash_bits) {
- size_t len = path->size();
- char* str = 0;
- if (len > 0)
- str = &(*path)[0];
- CanonicalizePath(str, &len, slash_bits);
- path->resize(len);
-}
-
-static bool IsPathSeparator(char c) {
-#ifdef _WIN32
- return c == '/' || c == '\\';
-#else
- return c == '/';
-#endif
-}
-
-void CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits) {
- // WARNING: this function is performance-critical; please benchmark
- // any changes you make to it.
- if (*len == 0) {
- return;
- }
-
- const int kMaxPathComponents = 60;
- char* components[kMaxPathComponents];
- int component_count = 0;
-
- char* start = path;
- char* dst = start;
- const char* src = start;
- const char* end = start + *len;
-
- if (IsPathSeparator(*src)) {
-#ifdef _WIN32
-
- // network path starts with //
- if (*len > 1 && IsPathSeparator(*(src + 1))) {
- src += 2;
- dst += 2;
- } else {
- ++src;
- ++dst;
- }
-#else
- ++src;
- ++dst;
-#endif
- }
-
- while (src < end) {
- if (*src == '.') {
- if (src + 1 == end || IsPathSeparator(src[1])) {
- // '.' component; eliminate.
- src += 2;
- continue;
- } else if (src[1] == '.' && (src + 2 == end || IsPathSeparator(src[2]))) {
- // '..' component. Back up if possible.
- if (component_count > 0) {
- dst = components[component_count - 1];
- src += 3;
- --component_count;
- } else {
- *dst++ = *src++;
- *dst++ = *src++;
- *dst++ = *src++;
- }
- continue;
- }
- }
-
- if (IsPathSeparator(*src)) {
- src++;
- continue;
- }
-
- if (component_count == kMaxPathComponents)
- Fatal("path has too many components : %s", path);
- components[component_count] = dst;
- ++component_count;
-
- while (src != end && !IsPathSeparator(*src))
- *dst++ = *src++;
- *dst++ = *src++; // Copy '/' or final \0 character as well.
- }
-
- if (dst == start) {
- *dst++ = '.';
- *dst++ = '\0';
- }
-
- *len = dst - start - 1;
-#ifdef _WIN32
- uint64_t bits = 0;
- uint64_t bits_mask = 1;
-
- for (char* c = start; c < start + *len; ++c) {
- switch (*c) {
- case '\\':
- bits |= bits_mask;
- *c = '/';
- NINJA_FALLTHROUGH;
- case '/':
- bits_mask <<= 1;
- }
- }
-
- *slash_bits = bits;
-#else
- *slash_bits = 0;
-#endif
-}
-
-static inline bool IsKnownShellSafeCharacter(char ch) {
- if ('A' <= ch && ch <= 'Z') return true;
- if ('a' <= ch && ch <= 'z') return true;
- if ('0' <= ch && ch <= '9') return true;
-
- switch (ch) {
- case '_':
- case '+':
- case '-':
- case '.':
- case '/':
- return true;
- default:
- return false;
- }
-}
-
-static inline bool IsKnownWin32SafeCharacter(char ch) {
- switch (ch) {
- case ' ':
- case '"':
- return false;
- default:
- return true;
- }
-}
-
-static inline bool StringNeedsShellEscaping(const string& input) {
- for (size_t i = 0; i < input.size(); ++i) {
- if (!IsKnownShellSafeCharacter(input[i])) return true;
- }
- return false;
-}
-
-static inline bool StringNeedsWin32Escaping(const string& input) {
- for (size_t i = 0; i < input.size(); ++i) {
- if (!IsKnownWin32SafeCharacter(input[i])) return true;
- }
- return false;
-}
-
-void GetShellEscapedString(const string& input, string* result) {
- assert(result);
-
- if (!StringNeedsShellEscaping(input)) {
- result->append(input);
- return;
- }
-
- const char kQuote = '\'';
- const char kEscapeSequence[] = "'\\'";
-
- result->push_back(kQuote);
-
- string::const_iterator span_begin = input.begin();
- for (string::const_iterator it = input.begin(), end = input.end(); it != end;
- ++it) {
- if (*it == kQuote) {
- result->append(span_begin, it);
- result->append(kEscapeSequence);
- span_begin = it;
- }
- }
- result->append(span_begin, input.end());
- result->push_back(kQuote);
-}
-
-
-void GetWin32EscapedString(const string& input, string* result) {
- assert(result);
- if (!StringNeedsWin32Escaping(input)) {
- result->append(input);
- return;
- }
-
- const char kQuote = '"';
- const char kBackslash = '\\';
-
- result->push_back(kQuote);
- size_t consecutive_backslash_count = 0;
- string::const_iterator span_begin = input.begin();
- for (string::const_iterator it = input.begin(), end = input.end(); it != end;
- ++it) {
- switch (*it) {
- case kBackslash:
- ++consecutive_backslash_count;
- break;
- case kQuote:
- result->append(span_begin, it);
- result->append(consecutive_backslash_count + 1, kBackslash);
- span_begin = it;
- consecutive_backslash_count = 0;
- break;
- default:
- consecutive_backslash_count = 0;
- break;
- }
- }
- result->append(span_begin, input.end());
- result->append(consecutive_backslash_count, kBackslash);
- result->push_back(kQuote);
-}
-
-int ReadFile(const string& path, string* contents, string* err) {
-#ifdef _WIN32
- // This makes a ninja run on a set of 1500 manifest files about 4% faster
- // than using the generic fopen code below.
- err->clear();
- HANDLE f = ::CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
- OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
- if (f == INVALID_HANDLE_VALUE) {
- err->assign(GetLastErrorString());
- return -ENOENT;
- }
-
- for (;;) {
- DWORD len;
- char buf[64 << 10];
- if (!::ReadFile(f, buf, sizeof(buf), &len, NULL)) {
- err->assign(GetLastErrorString());
- contents->clear();
- ::CloseHandle(f);
- return -EIO;
- }
- if (len == 0)
- break;
- contents->append(buf, len);
- }
- ::CloseHandle(f);
- return 0;
-#else
- FILE* f = fopen(path.c_str(), "rb");
- if (!f) {
- err->assign(strerror(errno));
- return -errno;
- }
-
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- if (fstat64(fileno(f), &st) < 0) {
-#else
- struct stat st;
- if (fstat(fileno(f), &st) < 0) {
-#endif
- err->assign(strerror(errno));
- fclose(f);
- return -errno;
- }
-
- // +1 is for the resize in ManifestParser::Load
- contents->reserve(st.st_size + 1);
-
- char buf[64 << 10];
- size_t len;
- while (!feof(f) && (len = fread(buf, 1, sizeof(buf), f)) > 0) {
- contents->append(buf, len);
- }
- if (ferror(f)) {
- err->assign(strerror(errno)); // XXX errno?
- contents->clear();
- fclose(f);
- return -errno;
- }
- fclose(f);
- return 0;
-#endif
-}
-
-void SetCloseOnExec(int fd) {
-#ifndef _WIN32
- int flags = fcntl(fd, F_GETFD);
- if (flags < 0) {
- perror("fcntl(F_GETFD)");
- } else {
- if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
- perror("fcntl(F_SETFD)");
- }
-#else
- HANDLE hd = (HANDLE) _get_osfhandle(fd);
- if (! SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0)) {
- fprintf(stderr, "SetHandleInformation(): %s", GetLastErrorString().c_str());
- }
-#endif // ! _WIN32
-}
-
-
-const char* SpellcheckStringV(const string& text,
- const vector<const char*>& words) {
- const bool kAllowReplacements = true;
- const int kMaxValidEditDistance = 3;
-
- int min_distance = kMaxValidEditDistance + 1;
- const char* result = NULL;
- for (vector<const char*>::const_iterator i = words.begin();
- i != words.end(); ++i) {
- int distance = EditDistance(*i, text, kAllowReplacements,
- kMaxValidEditDistance);
- if (distance < min_distance) {
- min_distance = distance;
- result = *i;
- }
- }
- return result;
-}
-
-const char* SpellcheckString(const char* text, ...) {
- // Note: This takes a const char* instead of a string& because using
- // va_start() with a reference parameter is undefined behavior.
- va_list ap;
- va_start(ap, text);
- vector<const char*> words;
- const char* word;
- while ((word = va_arg(ap, const char*)))
- words.push_back(word);
- va_end(ap);
- return SpellcheckStringV(text, words);
-}
-
-std::string GetCurrentDir() {
- std::string result;
- result.resize(128);
- while (true) {
- if (getcwd(&result[0], result.size()) != nullptr) {
- result.resize(strlen(result.data()));
- return result;
- }
- if (errno != ERANGE)
- ErrnoFatal("Could not get current directory");
-
- result.resize(result.size() * 2);
- }
-}
-
-std::string GetCurrentExecutable() {
- // See
- // https://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
-#ifdef _WIN32
- wchar_t filename_wide[MAX_PATH];
- DWORD size_wide = GetModuleFileNameW(nullptr, filename_wide, MAX_PATH);
- return ConvertFromUnicodeString(filename_wide, size_wide);
-#elif defined(__APPLE__)
- uint32_t size = 0;
- _NSGetExecutablePath(nullptr, &size);
- std::string result;
- result.resize(size);
- // NOTE: std::string::data() is |char*| since C++17 but
- // was |const char*| before that, so use an explicit const_cast<>
- // below to compile in both modes.
- _NSGetExecutablePath(const_cast<char*>(result.data()), &size);
- return result;
-#elif defined(sun) || defined(__sun) // Solaris
- return std::string(getexename());
-#else // Linux and BSDs
- static const char kProbePath[] =
-#ifdef __linux__
- "/proc/self/exe";
-#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
- "/proc/curproc/file";
-#elif defined(__NetBSD__)
- "/proc/curproc/exe";
-#else
-#error "Unknown system!"
-#endif
- char dest_path[PATH_MAX] = {};
- if (readlink(kProbePath, dest_path, PATH_MAX) < 0)
- ErrnoFatal("readlink");
- return std::string(dest_path);
-#endif
-}
-
-std::string GetCurrentExecutableDir() {
- std::string program = GetCurrentExecutable();
-#ifdef _WIN32
- char sep = '\\';
-#else
- char sep = '/';
-#endif
- size_t pos = program.rfind(sep);
- if (pos == std::string::npos || pos == 0)
- Fatal("Could not get path to current executable!");
- return program.substr(0, pos);
-}
-#ifdef _WIN32
-std::string GetLastErrorString(unsigned err) {
- char* msg_buf;
- FormatMessageA(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL,
- err,
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (char*)&msg_buf,
- 0,
- NULL);
- std::string msg = msg_buf;
- LocalFree(msg_buf);
- return msg;
-}
-
-std::string GetLastErrorString() {
- return GetLastErrorString(GetLastError());
-}
-
-void Win32Fatal(const char* function, const char* hint) {
- Win32Fatal(function, GetLastError(), hint);
-}
-
-void Win32Fatal(const char* function, unsigned long error, const char* hint) {
- if (hint) {
- Fatal("%s: %s (%s)", function, GetLastErrorString(error).c_str(), hint);
- } else {
- Fatal("%s: %s", function, GetLastErrorString(error).c_str());
- }
-}
-#endif // _WIN32
-
-void ErrnoFatal(const char* function, const char* hint) {
- ErrnoFatal(function, errno, hint);
-}
-
-void ErrnoFatal(const char* function, int errnum, const char* hint) {
- if (hint) {
- Fatal("%s: %s (%s)", function, strerror(errnum), hint);
- } else {
- Fatal("%s: %s", function, strerror(errnum));
- }
-}
-
-bool islatinalpha(int c) {
- // isalpha() is locale-dependent.
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
-}
-
-string StripAnsiEscapeCodes(const string& in) {
- string stripped;
- stripped.reserve(in.size());
-
- for (size_t i = 0; i < in.size(); ++i) {
- if (in[i] != '\33') {
- // Not an escape code.
- stripped.push_back(in[i]);
- continue;
- }
-
- // Only strip CSIs for now.
- if (i + 1 >= in.size()) break;
- if (in[i + 1] != '[') continue; // Not a CSI.
- i += 2;
-
- // Skip everything up to and including the next [a-zA-Z].
- while (i < in.size() && !islatinalpha(in[i]))
- ++i;
- }
- return stripped;
-}
-
-#if defined(linux) || defined(__GLIBC__)
-std::pair<int64_t, bool> readCount(const std::string& path) {
- std::ifstream file(path.c_str());
- if (!file.is_open())
- return std::make_pair(0, false);
- int64_t n = 0;
- file >> n;
- if (file.good())
- return std::make_pair(n, true);
- return std::make_pair(0, false);
-}
-
-struct MountPoint {
- int mountId;
- int parentId;
- StringPiece deviceId;
- StringPiece root;
- StringPiece mountPoint;
- vector<StringPiece> options;
- vector<StringPiece> optionalFields;
- StringPiece fsType;
- StringPiece mountSource;
- vector<StringPiece> superOptions;
- bool parse(const string& line) {
- vector<StringPiece> pieces = SplitStringPiece(line, ' ');
- if (pieces.size() < 10)
- return false;
- size_t optionalStart = 0;
- for (size_t i = 6; i < pieces.size(); i++) {
- if (pieces[i] == "-") {
- optionalStart = i + 1;
- break;
- }
- }
- if (optionalStart == 0)
- return false;
- if (optionalStart + 3 != pieces.size())
- return false;
- mountId = atoi(pieces[0].AsString().c_str());
- parentId = atoi(pieces[1].AsString().c_str());
- deviceId = pieces[2];
- root = pieces[3];
- mountPoint = pieces[4];
- options = SplitStringPiece(pieces[5], ',');
- optionalFields =
- vector<StringPiece>(&pieces[6], &pieces[optionalStart - 1]);
- fsType = pieces[optionalStart];
- mountSource = pieces[optionalStart + 1];
- superOptions = SplitStringPiece(pieces[optionalStart + 2], ',');
- return true;
- }
- string translate(string& path) const {
- // path must be sub dir of root
- if (path.compare(0, root.len_, root.str_, root.len_) != 0) {
- return string();
- }
- path.erase(0, root.len_);
- if (path == ".." || (path.length() > 2 && path.compare(0, 3, "../") == 0)) {
- return string();
- }
- return mountPoint.AsString() + "/" + path;
- }
-};
-
-struct CGroupSubSys {
- int id;
- string name;
- vector<string> subsystems;
- bool parse(string& line) {
- size_t first = line.find(':');
- if (first == string::npos)
- return false;
- line[first] = '\0';
- size_t second = line.find(':', first + 1);
- if (second == string::npos)
- return false;
- line[second] = '\0';
- id = atoi(line.c_str());
- name = line.substr(second + 1);
- vector<StringPiece> pieces =
- SplitStringPiece(StringPiece(line.c_str() + first + 1), ',');
- for (size_t i = 0; i < pieces.size(); i++) {
- subsystems.push_back(pieces[i].AsString());
- }
- return true;
- }
-};
-
-map<string, string> ParseMountInfo(map<string, CGroupSubSys>& subsystems) {
- map<string, string> cgroups;
- ifstream mountinfo("/proc/self/mountinfo");
- if (!mountinfo.is_open())
- return cgroups;
- while (!mountinfo.eof()) {
- string line;
- getline(mountinfo, line);
- MountPoint mp;
- if (!mp.parse(line))
- continue;
- if (mp.fsType != "cgroup")
- continue;
- for (size_t i = 0; i < mp.superOptions.size(); i++) {
- string opt = mp.superOptions[i].AsString();
- map<string, CGroupSubSys>::iterator subsys = subsystems.find(opt);
- if (subsys == subsystems.end())
- continue;
- string newPath = mp.translate(subsys->second.name);
- if (!newPath.empty())
- cgroups.insert(make_pair(opt, newPath));
- }
- }
- return cgroups;
-}
-
-map<string, CGroupSubSys> ParseSelfCGroup() {
- map<string, CGroupSubSys> cgroups;
- ifstream cgroup("/proc/self/cgroup");
- if (!cgroup.is_open())
- return cgroups;
- string line;
- while (!cgroup.eof()) {
- getline(cgroup, line);
- CGroupSubSys subsys;
- if (!subsys.parse(line))
- continue;
- for (size_t i = 0; i < subsys.subsystems.size(); i++) {
- cgroups.insert(make_pair(subsys.subsystems[i], subsys));
- }
- }
- return cgroups;
-}
-
-int ParseCPUFromCGroup() {
- map<string, CGroupSubSys> subsystems = ParseSelfCGroup();
- map<string, string> cgroups = ParseMountInfo(subsystems);
- map<string, string>::iterator cpu = cgroups.find("cpu");
- if (cpu == cgroups.end())
- return -1;
- std::pair<int64_t, bool> quota = readCount(cpu->second + "/cpu.cfs_quota_us");
- if (!quota.second || quota.first == -1)
- return -1;
- std::pair<int64_t, bool> period =
- readCount(cpu->second + "/cpu.cfs_period_us");
- if (!period.second)
- return -1;
- if (period.first == 0)
- return -1;
- return quota.first / period.first;
-}
-#endif
-
-int GetProcessorCount() {
-#ifdef _WIN32
- DWORD cpuCount = 0;
-#ifndef _WIN64
- // Need to use GetLogicalProcessorInformationEx to get real core count on
- // machines with >64 cores. See https://stackoverflow.com/a/31209344/21475
- DWORD len = 0;
- if (!GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &len)
- && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
- std::vector<char> buf(len);
- int cores = 0;
- if (GetLogicalProcessorInformationEx(RelationProcessorCore,
- reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(
- buf.data()), &len)) {
- for (DWORD i = 0; i < len; ) {
- auto info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(
- buf.data() + i);
- if (info->Relationship == RelationProcessorCore &&
- info->Processor.GroupCount == 1) {
- for (KAFFINITY core_mask = info->Processor.GroupMask[0].Mask;
- core_mask; core_mask >>= 1) {
- cores += (core_mask & 1);
- }
- }
- i += info->Size;
- }
- if (cores != 0) {
- cpuCount = cores;
- }
- }
- }
-#endif
- if (cpuCount == 0) {
- cpuCount = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
- }
- JOBOBJECT_CPU_RATE_CONTROL_INFORMATION info;
- // reference:
- // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_cpu_rate_control_information
- if (QueryInformationJobObject(NULL, JobObjectCpuRateControlInformation, &info,
- sizeof(info), NULL)) {
- if (info.ControlFlags & (JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
- JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP)) {
- return cpuCount * info.CpuRate / 10000;
- }
- }
- return cpuCount;
-#else
- int cgroupCount = -1;
- int schedCount = -1;
-#if defined(linux) || defined(__GLIBC__)
- cgroupCount = ParseCPUFromCGroup();
-#endif
- // 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) {
- schedCount = CPU_COUNT(&set);
- }
-#endif
- if (cgroupCount >= 0 && schedCount >= 0) return std::min(cgroupCount, schedCount);
- if (cgroupCount < 0 && schedCount < 0) return sysconf(_SC_NPROCESSORS_ONLN);
- return std::max(cgroupCount, schedCount);
-#endif
-}
-
-#if defined(_WIN32) || defined(__CYGWIN__)
-static double CalculateProcessorLoad(uint64_t idle_ticks, uint64_t total_ticks)
-{
- static uint64_t previous_idle_ticks = 0;
- static uint64_t previous_total_ticks = 0;
- static double previous_load = -0.0;
-
- uint64_t idle_ticks_since_last_time = idle_ticks - previous_idle_ticks;
- uint64_t total_ticks_since_last_time = total_ticks - previous_total_ticks;
-
- bool first_call = (previous_total_ticks == 0);
- bool ticks_not_updated_since_last_call = (total_ticks_since_last_time == 0);
-
- double load;
- if (first_call || ticks_not_updated_since_last_call) {
- load = previous_load;
- } else {
- // Calculate load.
- double idle_to_total_ratio =
- ((double)idle_ticks_since_last_time) / total_ticks_since_last_time;
- double load_since_last_call = 1.0 - idle_to_total_ratio;
-
- // Filter/smooth result when possible.
- if(previous_load > 0) {
- load = 0.9 * previous_load + 0.1 * load_since_last_call;
- } else {
- load = load_since_last_call;
- }
- }
-
- previous_load = load;
- previous_total_ticks = total_ticks;
- previous_idle_ticks = idle_ticks;
-
- return load;
-}
-
-static uint64_t FileTimeToTickCount(const FILETIME & ft)
-{
- uint64_t high = (((uint64_t)(ft.dwHighDateTime)) << 32);
- uint64_t low = ft.dwLowDateTime;
- return (high | low);
-}
-
-double GetLoadAverage() {
- FILETIME idle_time, kernel_time, user_time;
- BOOL get_system_time_succeeded =
- GetSystemTimes(&idle_time, &kernel_time, &user_time);
-
- double posix_compatible_load;
- if (get_system_time_succeeded) {
- uint64_t idle_ticks = FileTimeToTickCount(idle_time);
-
- // kernel_time from GetSystemTimes already includes idle_time.
- uint64_t total_ticks =
- FileTimeToTickCount(kernel_time) + FileTimeToTickCount(user_time);
-
- double processor_load = CalculateProcessorLoad(idle_ticks, total_ticks);
- posix_compatible_load = processor_load * GetProcessorCount();
-
- } else {
- posix_compatible_load = -0.0;
- }
-
- return posix_compatible_load;
-}
-#elif defined(__PASE__)
-double GetLoadAverage() {
- return -0.0f;
-}
-#elif defined(_AIX)
-double GetLoadAverage() {
- perfstat_cpu_total_t cpu_stats;
- if (perfstat_cpu_total(NULL, &cpu_stats, sizeof(cpu_stats), 1) < 0) {
- return -0.0f;
- }
-
- // Calculation taken from comment in libperfstats.h
- return double(cpu_stats.loadavg[0]) / double(1 << SBITS);
-}
-#elif defined(__UCLIBC__) || (defined(__BIONIC__) && __ANDROID_API__ < 29)
-double GetLoadAverage() {
- struct sysinfo si;
- if (sysinfo(&si) != 0)
- return -0.0f;
- return 1.0 / (1 << SI_LOAD_SHIFT) * si.loads[0];
-}
-#elif defined(__HAIKU__)
-double GetLoadAverage() {
- return -0.0f;
-}
-#else
-double GetLoadAverage() {
- double loadavg[3] = { 0.0f, 0.0f, 0.0f };
- if (getloadavg(loadavg, 3) < 0) {
- // Maybe we should return an error here or the availability of
- // getloadavg(3) should be checked when ninja is configured.
- return -0.0f;
- }
- return loadavg[0];
-}
-#endif // _WIN32
-
-string ElideMiddle(const string& str, size_t width) {
- switch (width) {
- case 0: return "";
- case 1: return ".";
- case 2: return "..";
- case 3: return "...";
- }
- const int kMargin = 3; // Space for "...".
- string result = str;
- if (result.size() > width) {
- size_t elide_size = (width - kMargin) / 2;
- result = result.substr(0, elide_size)
- + "..."
- + result.substr(result.size() - elide_size, elide_size);
- }
- return result;
-}
-
-bool Truncate(const string& path, size_t size, string* err) {
-#ifdef _WIN32
- int fh = _sopen(path.c_str(), _O_RDWR | _O_CREAT, _SH_DENYNO,
- _S_IREAD | _S_IWRITE);
- int success = _chsize(fh, size);
- _close(fh);
-#else
- int success = truncate(path.c_str(), size);
-#endif
- // Both truncate() and _chsize() return 0 on success and set errno and return
- // -1 on failure.
- if (success < 0) {
- *err = strerror(errno);
- return false;
- }
- return true;
-}
-
-bool RunsUnderWine() {
-#ifdef _WIN32
- auto probe_wine = []() -> bool {
- HMODULE module = ::GetModuleHandle("ntdll.dll");
- if (!module)
- return false;
- return ::GetProcAddress(module, "wine_get_version") != nullptr;
- };
- static bool runs_under_wine = probe_wine();
- return runs_under_wine;
-#else
- return false;
-#endif
-}
-
-#ifdef _WIN32
-int64_t TimeStampFromFileTime(const FILETIME& filetime) {
- // FILETIME is in 100-nanosecond increments since the Windows epoch.
- // We don't much care about epoch correctness but we do want the
- // resulting value to fit in a 64-bit integer.
- uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) |
- ((uint64_t)filetime.dwLowDateTime);
- // 1600 epoch -> 2000 epoch (subtract 400 years).
- return (int64_t)mtime - 12622770400LL * (1000000000LL / 100);
-}
-#endif // _WIN32
-
-int64_t GetFileTimestamp(const std::string& path, std::string* error) {
-#ifdef _WIN32
- WIN32_FILE_ATTRIBUTE_DATA attrs;
- if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) {
- DWORD win_err = GetLastError();
- if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND)
- return 0;
-
- *error = StringFormat("GetFileAttributesExA(%s): %s", path.c_str(),
- GetLastErrorString(win_err).c_str());
- return -1;
- }
- return TimeStampFromFileTime(attrs.ftLastWriteTime);
-#else // !_WIN32
-#ifdef __USE_LARGEFILE64
- struct stat64 st;
- if (stat64(path.c_str(), &st) < 0) {
-#else
- struct stat st;
- if (stat(path.c_str(), &st) < 0) {
-#endif
- if (errno == ENOENT || errno == ENOTDIR)
- return 0;
- *error = StringFormat("stat(%s): %s", path.c_str(), strerror(errno));
- return -1;
- }
- // Some users (Flatpak) set mtime to 0, this should be harmless
- // and avoids conflicting with our return value of 0 meaning
- // that it doesn't exist.
- if (st.st_mtime == 0)
- return 1;
-#if defined(_AIX)
- return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
-#elif defined(__APPLE__)
- return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
- st.st_mtimespec.tv_nsec);
-#elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
-return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
-#else // !defined(st_mtime)
-return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
-#endif // !defined(st_mtime)
-#endif // !_WIN32
-}
-
-void AppendStringFormatDurationMs(std::string& out, int64_t duration_ms) {
- if (duration_ms < 0) {
- out += '-';
- duration_ms = -duration_ms;
- }
- if (duration_ms < 1000) {
- StringAppendFormat(out, "%u ms", static_cast<unsigned>(duration_ms));
- } else if (duration_ms < 10000) {
- StringAppendFormat(out, "%u.%02u s",
- static_cast<unsigned>(duration_ms / 1000),
- static_cast<unsigned>((duration_ms % 1000) / 10));
- } else if (duration_ms < 60000) {
- StringAppendFormat(out, "%u.%01u s",
- static_cast<unsigned>(duration_ms / 1000),
- static_cast<unsigned>((duration_ms % 1000) / 100));
- } else {
- int64_t seconds = duration_ms / 1000;
- if (seconds < 3600) {
- StringAppendFormat(out, "%u:%02u min",
- static_cast<unsigned>(seconds / 60),
- static_cast<unsigned>(seconds % 60));
- } else {
- StringAppendFormat(out, "%u:%02u:%02u hr",
- static_cast<unsigned>(seconds / 3600),
- static_cast<unsigned>((seconds % 3600) / 60),
- static_cast<unsigned>(seconds % 60));
- }
- }
-}
-
-std::string StringFormatDurationMs(int64_t duration_ms) {
- std::string result;
- AppendStringFormatDurationMs(result, duration_ms);
- return result;
-}
diff --git a/src/util.h b/src/util.h
deleted file mode 100644
index c260b76..0000000
--- a/src/util.h
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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.
-
-#ifndef NINJA_UTIL_H_
-#define NINJA_UTIL_H_
-
-#ifdef _WIN32
-#include <windows.h>
-#undef ERROR
-#undef min
-#undef max
-#include "win32port.h"
-#else
-#include <stdint.h>
-#endif
-
-#include <stdarg.h>
-
-#include <string>
-#include <vector>
-
-#ifdef _MSC_VER
-#define NORETURN __declspec(noreturn)
-#else
-#define NORETURN __attribute__((noreturn))
-#endif
-
-/// Log a fatal message and exit.
-NORETURN void Fatal(const char* msg, ...);
-
-// Have a generic fall-through for different versions of C/C++.
-#if defined(__cplusplus) && __cplusplus >= 201703L
-#define NINJA_FALLTHROUGH [[fallthrough]]
-#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__clang__)
-#define NINJA_FALLTHROUGH [[clang::fallthrough]]
-#elif defined(__cplusplus) && __cplusplus >= 201103L && defined(__GNUC__) && \
- __GNUC__ >= 7
-#define NINJA_FALLTHROUGH [[gnu::fallthrough]]
-#elif defined(__GNUC__) && __GNUC__ >= 7 // gcc 7
-#define NINJA_FALLTHROUGH __attribute__ ((fallthrough))
-#else // C++11 on gcc 6, and all other cases
-#define NINJA_FALLTHROUGH
-#endif
-
-/// Log a warning message.
-void Warning(const char* msg, ...);
-void Warning(const char* msg, va_list ap);
-
-/// Log an error message.
-void Error(const char* msg, ...);
-void Error(const char* msg, va_list ap);
-
-/// Log an informational message.
-void Info(const char* msg, ...);
-void Info(const char* msg, va_list ap);
-
-/// Format a string.
-std::string StringFormat(const char* fmt, ...);
-std::string StringFormat(const char* fmt, va_list ap);
-void StringAppendFormat(std::string& str, const char* fmt, ...);
-void StringAppendFormat(std::string& str, const char* fmt, va_list ap);
-
-/// 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)
-void CanonicalizePath(std::string* path, uint64_t* slash_bits);
-void CanonicalizePath(char* path, size_t* len, uint64_t* slash_bits);
-
-/// 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 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 std::string& path, std::string* contents, std::string* err);
-
-/// Get the timestamp of a given file. On success return a positive timestamp
-/// in nanoseconds, or 0 if the file is missing. Otherwise, in case of failure,
-/// return -1 after setting |*error|.
-int64_t GetFileTimestamp(const std::string& path, std::string* error);
-
-#ifdef _WIN32
-/// Convert a Win32 FILETIME value into a TimeStamp value.
-int64_t TimeStampFromFileTime(const FILETIME& filetime);
-#endif // _WIN32
-
-/// 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 std::string& text,
- const std::vector<const char*>& words);
-
-/// Like SpellcheckStringV, but takes a NULL-terminated list.
-const char* SpellcheckString(const char* text, ...);
-
-bool islatinalpha(int c);
-
-/// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm).
-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.
-int GetProcessorCount();
-
-/// @return the load average of the machine. A negative value is returned
-/// on error.
-double GetLoadAverage();
-
-/// Elide the given string @a str with '...' in the middle if the length
-/// exceeds @a width.
-std::string ElideMiddle(const std::string& str, size_t width);
-
-/// Truncates a file to the given size.
-bool Truncate(const std::string& path, size_t size, std::string* err);
-
-/// Return absolute path of current directory.
-/// NOTE: GetCurrentDirectory() is a macro in Win32.
-std::string GetCurrentDir();
-
-/// Return absolute path of current executable.
-std::string GetCurrentExecutable();
-
-/// Return absolute path of current executable's directory.
-std::string GetCurrentExecutableDir();
-
-#ifdef _MSC_VER
-#define snprintf _snprintf
-#define fileno _fileno
-#define unlink _unlink
-#define chdir _chdir
-#define strtoull _strtoui64
-#define getcwd _getcwd
-#define PATH_MAX _MAX_PATH
-#endif
-
-#ifdef _WIN32
-/// Convert the value returned by GetLastError() into a string.
-std::string GetLastErrorString();
-std::string GetLastErrorString(unsigned error);
-
-/// Calls Fatal() with a function name and GetLastErrorString.
-NORETURN void Win32Fatal(const char* function, const char* hint = NULL);
-NORETURN void Win32Fatal(const char* function, unsigned long error,
- const char* hint = NULL);
-#endif // !_WIN32
-
-NORETURN void ErrnoFatal(const char* function, const char* hint = NULL);
-NORETURN void ErrnoFatal(const char* function, int errnum, const char* hint = NULL);
-
-/// Convert Unide code to Utf8 string.
-std::string ConvertFromUnicodeString(const wchar_t* str, size_t size);
-std::string ConvertFromUnicodeString(const std::wstring& str);
-
-/// Convert UTF8 string to a Win32 unicode string.
-std::wstring ConvertToUnicodeString(const char* str, size_t size);
-std::wstring ConvertToUnicodeString(const std::string& str);
-
-/// Convert a duration in milliseconds into a human-readable string.
-void AppendStringFormatDurationMs(std::string& out, int64_t duration_ms);
-std::string StringFormatDurationMs(int64_t duration_ms);
-
-/// Returns true if this is a Win32 program running under Wine. Only used
-/// to work-around Wine emulation bugs during unit-testing.
-bool RunsUnderWine();
-
-#endif // NINJA_UTIL_H_
diff --git a/src/util_test.cc b/src/util_test.cc
deleted file mode 100644
index a9a7956..0000000
--- a/src/util_test.cc
+++ /dev/null
@@ -1,518 +0,0 @@
-// 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.
-
-#include "util.h"
-
-#include "test.h"
-
-using namespace std;
-
-namespace {
-
-void CanonicalizePath(string* path) {
- uint64_t unused;
- ::CanonicalizePath(path, &unused);
-}
-
-} // namespace
-
-TEST(CanonicalizePath, PathSamples) {
- string path;
-
- CanonicalizePath(&path);
- EXPECT_EQ("", path);
-
- path = "foo.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo.h", path);
-
- path = "./foo.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo.h", path);
-
- path = "./foo/./bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/bar.h", path);
-
- path = "./x/foo/../bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("x/bar.h", path);
-
- path = "./x/foo/../../bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("bar.h", path);
-
- path = "foo//bar";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/bar", path);
-
- path = "foo//.//..///bar";
- CanonicalizePath(&path);
- EXPECT_EQ("bar", path);
-
- path = "./x/../foo/../../bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("../bar.h", path);
-
- path = "foo/./.";
- CanonicalizePath(&path);
- EXPECT_EQ("foo", path);
-
- path = "foo/bar/..";
- CanonicalizePath(&path);
- EXPECT_EQ("foo", path);
-
- path = "foo/.hidden_bar";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/.hidden_bar", path);
-
- path = "/foo";
- CanonicalizePath(&path);
- EXPECT_EQ("/foo", path);
-
- path = "//foo";
- CanonicalizePath(&path);
-#ifdef _WIN32
- EXPECT_EQ("//foo", path);
-#else
- EXPECT_EQ("/foo", path);
-#endif
-
- path = "/";
- CanonicalizePath(&path);
- EXPECT_EQ("", path);
-
- path = "/foo/..";
- CanonicalizePath(&path);
- EXPECT_EQ("", path);
-
- path = ".";
- CanonicalizePath(&path);
- EXPECT_EQ(".", path);
-
- path = "./.";
- CanonicalizePath(&path);
- EXPECT_EQ(".", path);
-
- path = "foo/..";
- CanonicalizePath(&path);
- EXPECT_EQ(".", path);
-}
-
-#ifdef _WIN32
-TEST(CanonicalizePath, PathSamplesWindows) {
- string path;
-
- CanonicalizePath(&path);
- EXPECT_EQ("", path);
-
- path = "foo.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo.h", path);
-
- path = ".\\foo.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo.h", path);
-
- path = ".\\foo\\.\\bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/bar.h", path);
-
- path = ".\\x\\foo\\..\\bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("x/bar.h", path);
-
- path = ".\\x\\foo\\..\\..\\bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("bar.h", path);
-
- path = "foo\\\\bar";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/bar", path);
-
- path = "foo\\\\.\\\\..\\\\\\bar";
- CanonicalizePath(&path);
- EXPECT_EQ("bar", path);
-
- path = ".\\x\\..\\foo\\..\\..\\bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("../bar.h", path);
-
- path = "foo\\.\\.";
- CanonicalizePath(&path);
- EXPECT_EQ("foo", path);
-
- path = "foo\\bar\\..";
- CanonicalizePath(&path);
- EXPECT_EQ("foo", path);
-
- path = "foo\\.hidden_bar";
- CanonicalizePath(&path);
- EXPECT_EQ("foo/.hidden_bar", path);
-
- path = "\\foo";
- CanonicalizePath(&path);
- EXPECT_EQ("/foo", path);
-
- path = "\\\\foo";
- CanonicalizePath(&path);
- EXPECT_EQ("//foo", path);
-
- path = "\\";
- CanonicalizePath(&path);
- EXPECT_EQ("", path);
-}
-
-TEST(CanonicalizePath, SlashTracking) {
- string path;
- uint64_t slash_bits;
-
- path = "foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("foo.h", path);
- EXPECT_EQ(0, slash_bits);
-
- path = "a\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a/bcd/efh\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/bcd/efh/foo.h", path);
- EXPECT_EQ(4, slash_bits);
-
- path = "a\\bcd/efh\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/bcd/efh/foo.h", path);
- EXPECT_EQ(5, slash_bits);
-
- path = "a\\bcd\\efh\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/bcd/efh/foo.h", path);
- EXPECT_EQ(7, slash_bits);
-
- path = "a/bcd/efh/foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/bcd/efh/foo.h", path);
- EXPECT_EQ(0, slash_bits);
-
- path = "a\\./efh\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/efh/foo.h", path);
- EXPECT_EQ(3, slash_bits);
-
- path = "a\\../efh\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("efh/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a\\b\\c\\d\\e\\f\\g\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/b/c/d/e/f/g/foo.h", path);
- EXPECT_EQ(127, slash_bits);
-
- path = "a\\b\\c\\..\\..\\..\\g\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("g/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a\\b/c\\../../..\\g\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("g/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a\\b/c\\./../..\\g\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/g/foo.h", path);
- EXPECT_EQ(3, slash_bits);
-
- path = "a\\b/c\\./../..\\g/foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/g/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a\\\\\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-
- path = "a/\\\\foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/foo.h", path);
- EXPECT_EQ(0, slash_bits);
-
- path = "a\\//foo.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ("a/foo.h", path);
- EXPECT_EQ(1, slash_bits);
-}
-
-TEST(CanonicalizePath, CanonicalizeNotExceedingLen) {
- // Make sure searching \/ doesn't go past supplied len.
- char buf[] = "foo/bar\\baz.h\\"; // Last \ past end.
- uint64_t slash_bits;
- size_t size = 13;
- ::CanonicalizePath(buf, &size, &slash_bits);
- EXPECT_EQ(0, strncmp("foo/bar/baz.h", buf, size));
- EXPECT_EQ(2, slash_bits); // Not including the trailing one.
-}
-
-TEST(CanonicalizePath, TooManyComponents) {
- string path;
- uint64_t slash_bits;
-
- // 64 is OK.
- path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./"
- "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0x0);
-
- // Backslashes version.
- path =
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x.h";
-
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0xffffffff);
-
- // 65 is OK if #component is less than 60 after path canonicalization.
- path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./"
- "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x/y.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0x0);
-
- // Backslashes version.
- path =
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\"
- "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x\\y.h";
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0x1ffffffff);
-
-
- // 59 after canonicalization is OK.
- path = "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/"
- "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/x/y.h";
- EXPECT_EQ(58, std::count(path.begin(), path.end(), '/'));
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0x0);
-
- // Backslashes version.
- path =
- "a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\"
- "a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\"
- "a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\"
- "a\\a\\a\\a\\a\\a\\a\\a\\a\\x\\y.h";
- EXPECT_EQ(58, std::count(path.begin(), path.end(), '\\'));
- CanonicalizePath(&path, &slash_bits);
- EXPECT_EQ(slash_bits, 0x3ffffffffffffff);
-}
-#endif
-
-TEST(CanonicalizePath, UpDir) {
- string path, err;
- path = "../../foo/bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("../../foo/bar.h", path);
-
- path = "test/../../foo/bar.h";
- CanonicalizePath(&path);
- EXPECT_EQ("../foo/bar.h", path);
-}
-
-TEST(CanonicalizePath, AbsolutePath) {
- string path = "/usr/include/stdio.h";
- string err;
- CanonicalizePath(&path);
- EXPECT_EQ("/usr/include/stdio.h", path);
-}
-
-TEST(CanonicalizePath, NotNullTerminated) {
- string path;
- size_t len;
- uint64_t unused;
-
- path = "foo/. bar/.";
- len = strlen("foo/."); // Canonicalize only the part before the space.
- CanonicalizePath(&path[0], &len, &unused);
- EXPECT_EQ(strlen("foo"), len);
- EXPECT_EQ("foo/. bar/.", string(path));
-
- path = "foo/../file bar/.";
- len = strlen("foo/../file");
- CanonicalizePath(&path[0], &len, &unused);
- EXPECT_EQ(strlen("file"), len);
- EXPECT_EQ("file ./file bar/.", string(path));
-}
-
-TEST(PathEscaping, TortureTest) {
- string result;
-
- GetWin32EscapedString("foo bar\\\"'$@d!st!c'\\path'\\", &result);
- EXPECT_EQ("\"foo bar\\\\\\\"'$@d!st!c'\\path'\\\\\"", result);
- result.clear();
-
- GetShellEscapedString("foo bar\"/'$@d!st!c'/path'", &result);
- EXPECT_EQ("'foo bar\"/'\\''$@d!st!c'\\''/path'\\'''", result);
-}
-
-TEST(PathEscaping, SensiblePathsAreNotNeedlesslyEscaped) {
- const char* path = "some/sensible/path/without/crazy/characters.c++";
- string result;
-
- GetWin32EscapedString(path, &result);
- EXPECT_EQ(path, result);
- result.clear();
-
- GetShellEscapedString(path, &result);
- EXPECT_EQ(path, result);
-}
-
-TEST(PathEscaping, SensibleWin32PathsAreNotNeedlesslyEscaped) {
- const char* path = "some\\sensible\\path\\without\\crazy\\characters.c++";
- string result;
-
- GetWin32EscapedString(path, &result);
- EXPECT_EQ(path, result);
-}
-
-TEST(StripAnsiEscapeCodes, EscapeAtEnd) {
- string stripped = StripAnsiEscapeCodes("foo\33");
- EXPECT_EQ("foo", stripped);
-
- stripped = StripAnsiEscapeCodes("foo\33[");
- EXPECT_EQ("foo", stripped);
-}
-
-TEST(StripAnsiEscapeCodes, StripColors) {
- // An actual clang warning.
- string input = "\33[1maffixmgr.cxx:286:15: \33[0m\33[0;1;35mwarning: "
- "\33[0m\33[1musing the result... [-Wparentheses]\33[0m";
- string stripped = StripAnsiEscapeCodes(input);
- EXPECT_EQ("affixmgr.cxx:286:15: warning: using the result... [-Wparentheses]",
- stripped);
-}
-
-TEST(ElideMiddle, NothingToElide) {
- string input = "Nothing to elide in this short string.";
- EXPECT_EQ(input, ElideMiddle(input, 80));
- EXPECT_EQ(input, ElideMiddle(input, 38));
- EXPECT_EQ("", ElideMiddle(input, 0));
- EXPECT_EQ(".", ElideMiddle(input, 1));
- EXPECT_EQ("..", ElideMiddle(input, 2));
- EXPECT_EQ("...", ElideMiddle(input, 3));
-}
-
-TEST(ElideMiddle, ElideInTheMiddle) {
- string input = "01234567890123456789";
- string elided = ElideMiddle(input, 10);
- EXPECT_EQ("012...789", elided);
- EXPECT_EQ("01234567...23456789", ElideMiddle(input, 19));
-}
-
-TEST(StringFormat, StringFormatWithVarargs) {
- std::string str = StringFormat("%s %d %s", "foo", 42, "bar");
- EXPECT_EQ("foo 42 bar", str);
-}
-
-TEST(StringFormat, StringFormatWithValist) {
- auto f = [](const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- std::string result = StringFormat(fmt, args);
- va_end(args);
- return result;
- };
-
- std::string str = f("%s %d %s", "foo", 42, "bar");
- EXPECT_EQ("foo 42 bar", str);
-}
-
-TEST(StringFormat, StringAppendFormatWithVarargs) {
- std::string str("foo");
- StringAppendFormat(str, " %d %s", 42, "bar");
- EXPECT_EQ("foo 42 bar", str);
-}
-
-TEST(StringFormat, StringAppendFormatWithVarargsAndLongResult) {
- std::string str("foo");
- std::string bar("xxxxxxxx");
- bar += bar;
- bar += bar;
- bar += bar;
- bar += bar;
- bar += bar;
- StringAppendFormat(str, " %d %s --", 42, bar.c_str());
-
- std::string expected = std::string("foo 42 ") + bar + " --";
- EXPECT_EQ(expected, str);
-}
-
-TEST(StringFormat, StringAppendFormatWithValist) {
- std::string str;
- auto f = [&str](const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- StringAppendFormat(str, fmt, args);
- va_end(args);
- };
-
- f("%s %d %s", "foo", 42, "bar");
- EXPECT_EQ("foo 42 bar", str);
-}
-
-TEST(RunsUnderWine, Test) {
- // Try to run Determine
- bool expected = false;
-#ifdef _WIN32
- expected = getenv("WINELOADER") || getenv("WINEUSERNAME") ||
- getenv("WINEDEBUG") || getenv("WINEDLLDIR0");
-#endif
- ASSERT_EQ(expected, RunsUnderWine());
-}
-
-#ifdef _WIN32
-TEST(ConvertToUnicodeString, Test) {
- EXPECT_EQ(std::wstring(L"Bébé"), ConvertToUnicodeString("Bébé"));
-}
-
-TEST(ConvertFromUnicodeString, Test) {
- EXPECT_EQ(std::string("Bébé"), ConvertFromUnicodeString(L"Bébé"));
-}
-#endif // _WIN32
-
-TEST(StringFormatDurationMs, Test) {
- EXPECT_EQ(StringFormatDurationMs(0), "0 ms");
- EXPECT_EQ(StringFormatDurationMs(123), "123 ms");
- EXPECT_EQ(StringFormatDurationMs(995), "995 ms");
- EXPECT_EQ(StringFormatDurationMs(1000), "1.00 s");
- EXPECT_EQ(StringFormatDurationMs(3148), "3.14 s");
- EXPECT_EQ(StringFormatDurationMs(9999), "9.99 s");
- EXPECT_EQ(StringFormatDurationMs(10000), "10.0 s");
- EXPECT_EQ(StringFormatDurationMs(59999), "59.9 s");
- EXPECT_EQ(StringFormatDurationMs(60000), "1:00 min");
- EXPECT_EQ(StringFormatDurationMs(157892), "2:37 min");
- EXPECT_EQ(StringFormatDurationMs(3599999), "59:59 min");
- EXPECT_EQ(StringFormatDurationMs(3600000), "1:00:00 hr");
- EXPECT_EQ(StringFormatDurationMs(1000000000), "277:46:40 hr");
-
- EXPECT_EQ(StringFormatDurationMs(-123), "-123 ms");
- EXPECT_EQ(StringFormatDurationMs(-995), "-995 ms");
- EXPECT_EQ(StringFormatDurationMs(-1000), "-1.00 s");
- EXPECT_EQ(StringFormatDurationMs(3148), "3.14 s");
-}
diff --git a/src/version.cc b/src/version.cc
deleted file mode 100644
index d306957..0000000
--- a/src/version.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2013 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 "version.h"
-
-#include <stdlib.h>
-
-#include "util.h"
-
-using namespace std;
-
-const char* kNinjaVersion = "1.12.0.git";
-
-void ParseVersion(const string& version, int* major, int* minor) {
- size_t end = version.find('.');
- *major = atoi(version.substr(0, end).c_str());
- *minor = 0;
- if (end != string::npos) {
- size_t start = end + 1;
- end = version.find('.', start);
- *minor = atoi(version.substr(start, end).c_str());
- }
-}
-
-void CheckNinjaVersion(const string& version) {
- int bin_major, bin_minor;
- ParseVersion(kNinjaVersion, &bin_major, &bin_minor);
- int file_major, file_minor;
- ParseVersion(version, &file_major, &file_minor);
-
- if (bin_major > file_major) {
- Warning("ninja executable version (%s) greater than build file "
- "ninja_required_version (%s); versions may be incompatible.",
- kNinjaVersion, version.c_str());
- return;
- }
-
- if ((bin_major == file_major && bin_minor < file_minor) ||
- bin_major < file_major) {
- Fatal("ninja version (%s) incompatible with build file "
- "ninja_required_version version (%s).",
- kNinjaVersion, version.c_str());
- }
-}
diff --git a/src/version.h b/src/version.h
deleted file mode 100644
index 9d84ecb..0000000
--- a/src/version.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2013 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_VERSION_H_
-#define NINJA_VERSION_H_
-
-#include <string>
-
-/// 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 std::string& version, int* major, int* minor);
-
-/// Check whether \a version is compatible with the current Ninja version,
-/// aborting if not.
-void CheckNinjaVersion(const std::string& required_version);
-
-#endif // NINJA_VERSION_H_
diff --git a/src/win32port.h b/src/win32port.h
deleted file mode 100644
index e542536..0000000
--- a/src/win32port.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2012 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_WIN32PORT_H_
-#define NINJA_WIN32PORT_H_
-
-#if defined(__MINGW32__) || defined(__MINGW64__)
-#ifndef __STDC_FORMAT_MACROS
-#define __STDC_FORMAT_MACROS
-#endif
-#include <inttypes.h>
-#endif
-
-typedef signed short int16_t;
-typedef unsigned short uint16_t;
-/// A 64-bit integer type
-typedef signed long long int64_t;
-typedef unsigned long long uint64_t;
-
-// printf format specifier for uint64_t, from C99.
-#ifndef PRIu64
-#define PRId64 "I64d"
-#define PRIu64 "I64u"
-#define PRIx64 "I64x"
-#endif
-
-#endif // NINJA_WIN32PORT_H_
-
diff --git a/windows/ninja.manifest b/windows/ninja.manifest
deleted file mode 100644
index 47949dd..0000000
--- a/windows/ninja.manifest
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
- <application>
- <windowsSettings>
- <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
- <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
- </windowsSettings>
- </application>
-</assembly>