summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/windows.yml9
-rw-r--r--CMakeLists.txt41
-rwxr-xr-xconfigure.py4
-rw-r--r--doc/manual.asciidoc31
-rw-r--r--src/build.cc264
-rw-r--r--src/build.h115
-rw-r--r--src/build_test.cc47
-rw-r--r--src/disk_interface.cc28
-rw-r--r--src/disk_interface_test.cc6
-rw-r--r--src/graph.cc11
-rw-r--r--src/graph.h8
-rw-r--r--src/line_printer.cc35
-rw-r--r--src/missing_deps.cc194
-rw-r--r--src/missing_deps.h81
-rw-r--r--src/missing_deps_test.cc162
-rw-r--r--src/ninja.cc86
-rw-r--r--src/status.cc266
-rw-r--r--src/status.h117
-rw-r--r--src/status_test.cc35
-rw-r--r--src/util.cc29
-rw-r--r--src/util.h8
-rw-r--r--windows/ninja.manifest8
22 files changed, 1155 insertions, 430 deletions
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 04fc2f6..e4fe7bd 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -19,10 +19,15 @@ jobs:
- name: Build ninja
shell: bash
run: |
- cmake -DCMAKE_BUILD_TYPE=Release -B build
+ cmake -Bbuild
+ cmake --build build --parallel --config Debug
cmake --build build --parallel --config Release
- - name: Test ninja
+ - name: Test ninja (Debug)
+ run: .\ninja_test.exe
+ working-directory: build/Debug
+
+ - name: Test ninja (Release)
run: .\ninja_test.exe
working-directory: build/Release
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39348c9..8de69e8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,17 +19,17 @@ endif()
if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
- string(APPEND CMAKE_CXX_FLAGS " /W4 /wd4100 /wd4267 /wd4706 /wd4702 /wd4244 /GR- /Zc:__cplusplus")
- add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+ 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)
- string(APPEND CMAKE_CXX_FLAGS " -Wno-deprecated")
+ add_compile_options(-Wno-deprecated)
endif()
check_cxx_compiler_flag(-fdiagnostics-color flag_color_diag)
if(flag_color_diag)
- string(APPEND CMAKE_CXX_FLAGS " -fdiagnostics-color")
+ add_compile_options(-fdiagnostics-color)
endif()
endif()
@@ -55,9 +55,10 @@ target_include_directories(libninja-re2c PRIVATE src)
function(check_platform_supports_browse_mode RESULT)
# Make sure the inline.sh script works on this platform.
# It uses the shell commands such as 'od', which may not be available.
+
execute_process(
COMMAND sh -c "echo 'TEST' | src/inline.sh var"
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE inline_result
OUTPUT_QUIET
ERROR_QUIET
@@ -65,12 +66,19 @@ function(check_platform_supports_browse_mode RESULT)
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_include_file_cxx(unistd.h PLATFORM_HAS_UNISTD_HEADER)
set(${RESULT} "${PLATFORM_HAS_UNISTD_HEADER}" PARENT_SCOPE)
+ if(NOT PLATFORM_HAS_UNISTD_HEADER)
+ message(WARNING "browse feature omitted due to missing unistd.h")
+ endif()
+
endfunction()
check_platform_supports_browse_mode(platform_supports_ninja_browse)
@@ -93,8 +101,10 @@ add_library(libninja OBJECT
src/line_printer.cc
src/manifest_parser.cc
src/metrics.cc
+ src/missing_deps.cc
src/parser.cc
src/state.cc
+ src/status.cc
src/string_piece_util.cc
src/util.cc
src/version.cc
@@ -130,13 +140,17 @@ endif()
# On IBM i (identified as "OS400" for compatibility reasons) and AIX, this fixes missing
# PRId64 (and others) at compile time in C++ sources
if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
- string(APPEND CMAKE_CXX_FLAGS " -D__STDC_FORMAT_MACROS")
+ add_compile_definitions(__STDC_FORMAT_MACROS)
endif()
# Main executable is library plus main() function.
add_executable(ninja src/ninja.cc)
target_link_libraries(ninja PRIVATE libninja libninja-re2c)
+if(WIN32)
+ target_sources(ninja PRIVATE windows/ninja.manifest)
+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
@@ -145,11 +159,11 @@ if(platform_supports_ninja_browse)
OUTPUT build/browse_py.h
MAIN_DEPENDENCY src/browse.py
DEPENDS src/inline.sh
- COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/build
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/build
COMMAND src/inline.sh kBrowsePy
< src/browse.py
- > ${CMAKE_BINARY_DIR}/build/browse_py.h
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ > ${PROJECT_BINARY_DIR}/build/browse_py.h
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
VERBATIM
)
@@ -157,8 +171,8 @@ if(platform_supports_ninja_browse)
target_sources(ninja PRIVATE src/browse.cc)
set_source_files_properties(src/browse.cc
PROPERTIES
- OBJECT_DEPENDS "${CMAKE_BINARY_DIR}/build/browse_py.h"
- INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR}"
+ OBJECT_DEPENDS "${PROJECT_BINARY_DIR}/build/browse_py.h"
+ INCLUDE_DIRECTORIES "${PROJECT_BINARY_DIR}"
COMPILE_DEFINITIONS NINJA_PYTHON="python"
)
endif()
@@ -179,6 +193,7 @@ if(BUILD_TESTING)
src/graph_test.cc
src/lexer_test.cc
src/manifest_parser_test.cc
+ src/missing_deps_test.cc
src/ninja_test.cc
src/state_test.cc
src/string_piece_util_test.cc
@@ -205,8 +220,8 @@ if(BUILD_TESTING)
if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
# These tests require more memory than will fit in the standard AIX shared stack/heap (256M)
- target_link_libraries(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000")
- target_link_libraries(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000")
+ 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)
diff --git a/configure.py b/configure.py
index cded265..ffa75c7 100755
--- a/configure.py
+++ b/configure.py
@@ -511,8 +511,10 @@ for name in ['build',
'line_printer',
'manifest_parser',
'metrics',
+ 'missing_deps',
'parser',
'state',
+ 'status',
'string_piece_util',
'util',
'version']:
@@ -577,8 +579,10 @@ for name in ['build_log_test',
'graph_test',
'lexer_test',
'manifest_parser_test',
+ 'missing_deps_test',
'ninja_test',
'state_test',
+ 'status_test',
'string_piece_util_test',
'subprocess_test',
'test',
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index 8fd5d86..a5012b4 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -284,6 +284,21 @@ _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 buidling 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`
@@ -293,6 +308,22 @@ file. _Available since Ninja 1.10._
if they have one). It can be used to know which rule name to pass to
+ninja -t targets rule _name_+ or +ninja -t compdb+.
+`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.
+
Writing your own Ninja files
----------------------------
diff --git a/src/build.cc b/src/build.cc
index 2007d82..fb5890a 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -20,11 +20,6 @@
#include <stdlib.h>
#include <functional>
-#ifdef _WIN32
-#include <fcntl.h>
-#include <io.h>
-#endif
-
#if defined(__SVR4) && defined(__sun)
#include <sys/termios.h>
#endif
@@ -36,7 +31,9 @@
#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"
@@ -78,233 +75,6 @@ bool DryRunCommandRunner::WaitForCommand(Result* result) {
} // namespace
-BuildStatus::BuildStatus(const BuildConfig& config)
- : config_(config), start_time_millis_(GetTimeMillis()), started_edges_(0),
- finished_edges_(0), total_edges_(0), progress_status_format_(NULL),
- current_rate_(config.parallelism) {
- // Don't do anything fancy in verbose mode.
- if (config_.verbosity != BuildConfig::NORMAL)
- printer_.set_smart_terminal(false);
-
- progress_status_format_ = getenv("NINJA_STATUS");
- if (!progress_status_format_)
- progress_status_format_ = "[%f/%t] ";
-}
-
-void BuildStatus::PlanHasTotalEdges(int total) {
- total_edges_ = total;
-}
-
-void BuildStatus::BuildEdgeStarted(const Edge* edge) {
- assert(running_edges_.find(edge) == running_edges_.end());
- int start_time = (int)(GetTimeMillis() - start_time_millis_);
- running_edges_.insert(make_pair(edge, start_time));
- ++started_edges_;
-
- if (edge->use_console() || printer_.is_smart_terminal())
- PrintStatus(edge, kEdgeStarted);
-
- if (edge->use_console())
- printer_.SetConsoleLocked(true);
-}
-
-void BuildStatus::BuildEdgeFinished(Edge* edge,
- bool success,
- const string& output,
- int* start_time,
- int* end_time) {
- int64_t now = GetTimeMillis();
-
- ++finished_edges_;
-
- RunningEdgeMap::iterator i = running_edges_.find(edge);
- *start_time = i->second;
- *end_time = (int)(now - start_time_millis_);
- running_edges_.erase(i);
-
- if (edge->use_console())
- printer_.SetConsoleLocked(false);
-
- if (config_.verbosity == BuildConfig::QUIET)
- return;
-
- if (!edge->use_console())
- PrintStatus(edge, kEdgeFinished);
-
- // 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 BuildStatus::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)
- printer_.PrintOnNewLine("");
-}
-
-void BuildStatus::BuildStarted() {
- overall_rate_.Restart();
- current_rate_.Restart();
-}
-
-void BuildStatus::BuildFinished() {
- printer_.SetConsoleLocked(false);
- printer_.PrintOnNewLine("");
-}
-
-string BuildStatus::FormatProgressStatus(
- const char* progress_status_format, EdgeStatus status) const {
- string out;
- char buf[32];
- int percent;
- for (const char* s = progress_status_format; *s != '\0'; ++s) {
- if (*s == '%') {
- ++s;
- switch (*s) {
- case '%':
- out.push_back('%');
- break;
-
- // Started edges.
- case 's':
- snprintf(buf, sizeof(buf), "%d", started_edges_);
- out += buf;
- break;
-
- // Total edges.
- case 't':
- snprintf(buf, sizeof(buf), "%d", total_edges_);
- out += buf;
- break;
-
- // Running edges.
- case 'r': {
- int running_edges = started_edges_ - finished_edges_;
- // count the edge that just finished as a running edge
- if (status == kEdgeFinished)
- running_edges++;
- snprintf(buf, sizeof(buf), "%d", running_edges);
- out += buf;
- break;
- }
-
- // Unstarted edges.
- case 'u':
- snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
- out += buf;
- break;
-
- // Finished edges.
- case 'f':
- snprintf(buf, sizeof(buf), "%d", finished_edges_);
- out += buf;
- break;
-
- // Overall finished edges per second.
- case 'o':
- overall_rate_.UpdateRate(finished_edges_);
- SnprintfRate(overall_rate_.rate(), buf, "%.1f");
- out += buf;
- break;
-
- // Current rate, average over the last '-j' jobs.
- case 'c':
- current_rate_.UpdateRate(finished_edges_);
- SnprintfRate(current_rate_.rate(), buf, "%.1f");
- out += buf;
- break;
-
- // Percentage
- case 'p':
- percent = (100 * finished_edges_) / total_edges_;
- snprintf(buf, sizeof(buf), "%3i%%", percent);
- out += buf;
- break;
-
- case 'e': {
- double elapsed = overall_rate_.Elapsed();
- snprintf(buf, sizeof(buf), "%.3f", elapsed);
- out += buf;
- break;
- }
-
- default:
- Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
- return "";
- }
- } else {
- out.push_back(*s);
- }
- }
-
- return out;
-}
-
-void BuildStatus::PrintStatus(const Edge* edge, EdgeStatus status) {
- if (config_.verbosity == BuildConfig::QUIET)
- 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(progress_status_format_, status) + to_print;
-
- printer_.Print(to_print,
- force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
-}
-
Plan::Plan(Builder* builder)
: builder_(builder)
, command_edges_(0)
@@ -729,12 +499,12 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
Builder::Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface)
- : state_(state), config_(config),
- plan_(this), disk_interface_(disk_interface),
+ 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) {
- status_ = new BuildStatus(config);
}
Builder::~Builder() {
@@ -761,7 +531,7 @@ void Builder::Cleanup() {
string err;
TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), &err);
if (new_mtime == -1) // Log and ignore Stat() errors.
- Error("%s", err.c_str());
+ status_->Error("%s", err.c_str());
if (!depfile.empty() || (*o)->mtime() != new_mtime)
disk_interface_->RemoveFile((*o)->path());
}
@@ -904,7 +674,10 @@ bool Builder::StartEdge(Edge* edge, string* err) {
if (edge->is_phony())
return true;
- status_->BuildEdgeStarted(edge);
+ int64_t start_time_millis = GetTimeMillis() - start_time_millis_;
+ running_edges_.insert(make_pair(edge, start_time_millis));
+
+ status_->BuildEdgeStarted(edge, start_time_millis);
// Create directories necessary for outputs.
// XXX: this will block; do we care?
@@ -957,9 +730,14 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
}
}
- int start_time, end_time;
- status_->BuildEdgeFinished(edge, result->success(), result->output,
- &start_time, &end_time);
+ 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, end_time_millis, result->success(),
+ result->output);
// The rest of this function only applies to successful commands.
if (!result->success()) {
@@ -1028,8 +806,8 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
disk_interface_->RemoveFile(rspfile);
if (scan_.build_log()) {
- if (!scan_.build_log()->RecordCommand(edge, start_time, end_time,
- output_mtime)) {
+ if (!scan_.build_log()->RecordCommand(edge, start_time_millis,
+ end_time_millis, output_mtime)) {
*err = string("Error writing to build log: ") + strerror(errno);
return false;
}
diff --git a/src/build.h b/src/build.h
index 0a68478..06823c2 100644
--- a/src/build.h
+++ b/src/build.h
@@ -25,17 +25,15 @@
#include "depfile_parser.h"
#include "graph.h" // XXX needed for DependencyScan; should rearrange.
#include "exit_status.h"
-#include "line_printer.h"
-#include "metrics.h"
#include "util.h" // int64_t
struct BuildLog;
-struct BuildStatus;
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.
@@ -179,7 +177,8 @@ struct BuildConfig {
struct Builder {
Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface);
+ DiskInterface* disk_interface, Status* status,
+ int64_t start_time_millis);
~Builder();
/// Clean up after interrupted commands by deleting output files.
@@ -220,118 +219,26 @@ struct Builder {
#else
std::unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
#endif
- BuildStatus* status_;
+ 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);
- 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
-};
-
-/// Tracks the status of a build: completion fraction, printing updates.
-struct BuildStatus {
- explicit BuildStatus(const BuildConfig& config);
- void PlanHasTotalEdges(int total);
- void BuildEdgeStarted(const Edge* edge);
- void BuildEdgeFinished(Edge* edge, bool success, const std::string& output,
- int* start_time, int* end_time);
- void BuildLoadDyndeps();
- void BuildStarted();
- void BuildFinished();
-
- enum EdgeStatus {
- kEdgeStarted,
- kEdgeFinished,
- };
-
- /// Format the progress status string by replacing the placeholders.
- /// See the user manual for more information about the available
- /// placeholders.
- /// @param progress_status_format The format of the progress status.
- /// @param status The status of the edge.
- std::string FormatProgressStatus(const char* progress_status_format,
- EdgeStatus status) const;
-
- private:
- void PrintStatus(const Edge* edge, EdgeStatus status);
-
- const BuildConfig& config_;
-
- /// Time the build started.
- int64_t start_time_millis_;
-
- int started_edges_, finished_edges_, total_edges_;
-
/// Map of running edge to time the edge started running.
typedef std::map<const Edge*, int> RunningEdgeMap;
RunningEdgeMap running_edges_;
- /// Prints progress output.
- LinePrinter printer_;
-
- /// The custom progress status format to use.
- const char* progress_status_format_;
-
- template<size_t S>
- void SnprintfRate(double rate, char(&buf)[S], const char* format) const {
- if (rate == -1)
- snprintf(buf, S, "?");
- else
- snprintf(buf, S, format, rate);
- }
-
- struct RateInfo {
- RateInfo() : rate_(-1) {}
-
- void Restart() { stopwatch_.Restart(); }
- double Elapsed() const { return stopwatch_.Elapsed(); }
- double rate() { return rate_; }
-
- void UpdateRate(int edges) {
- if (edges && stopwatch_.Elapsed())
- rate_ = edges / stopwatch_.Elapsed();
- }
-
- private:
- double rate_;
- Stopwatch stopwatch_;
- };
+ /// Time the build started.
+ int64_t start_time_millis_;
- struct SlidingRateInfo {
- SlidingRateInfo(int n) : rate_(-1), N(n), last_update_(-1) {}
-
- void Restart() { stopwatch_.Restart(); }
- double rate() { return rate_; }
-
- void UpdateRate(int update_hint) {
- if (update_hint == last_update_)
- return;
- last_update_ = update_hint;
-
- if (times_.size() == N)
- times_.pop();
- times_.push(stopwatch_.Elapsed());
- if (times_.back() != times_.front())
- rate_ = times_.size() / (times_.back() - times_.front());
- }
-
- private:
- double rate_;
- Stopwatch stopwatch_;
- const size_t N;
- std::queue<double> times_;
- int last_update_;
- };
+ DiskInterface* disk_interface_;
+ DependencyScan scan_;
- mutable RateInfo overall_rate_;
- mutable SlidingRateInfo current_rate_;
+ // 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_test.cc b/src/build_test.cc
index 0baabc4..f58a7de 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -19,6 +19,7 @@
#include "build_log.h"
#include "deps_log.h"
#include "graph.h"
+#include "status.h"
#include "test.h"
using namespace std;
@@ -485,14 +486,13 @@ struct FakeCommandRunner : public CommandRunner {
};
struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
- BuildTest() : config_(MakeConfig()), command_runner_(&fs_),
- builder_(&state_, config_, NULL, NULL, &fs_),
- status_(config_) {
+ 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_),
- builder_(&state_, config_, NULL, log, &fs_), status_(config_) {}
+ : config_(MakeConfig()), command_runner_(&fs_), status_(config_),
+ builder_(&state_, config_, NULL, log, &fs_, &status_, 0) {}
virtual void SetUp() {
StateTestWithBuiltinRules::SetUp();
@@ -532,9 +532,8 @@ struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
BuildConfig config_;
FakeCommandRunner command_runner_;
VirtualFileSystem fs_;
+ StatusPrinter status_;
Builder builder_;
-
- BuildStatus status_;
};
void BuildTest::RebuildTarget(const string& target, const char* manifest,
@@ -563,7 +562,7 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest,
pdeps_log = &deps_log;
}
- Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_);
+ Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_, &status_, 0);
EXPECT_TRUE(builder.AddTarget(target, &err));
command_runner_.commands_ran_.clear();
@@ -1400,8 +1399,8 @@ TEST_F(BuildWithLogTest, RestatTest) {
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- EXPECT_EQ("[3/3]", builder_.status_->FormatProgressStatus("[%s/%t]",
- BuildStatus::kEdgeStarted));
+ EXPECT_EQ(3u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(3u, builder_.plan_.command_edge_count());
command_runner_.commands_ran_.clear();
state_.Reset();
@@ -1843,14 +1842,12 @@ TEST_F(BuildTest, StatusFormatElapsed) {
status_.BuildStarted();
// Before any task is done, the elapsed time must be zero.
EXPECT_EQ("[%/e0.000]",
- status_.FormatProgressStatus("[%%/e%e]",
- BuildStatus::kEdgeStarted));
+ 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]",
- BuildStatus::kEdgeStarted));
+ status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", 0));
}
TEST_F(BuildTest, FailedDepsParse) {
@@ -2120,7 +2117,7 @@ TEST_F(BuildWithDepsLogTest, Straightforward) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ 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);
@@ -2150,7 +2147,7 @@ TEST_F(BuildWithDepsLogTest, Straightforward) {
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_);
+ 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));
@@ -2191,7 +2188,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ 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);
@@ -2220,7 +2217,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
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_);
+ 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));
@@ -2256,7 +2253,7 @@ TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) {
// The deps log is NULL in dry runs.
config_.dry_run = true;
- Builder builder(&state, config_, NULL, NULL, &fs_);
+ Builder builder(&state, config_, NULL, NULL, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
@@ -2314,7 +2311,7 @@ TEST_F(BuildWithDepsLogTest, RestatDepfileDependencyDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ 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);
@@ -2340,7 +2337,7 @@ TEST_F(BuildWithDepsLogTest, RestatDepfileDependencyDepsLog) {
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_);
+ 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));
@@ -2373,7 +2370,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ 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);
@@ -2394,7 +2391,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
Edge* edge = state.edges_.back();
@@ -2435,7 +2432,7 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ 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);
@@ -2458,7 +2455,7 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
Edge* edge = state.edges_.back();
diff --git a/src/disk_interface.cc b/src/disk_interface.cc
index 49af001..a9497cb 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -265,6 +265,30 @@ FileReader::Status RealDiskInterface::ReadFile(const string& path,
}
int RealDiskInterface::RemoveFile(const string& path) {
+#ifdef _WIN32
+ DWORD attributes = GetFileAttributes(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.
+ SetFileAttributes(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
+ }
+ if (!DeleteFile(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:
@@ -273,9 +297,9 @@ int RealDiskInterface::RemoveFile(const string& path) {
Error("remove(%s): %s", path.c_str(), strerror(errno));
return -1;
}
- } else {
- return 0;
}
+#endif
+ return 0;
}
void RealDiskInterface::AllowStatCache(bool allow) {
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index 066c770..b424243 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -211,6 +211,12 @@ TEST_F(DiskInterfaceTest, RemoveFile) {
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
}
struct StatTest : public StateTestWithBuiltinRules,
diff --git a/src/graph.cc b/src/graph.cc
index 78d0d49..90e8d08 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -588,13 +588,18 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
}
}
+ return ProcessDepfileDeps(edge, &depfile.ins_, err);
+}
+
+bool ImplicitDepLoader::ProcessDepfileDeps(
+ Edge* edge, std::vector<StringPiece>* depfile_ins, std::string* err) {
// Preallocate space in edge->inputs_ to be filled in below.
vector<Node*>::iterator implicit_dep =
- PreallocateSpace(edge, depfile.ins_.size());
+ PreallocateSpace(edge, depfile_ins->size());
// Add all its in-edges.
- for (vector<StringPiece>::iterator i = depfile.ins_.begin();
- i != depfile.ins_.end(); ++i, ++implicit_dep) {
+ for (std::vector<StringPiece>::iterator i = depfile_ins->begin();
+ i != depfile_ins->end(); ++i, ++implicit_dep) {
uint64_t slash_bits;
if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits,
err))
diff --git a/src/graph.h b/src/graph.h
index 8c51782..6756378 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -247,7 +247,13 @@ struct ImplicitDepLoader {
return deps_log_;
}
- private:
+ 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);
diff --git a/src/line_printer.cc b/src/line_printer.cc
index 68c58ad..3138960 100644
--- a/src/line_printer.cc
+++ b/src/line_printer.cc
@@ -87,22 +87,27 @@ void LinePrinter::Print(string to_print, LineType type) {
GetConsoleScreenBufferInfo(console_, &csbi);
to_print = ElideMiddle(to_print, static_cast<size_t>(csbi.dwSize.X));
- // 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;
+ 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);
}
- 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.
diff --git a/src/missing_deps.cc b/src/missing_deps.cc
new file mode 100644
index 0000000..eaa3f73
--- /dev/null
+++ b/src/missing_deps.cc
@@ -0,0 +1,194 @@
+// 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;
+ if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits,
+ err))
+ return false;
+ 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 dependecy
+ // 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
new file mode 100644
index 0000000..ae57074
--- /dev/null
+++ b/src/missing_deps.h
@@ -0,0 +1,81 @@
+// 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>
+
+#if __cplusplus >= 201103L
+#include <unordered_map>
+#endif
+
+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:
+#if __cplusplus >= 201103L
+ using InnerAdjacencyMap = std::unordered_map<Edge*, bool>;
+ using AdjacencyMap = std::unordered_map<Edge*, InnerAdjacencyMap>;
+#else
+ typedef std::map<Edge*, bool> InnerAdjacencyMap;
+ typedef std::map<Edge*, InnerAdjacencyMap> AdjacencyMap;
+#endif
+ AdjacencyMap adjacency_map_;
+};
+
+#endif // NINJA_MISSING_DEPS_H_
diff --git a/src/missing_deps_test.cc b/src/missing_deps_test.cc
new file mode 100644
index 0000000..7b62e6c
--- /dev/null
+++ b/src/missing_deps_test.cc
@@ -0,0 +1,162 @@
+// 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, beacuse
+ // 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/ninja.cc b/src/ninja.cc
index eb97320..45fc8ea 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -37,12 +37,15 @@
#include "deps_log.h"
#include "clean.h"
#include "debug_flags.h"
+#include "depfile_parser.h"
#include "disk_interface.h"
#include "graph.h"
#include "graphviz.h"
#include "manifest_parser.h"
#include "metrics.h"
+#include "missing_deps.h"
#include "state.h"
+#include "status.h"
#include "util.h"
#include "version.h"
@@ -82,7 +85,8 @@ struct Options {
/// 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) {}
+ ninja_command_(ninja_command), config_(config),
+ start_time_millis_(GetTimeMillis()) {}
/// Command line used to run Ninja.
const char* ninja_command_;
@@ -117,6 +121,7 @@ struct NinjaMain : public BuildLogUser {
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[]);
@@ -128,6 +133,7 @@ struct NinjaMain : public BuildLogUser {
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[]);
/// Open the build log.
/// @return LOAD_ERROR on error.
@@ -144,11 +150,11 @@ struct NinjaMain : public BuildLogUser {
/// Rebuild the manifest, if necessary.
/// Fills in \a err on error.
/// @return true if the manifest was rebuilt.
- bool RebuildManifest(const char* input_file, string* err);
+ bool RebuildManifest(const char* input_file, string* err, Status* status);
/// Build the targets listed on the command line.
/// @return an exit code.
- int RunBuild(int argc, char** argv);
+ int RunBuild(int argc, char** argv, Status* status);
/// Dump the output requested by '-d stats'.
void DumpMetrics();
@@ -172,6 +178,8 @@ struct NinjaMain : public BuildLogUser {
Error("%s", err.c_str()); // Log and ignore Stat() errors.
return mtime == 0;
}
+
+ int64_t start_time_millis_;
};
/// Subtools, accessible via "-t foo".
@@ -240,7 +248,8 @@ int GuessParallelism() {
/// Rebuild the build manifest, if necessary.
/// Returns true if the manifest was rebuilt.
-bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
+bool NinjaMain::RebuildManifest(const char* input_file, string* err,
+ Status* status) {
string path = input_file;
uint64_t slash_bits; // Unused because this path is only used for lookup.
if (!CanonicalizePath(&path, &slash_bits, err))
@@ -249,7 +258,8 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
if (!node)
return false;
- Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
+ status, start_time_millis_);
if (!builder.AddTarget(node, err))
return false;
@@ -523,6 +533,26 @@ int NinjaMain::ToolDeps(const Options* options, int argc, char** argv) {
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) {
@@ -612,6 +642,17 @@ int NinjaMain::ToolRules(const Options* options, int argc, char* argv[]) {
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)
@@ -960,6 +1001,8 @@ const Tool* ChooseTool(const string& tool_name) {
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
{ "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",
@@ -978,6 +1021,10 @@ const Tool* ChooseTool(const string& tool_name) {
Tool::RUN_AFTER_LOGS, &NinjaMain::ToolCleanDead },
{ "urtle", NULL,
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle },
+#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 }
};
@@ -985,7 +1032,7 @@ const Tool* ChooseTool(const string& tool_name) {
printf("ninja subtools:\n");
for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
if (tool->desc)
- printf("%10s %s\n", tool->name, tool->desc);
+ printf("%11s %s\n", tool->name, tool->desc);
}
return NULL;
}
@@ -1189,21 +1236,22 @@ bool NinjaMain::EnsureBuildDirExists() {
return true;
}
-int NinjaMain::RunBuild(int argc, char** argv) {
+int NinjaMain::RunBuild(int argc, char** argv, Status* status) {
string err;
vector<Node*> targets;
if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
return 1;
}
disk_interface_.AllowStatCache(g_experimental_statcache);
- Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
+ status, start_time_millis_);
for (size_t i = 0; i < targets.size(); ++i) {
if (!builder.AddTarget(targets[i], &err)) {
if (!err.empty()) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
return 1;
} else {
// Added a target that is already up-to-date; not really
@@ -1216,12 +1264,12 @@ int NinjaMain::RunBuild(int argc, char** argv) {
disk_interface_.AllowStatCache(false);
if (builder.AlreadyUpToDate()) {
- printf("ninja: no work to do.\n");
+ status->Info("no work to do.");
return 0;
}
if (!builder.Build(&err)) {
- printf("ninja: build stopped: %s.\n", err.c_str());
+ status->Info("build stopped: %s.", err.c_str());
if (err.find("interrupted by user") != string::npos) {
return 2;
}
@@ -1359,6 +1407,8 @@ NORETURN void real_main(int argc, char** argv) {
if (exit_code >= 0)
exit(exit_code);
+ Status* status = new StatusPrinter(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
@@ -1366,7 +1416,7 @@ NORETURN void real_main(int argc, char** argv) {
// 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)
- printf("ninja: Entering directory `%s'\n", options.working_dir);
+ status->Info("Entering directory `%s'", options.working_dir);
if (chdir(options.working_dir) < 0) {
Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno));
}
@@ -1394,7 +1444,7 @@ NORETURN void real_main(int argc, char** argv) {
ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts);
string err;
if (!parser.Load(options.input_file, &err)) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
exit(1);
}
@@ -1411,7 +1461,7 @@ NORETURN void real_main(int argc, char** argv) {
exit((ninja.*options.tool->func)(&options, argc, argv));
// Attempt to rebuild the manifest before building anything else
- if (ninja.RebuildManifest(options.input_file, &err)) {
+ if (ninja.RebuildManifest(options.input_file, &err, status)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return immediately.
if (config.dry_run)
@@ -1419,17 +1469,17 @@ NORETURN void real_main(int argc, char** argv) {
// Start the build over with the new manifest.
continue;
} else if (!err.empty()) {
- Error("rebuilding '%s': %s", options.input_file, err.c_str());
+ status->Error("rebuilding '%s': %s", options.input_file, err.c_str());
exit(1);
}
- int result = ninja.RunBuild(argc, argv);
+ int result = ninja.RunBuild(argc, argv, status);
if (g_metrics)
ninja.DumpMetrics();
exit(result);
}
- Error("manifest '%s' still dirty after %d tries\n",
+ status->Error("manifest '%s' still dirty after %d tries",
options.input_file, kCycleLimit);
exit(1);
}
diff --git a/src/status.cc b/src/status.cc
new file mode 100644
index 0000000..171cbeb
--- /dev/null
+++ b/src/status.cc
@@ -0,0 +1,266 @@
+// 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 <stdarg.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#include "debug_flags.h"
+
+using namespace std;
+
+StatusPrinter::StatusPrinter(const BuildConfig& config)
+ : config_(config),
+ started_edges_(0), finished_edges_(0), total_edges_(0), running_edges_(0),
+ time_millis_(0), progress_status_format_(NULL),
+ current_rate_(config.parallelism) {
+
+ // Don't do anything fancy in verbose mode.
+ if (config_.verbosity != BuildConfig::NORMAL)
+ printer_.set_smart_terminal(false);
+
+ progress_status_format_ = getenv("NINJA_STATUS");
+ if (!progress_status_format_)
+ progress_status_format_ = "[%f/%t] ";
+}
+
+void StatusPrinter::PlanHasTotalEdges(int total) {
+ total_edges_ = total;
+}
+
+void StatusPrinter::BuildEdgeStarted(const Edge* edge,
+ int64_t start_time_millis) {
+ ++started_edges_;
+ ++running_edges_;
+ time_millis_ = start_time_millis;
+
+ if (edge->use_console() || printer_.is_smart_terminal())
+ PrintStatus(edge, start_time_millis);
+
+ if (edge->use_console())
+ printer_.SetConsoleLocked(true);
+}
+
+void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
+ bool success, const string& output) {
+ time_millis_ = end_time_millis;
+ ++finished_edges_;
+
+ if (edge->use_console())
+ printer_.SetConsoleLocked(false);
+
+ if (config_.verbosity == BuildConfig::QUIET)
+ return;
+
+ if (!edge->use_console())
+ PrintStatus(edge, end_time_millis);
+
+ --running_edges_;
+
+ // 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)
+ printer_.PrintOnNewLine("");
+}
+
+void StatusPrinter::BuildStarted() {
+ started_edges_ = 0;
+ finished_edges_ = 0;
+ running_edges_ = 0;
+}
+
+void StatusPrinter::BuildFinished() {
+ printer_.SetConsoleLocked(false);
+ printer_.PrintOnNewLine("");
+}
+
+string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
+ int64_t time_millis) const {
+ string out;
+ char buf[32];
+ for (const char* s = progress_status_format; *s != '\0'; ++s) {
+ if (*s == '%') {
+ ++s;
+ switch (*s) {
+ case '%':
+ out.push_back('%');
+ break;
+
+ // Started edges.
+ case 's':
+ snprintf(buf, sizeof(buf), "%d", started_edges_);
+ out += buf;
+ break;
+
+ // Total edges.
+ case 't':
+ snprintf(buf, sizeof(buf), "%d", total_edges_);
+ out += buf;
+ break;
+
+ // Running edges.
+ case 'r': {
+ snprintf(buf, sizeof(buf), "%d", running_edges_);
+ out += buf;
+ break;
+ }
+
+ // Unstarted edges.
+ case 'u':
+ snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
+ out += buf;
+ break;
+
+ // Finished edges.
+ case 'f':
+ snprintf(buf, sizeof(buf), "%d", finished_edges_);
+ out += buf;
+ break;
+
+ // Overall finished edges per second.
+ case 'o':
+ SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
+ out += buf;
+ break;
+
+ // Current rate, average over the last '-j' jobs.
+ case 'c':
+ current_rate_.UpdateRate(finished_edges_, time_millis_);
+ SnprintfRate(current_rate_.rate(), buf, "%.1f");
+ out += buf;
+ break;
+
+ // Percentage
+ case 'p': {
+ int percent = (100 * finished_edges_) / total_edges_;
+ snprintf(buf, sizeof(buf), "%3i%%", percent);
+ out += buf;
+ break;
+ }
+
+ case 'e': {
+ snprintf(buf, sizeof(buf), "%.3f", time_millis_ / 1e3);
+ out += buf;
+ 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)
+ 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(progress_status_format_, time_millis)
+ + to_print;
+
+ printer_.Print(to_print,
+ force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
+}
+
+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
new file mode 100644
index 0000000..e211ba3
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,117 @@
+// 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 <map>
+#include <string>
+
+#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, int64_t start_time_millis) = 0;
+ virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
+ 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);
+ virtual void PlanHasTotalEdges(int total);
+ virtual void BuildEdgeStarted(const Edge* edge, int64_t start_time_millis);
+ virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
+ bool success, const std::string& output);
+ virtual void BuildLoadDyndeps();
+ virtual void BuildStarted();
+ virtual void BuildFinished();
+
+ virtual void Info(const char* msg, ...);
+ virtual void Warning(const char* msg, ...);
+ virtual void Error(const char* msg, ...);
+
+ 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);
+
+ 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_;
+
+ template<size_t S>
+ void SnprintfRate(double rate, char(&buf)[S], const char* format) const {
+ if (rate == -1)
+ snprintf(buf, S, "?");
+ else
+ snprintf(buf, S, format, rate);
+ }
+
+ 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_;
+ };
+
+ mutable SlidingRateInfo current_rate_;
+};
+
+#endif // NINJA_STATUS_H_
diff --git a/src/status_test.cc b/src/status_test.cc
new file mode 100644
index 0000000..6e42490
--- /dev/null
+++ b/src/status_test.cc
@@ -0,0 +1,35 @@
+// 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 "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/util.cc b/src/util.cc
index 1e0d147..b40a636 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -78,24 +78,45 @@ void Fatal(const char* msg, ...) {
#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;
- fprintf(stderr, "ninja: warning: ");
va_start(ap, msg);
- vfprintf(stderr, msg, ap);
+ 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;
- fprintf(stderr, "ninja: error: ");
va_start(ap, msg);
- vfprintf(stderr, msg, ap);
+ Error(msg, ap);
va_end(ap);
+}
+
+void Info(const char* msg, va_list ap) {
+ fprintf(stderr, "ninja: ");
+ vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
}
+void Info(const char* msg, ...) {
+ va_list ap;
+ va_start(ap, msg);
+ Info(msg, ap);
+ va_end(ap);
+}
+
bool CanonicalizePath(string* path, uint64_t* slash_bits, string* err) {
METRIC_RECORD("canonicalize str");
size_t len = path->size();
diff --git a/src/util.h b/src/util.h
index 4e6ebb8..15414e1 100644
--- a/src/util.h
+++ b/src/util.h
@@ -21,6 +21,8 @@
#include <stdint.h>
#endif
+#include <stdarg.h>
+
#include <string>
#include <vector>
@@ -49,9 +51,15 @@ NORETURN void Fatal(const char* msg, ...);
/// 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);
/// Canonicalize a path like "foo/../bar.h" into just "bar.h".
/// |slash_bits| has bits set starting from lowest for a backslash that was
diff --git a/windows/ninja.manifest b/windows/ninja.manifest
new file mode 100644
index 0000000..dab929e
--- /dev/null
+++ b/windows/ninja.manifest
@@ -0,0 +1,8 @@
+<?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>
+ </windowsSettings>
+ </application>
+</assembly>