--- /dev/null
+From afcd4a146fb82843f6ff695f89504ce4ca65ddfd Mon Sep 17 00:00:00 2001
+From: David 'Digit' Turner <digit+github@google.com>
+Date: Sun, 12 May 2024 23:45:28 +0200
+Subject: [PATCH] configure.py: Support --gtest-source-dir to build tests.
+
+Allow the Ninja build plan generated by configure.py to
+build `ninja_test` by compiling GoogleTest from source if
+the path to the library if passed through the new option
+`--gtest-source-dir` or the GTEST_SOURCE_DIR environment
+variable.
+
+For simplicity, probing for an installed version of the
+library, and linking to it, is not supported (use the
+CMake build for this).
+
+This also removes the obsolete `--gtest-dir` option.
+
++ Update README.md
+
+Fixes #2447
+---
+ README.md | 13 ++++++++
+ configure.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 95 insertions(+), 1 deletion(-)
+
+--- a/README.md
++++ b/README.md
+@@ -34,6 +34,19 @@ via CMake. For more details see
+ This will generate the `ninja` binary and a `build.ninja` file you can now use
+ to build Ninja with itself.
+
++If you have a GoogleTest source directory, you can build the tests
++by passing its path with `--gtest-source-dir=PATH` option, or the
++`GTEST_SOURCE_DIR` environment variable, e.g.:
++
++```
++./configure.py --bootstrap --gtest-source-dir=/path/to/googletest
++./ninja all # build ninja_test and other auxiliary binaries
++./ninja_test` # run the unit-test suite.
++```
++
++Use the CMake build below if you want to use a preinstalled binary
++version of the library.
++
+ ### CMake
+
+ ```
+--- a/configure.py
++++ b/configure.py
+@@ -213,7 +213,10 @@ parser.add_option('--debug', action='sto
+ 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('--gtest-source-dir', metavar='PATH',
++ help='Path to GoogleTest source directory. If not provided ' +
++ 'GTEST_SOURCE_DIR will be probed in the environment. ' +
++ 'Tests will not be built without a value.')
+ parser.add_option('--with-python', metavar='EXE',
+ help='use EXE as the Python interpreter',
+ default=os.path.basename(sys.executable))
+@@ -425,6 +428,7 @@ n.variable('cflags', ' '.join(shell_esca
+ 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():
+@@ -582,6 +586,83 @@ if options.bootstrap:
+ # build.ninja file.
+ n = ninja_writer
+
++# Build the ninja_test executable only if the GTest source directory
++# is provided explicitly. Either from the environment with GTEST_SOURCE_DIR
++# or with the --gtest-source-dir command-line option.
++#
++# Do not try to look for an installed binary version, and link against it
++# because doing so properly is platform-specific (use the CMake build for
++# this).
++if options.gtest_source_dir:
++ gtest_src_dir = options.gtest_source_dir
++else:
++ gtest_src_dir = os.environ.get('GTEST_SOURCE_DIR')
++
++if gtest_src_dir:
++ # Verify GoogleTest source directory, and add its include directory
++ # to the global include search path (even for non-test sources) to
++ # keep the build plan generation simple.
++ gtest_all_cc = os.path.join(gtest_src_dir, 'googletest', 'src', 'gtest-all.cc')
++ if not os.path.exists(gtest_all_cc):
++ print('ERROR: Missing GoogleTest source file: %s' % gtest_all_cc)
++ sys.exit(1)
++
++ n.comment('Tests all build into ninja_test executable.')
++
++ # Test-specific version of cflags, must include the GoogleTest
++ # include directory. Also GoogleTest can only build with a C++14 compiler.
++ test_cflags = [f.replace('std=c++11', 'std=c++14') for f in cflags]
++ test_cflags.append('-I' + os.path.join(gtest_src_dir, 'googletest', 'include'))
++
++ test_variables = [('cflags', test_cflags)]
++ if platform.is_msvc():
++ test_variables += [('pdb', 'ninja_test.pdb')]
++
++ test_names = [
++ 'build_log_test',
++ 'build_test',
++ 'clean_test',
++ 'clparser_test',
++ 'depfile_parser_test',
++ 'deps_log_test',
++ 'disk_interface_test',
++ 'dyndep_parser_test',
++ 'edit_distance_test',
++ 'graph_test',
++ 'json_test',
++ 'lexer_test',
++ 'manifest_parser_test',
++ 'ninja_test',
++ 'state_test',
++ 'string_piece_util_test',
++ 'subprocess_test',
++ 'test',
++ 'util_test',
++ ]
++ if platform.is_windows():
++ test_names += [
++ 'includes_normalize_test',
++ 'msvc_helper_test',
++ ]
++
++ objs = []
++ for name in test_names:
++ objs += cxx(name, variables=test_variables)
++
++ # Build GTest as a monolithic source file.
++ # This requires one extra include search path, so replace the
++ # value of 'cflags' in our list.
++ gtest_all_variables = test_variables[1:] + [
++ ('cflags', test_cflags + ['-I' + os.path.join(gtest_src_dir, 'googletest') ]),
++ ]
++ # Do not use cxx() directly to ensure the object file is under $builddir.
++ objs += n.build(built('gtest_all' + objext), 'cxx', gtest_all_cc, variables=gtest_all_variables)
++
++ 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:
http://make.mad-scientist.net/papers/jobserver-implementation/
-Fixes https://github.com/ninja-build/ninja/issues/1139
----
- configure.py | 2 +
- src/build.cc | 63 ++++++++----
- src/build.h | 3 +
- src/tokenpool-gnu-make.cc | 211 ++++++++++++++++++++++++++++++++++++++
- src/tokenpool-none.cc | 27 +++++
- src/tokenpool.h | 26 +++++
- 6 files changed, 310 insertions(+), 22 deletions(-)
- create mode 100644 src/tokenpool-gnu-make.cc
- create mode 100644 src/tokenpool-none.cc
- create mode 100644 src/tokenpool.h
-
--- a/configure.py
+++ b/configure.py
-@@ -519,11 +519,13 @@ for name in ['build',
+@@ -543,11 +543,13 @@ for name in ['build',
'state',
'status',
'string_piece_util',
'includes_normalize-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
-@@ -532,7 +534,9 @@ if platform.is_windows():
+@@ -556,7 +558,9 @@ if platform.is_windows():
objs += cxx('minidump-win32', variables=cxxvariables)
objs += cc('getopt')
else:
if platform.is_aix():
objs += cc('getopt')
if platform.is_msvc():
-@@ -590,6 +594,7 @@ for name in ['build_log_test',
- 'string_piece_util_test',
- 'subprocess_test',
- 'test',
-+ 'tokenpool_test',
- 'util_test']:
- objs += cxx(name, variables=cxxvariables)
- if platform.is_windows():
+@@ -639,6 +643,7 @@ if gtest_src_dir:
+ 'string_piece_util_test',
+ 'subprocess_test',
+ 'test',
++ 'tokenpool_test',
+ 'util_test',
+ ]
+ if platform.is_windows():
--- a/src/build.cc
+++ b/src/build.cc
-@@ -35,6 +35,7 @@
+@@ -39,6 +39,7 @@
#include "state.h"
#include "status.h"
#include "subprocess.h"
#include "util.h"
using namespace std;
-@@ -47,8 +48,9 @@ struct DryRunCommandRunner : public Comm
+@@ -50,24 +51,29 @@ struct DryRunCommandRunner : public Comm
+ virtual ~DryRunCommandRunner() {}
// Overridden from CommandRunner:
- virtual bool CanRunMore() const;
+- virtual size_t CanRunMore() const;
++ virtual size_t CanRunMore();
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
private:
queue<Edge*> finished_;
-@@ -58,12 +60,16 @@ bool DryRunCommandRunner::CanRunMore() c
- return true;
+ };
+
+-size_t DryRunCommandRunner::CanRunMore() const {
++size_t DryRunCommandRunner::CanRunMore() {
+ return SIZE_MAX;
}
+bool DryRunCommandRunner::AcquireToken() {
if (finished_.empty())
return false;
-@@ -149,7 +155,7 @@ void Plan::EdgeWanted(const Edge* edge)
+@@ -160,7 +166,7 @@ void Plan::EdgeWanted(const Edge* edge)
}
Edge* Plan::FindWork() {
- if (ready_.empty())
+ if (!more_ready())
return NULL;
- EdgeSet::iterator e = ready_.begin();
- Edge* edge = *e;
-@@ -448,19 +454,39 @@ void Plan::Dump() const {
+
+ Edge* work = ready_.top();
+@@ -595,19 +601,39 @@ void Plan::Dump() const {
}
struct RealCommandRunner : public CommandRunner {
- explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
- virtual ~RealCommandRunner() {}
+- virtual size_t CanRunMore() const;
+ explicit RealCommandRunner(const BuildConfig& config);
+ virtual ~RealCommandRunner();
- virtual bool CanRunMore() const;
++ virtual size_t CanRunMore();
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
vector<Edge*> RealCommandRunner::GetActiveEdges() {
vector<Edge*> edges;
for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
-@@ -471,14 +497,23 @@ vector<Edge*> RealCommandRunner::GetActi
+@@ -618,9 +644,11 @@ vector<Edge*> RealCommandRunner::GetActi
void RealCommandRunner::Abort() {
subprocs_.Clear();
+ tokens_->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 parallelism_limit_not_reached =
-+ tokens_ || // ignore config_.parallelism
-+ ((int) (subprocs_.running_.size() +
-+ subprocs_.finished_.size()) < config_.parallelism);
-+ return parallelism_limit_not_reached
-+ && (subprocs_.running_.empty() ||
-+ (max_load_average_ <= 0.0f ||
-+ GetLoadAverage() < max_load_average_));
-+}
+-size_t RealCommandRunner::CanRunMore() const {
++size_t RealCommandRunner::CanRunMore() {
+ size_t subproc_number =
+ subprocs_.running_.size() + subprocs_.finished_.size();
+
+@@ -635,6 +663,13 @@ size_t RealCommandRunner::CanRunMore() c
+ if (capacity < 0)
+ capacity = 0;
+
++ if (tokens_) {
++ if (AcquireToken())
++ return SIZE_MAX;
++ else
++ capacity = 0;
++ }
+
-+bool RealCommandRunner::AcquireToken() {
-+ return (!tokens_ || tokens_->Acquire());
+ if (capacity == 0 && subprocs_.running_.empty())
+ // Ensure that we make progress.
+ capacity = 1;
+@@ -642,24 +677,42 @@ size_t RealCommandRunner::CanRunMore() c
+ return capacity;
}
++bool RealCommandRunner::AcquireToken() {
++ return (!tokens_ || tokens_->Acquire());
++}
++
bool RealCommandRunner::StartCommand(Edge* edge) {
-@@ -486,19 +521,33 @@ bool RealCommandRunner::StartCommand(Edg
+ string command = edge->EvaluateCommand();
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
if (!subproc)
return false;
result->status = subproc->Finish();
result->output = subproc->GetOutput();
-@@ -620,38 +669,43 @@ bool Builder::Build(string* err) {
- // command runner.
+@@ -790,7 +843,8 @@ bool Builder::Build(string* err) {
// 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->GetBindingBool("generator")) {
-+ // See if we can start any more commands...
-+ bool can_run_more =
-+ failures_allowed &&
-+ plan_.more_ready() &&
-+ command_runner_->CanRunMore();
-+
-+ // ... but we also need a token to do that.
-+ if (can_run_more && command_runner_->AcquireToken()) {
-+ Edge* edge = plan_.FindWork();
-+ if (edge->GetBindingBool("generator")) {
- scan_.build_log()->Close();
- }
-
-- if (!StartEdge(edge, err)) {
-+ 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;
- }
--
-- 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;
-+ } else {
-+ ++pending_commands;
- }
-+
-+ // We made some progress; go back to the main loop.
-+ continue;
- }
-
+ // See if we can start any more commands.
+- if (failures_allowed) {
++ bool can_run_more = failures_allowed && plan_.more_ready();
++ if (can_run_more) {
+ size_t capacity = command_runner_->CanRunMore();
+ while (capacity > 0) {
+ Edge* edge = plan_.FindWork();
+@@ -833,7 +887,7 @@ bool Builder::Build(string* err) {
// See if we can reap any finished commands.
if (pending_commands) {
CommandRunner::Result result;
result.status == ExitInterrupted) {
Cleanup();
status_->BuildFinished();
-@@ -659,6 +713,10 @@ bool Builder::Build(string* err) {
+@@ -841,6 +895,10 @@ bool Builder::Build(string* err) {
return false;
}
Cleanup();
--- a/src/build.h
+++ b/src/build.h
-@@ -52,6 +52,9 @@ struct Plan {
+@@ -51,6 +51,9 @@ struct Plan {
/// 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;
-@@ -136,6 +139,7 @@ private:
+@@ -145,7 +148,8 @@ private:
+ /// RealCommandRunner is an implementation that actually runs commands.
struct CommandRunner {
virtual ~CommandRunner() {}
- virtual bool CanRunMore() const = 0;
+- virtual size_t CanRunMore() const = 0;
++ virtual size_t CanRunMore() = 0;
+ virtual bool AcquireToken() = 0;
virtual bool StartCommand(Edge* edge) = 0;
/// The result of waiting for a command.
-@@ -147,7 +151,9 @@ struct CommandRunner {
+@@ -157,7 +161,9 @@ struct CommandRunner {
bool success() const { return status == ExitSuccess; }
};
/// Wait for a command to complete, or return false if interrupted.
virtual std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
virtual void Abort() {}
-@@ -155,7 +161,8 @@ struct CommandRunner {
+@@ -165,7 +171,8 @@ struct CommandRunner {
/// Options (e.g. verbosity, parallelism) passed to a build.
struct BuildConfig {
failures_allowed(1), max_load_average(-0.0f) {}
enum Verbosity {
-@@ -167,6 +174,7 @@ struct BuildConfig {
+@@ -177,6 +184,7 @@ struct BuildConfig {
Verbosity verbosity;
bool dry_run;
int parallelism;
#include <assert.h>
+#include <stdarg.h>
+ #include <climits>
+ #include <stdint.h>
- #include "build_log.h"
- #include "deps_log.h"
-@@ -474,8 +475,9 @@ struct FakeCommandRunner : public Comman
+@@ -521,9 +522,10 @@ struct FakeCommandRunner : public Comman
+ max_active_edges_(1), fs_(fs) {}
// CommandRunner impl
- virtual bool CanRunMore() const;
+- virtual size_t CanRunMore() const;
++ virtual size_t CanRunMore();
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
virtual vector<Edge*> GetActiveEdges();
virtual void Abort();
-@@ -578,6 +580,10 @@ bool FakeCommandRunner::CanRunMore() con
- return active_edges_.size() < max_active_edges_;
+@@ -622,13 +624,17 @@ void BuildTest::RebuildTarget(const stri
+ builder.command_runner_.release();
+ }
+
+-size_t FakeCommandRunner::CanRunMore() const {
++size_t FakeCommandRunner::CanRunMore() {
+ if (active_edges_.size() < max_active_edges_)
+ return SIZE_MAX;
+
+ return 0;
}
+bool FakeCommandRunner::AcquireToken() {
bool FakeCommandRunner::StartCommand(Edge* edge) {
assert(active_edges_.size() < max_active_edges_);
assert(find(active_edges_.begin(), active_edges_.end(), edge)
-@@ -649,7 +655,7 @@ bool FakeCommandRunner::StartCommand(Edg
+@@ -720,7 +726,7 @@ bool FakeCommandRunner::StartCommand(Edg
return true;
}
if (active_edges_.empty())
return false;
-@@ -3985,3 +3991,356 @@ TEST_F(BuildTest, ValidationWithCircular
+@@ -4380,3 +4386,355 @@ TEST_F(BuildTest, ValidationWithCircular
EXPECT_FALSE(builder_.AddTarget("out", &err));
EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err);
}
+ explicit FakeTokenCommandRunner() {}
+
+ // CommandRunner impl
-+ virtual bool CanRunMore() const;
++ virtual bool CanRunMore();
+ virtual bool AcquireToken();
+ virtual bool StartCommand(Edge* edge);
+ virtual bool WaitForCommand(Result* result, bool more_ready);
+ vector<bool> wait_for_command_;
+};
+
-+bool FakeTokenCommandRunner::CanRunMore() const {
++bool FakeTokenCommandRunner::CanRunMore() {
+ if (can_run_more_.size() == 0) {
+ EXPECT_FALSE("unexpected call to CommandRunner::CanRunMore()");
+ return false;
+
+ bool result = can_run_more_[0];
+
-+ // Unfortunately CanRunMore() isn't "const" for tests
-+ const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.erase(
-+ const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.begin()
++ can_run_more_.erase(
++ can_run_more_.begin()
+ );
+
+ return result;
+}
--- a/src/ninja.cc
+++ b/src/ninja.cc
-@@ -1447,6 +1447,7 @@ int ReadFlags(int* argc, char*** argv,
+@@ -1466,6 +1466,7 @@ int ReadFlags(int* argc, char*** argv,
// 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;
+};
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -112,6 +112,7 @@ add_library(libninja OBJECT
+@@ -142,6 +142,7 @@ add_library(libninja OBJECT
src/state.cc
src/status.cc
src/string_piece_util.cc
src/util.cc
src/version.cc
)
-@@ -123,9 +124,14 @@ if(WIN32)
+@@ -153,13 +154,17 @@ if(WIN32)
src/msvc_helper_main-win32.cc
src/getopt.c
src/minidump-win32.cc
+ src/tokenpool-gnu-make-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/subprocess-posix.cc)
+- target_sources(libninja PRIVATE src/subprocess-posix.cc)
+ target_sources(libninja PRIVATE
+ src/subprocess-posix.cc
+ src/tokenpool-gnu-make-posix.cc
+ )
if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
target_sources(libninja PRIVATE src/getopt.c)
- endif()
-@@ -204,6 +210,7 @@ if(BUILD_TESTING)
+ # Build getopt.c, which can be compiled as either C or C++, as C++
+@@ -286,6 +291,7 @@ if(BUILD_TESTING)
src/string_piece_util_test.cc
src/subprocess_test.cc
src/test.cc