Skip to content

Commit 454271e

Browse files
committed
Support enable_jobserver_pool = 1 in build.ninja.
Another way to enable jobserver pool mode is to set `enable_jobserver_pool = 1` in the `build.ninja` file directly, which can be determined by the generator directly. This is equivalent to using `--jobserver-pool` on the command-line. Note that: - Any value other than 1 is ignored, intentionally (the size of the pool is determined when Ninja is invoked only). - There is no way to disable the feature from the command-line when enabled in the build plan. - Just like `--jobserver-pool`, this is ignored if a parent jobserver pool is detected when Ninja is invoked.
1 parent 49315e2 commit 454271e

File tree

6 files changed

+109
-8
lines changed

6 files changed

+109
-8
lines changed

doc/manual.asciidoc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ Ninja becomes a protocol client automatically if it detects the right
203203
values in the `MAKEFLAGS` environment variable (see exact conditions below).
204204
205205
Since version 1.14, Ninja can also be a protocol server, if needed, using
206-
the `--jobserver-pool` command-line flag.
206+
the `--jobserver-pool` command-line flag, or if `enable_jobserver_pool = 1`
207+
is set in the Ninja build plan.
207208
208209
In jobserver-enabled builds, there is one top-level "server" process which:
209210
@@ -235,8 +236,8 @@ when a pipe-based jobserver is being used (i.e. when `MAKEFLAGS` contains
235236
`--jobserver-auth=<read>,<write>`) and will print a warning, but will
236237
otherwise ignore it.
237238
238-
Using `--jobserver-pool` will make Ninja act as a protocol server, unless
239-
any of these are true:
239+
Using `--jobserver-pool` or `enable_jobserver_pool = 1` will make Ninja
240+
act as a protocol server, unless any of these are true:
240241
241242
- An existing pool was detected, as this keeps all processes cooperating
242243
properly.
@@ -983,7 +984,7 @@ previous one, it closes the previous scope.
983984
Top-level variables
984985
~~~~~~~~~~~~~~~~~~~
985986

986-
Two variables are significant when declared in the outermost file scope.
987+
Three variables are significant when declared in the outermost file scope.
987988

988989
`builddir`:: a directory for some Ninja output files. See <<ref_log,the
989990
discussion of the build log>>. (You can also store other build output
@@ -992,6 +993,11 @@ Two variables are significant when declared in the outermost file scope.
992993
`ninja_required_version`:: the minimum version of Ninja required to process
993994
the build correctly. See <<ref_versioning,the discussion of versioning>>.
994995

996+
`enable_jobserver_pool`:: If set to `1` (any other value is ignored), enable
997+
jobserver pool mode, as if `--jobserver-pool` was passed on the command
998+
line. Note that `0` does not disable the feature, and that the size of
999+
the pool is determined by the parallel job count that is either auto-detected
1000+
or controlled by the `-j<COUNT>` command-line option.
9951001

9961002
[[ref_rule]]
9971003
Rule variables

misc/jobserver_test.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ def span_output_file(span_n: int) -> str:
111111
return "out%02d" % span_n
112112

113113

114-
def generate_build_plan(command_count: int) -> str:
114+
def generate_build_plan(command_count: int, prefix: str = "") -> str:
115115
"""Generate a Ninja build plan for |command_count| parallel tasks.
116116
117117
Each task calls the test helper script which waits for 50ms
118118
then writes its own start and end time to its output file.
119119
"""
120-
result = f"""
120+
result = prefix + f"""
121121
rule span
122122
command = {sys.executable} -S {_JOBSERVER_TEST_HELPER_SCRIPT} --duration-ms=50 $out
123123
@@ -291,7 +291,7 @@ def run_ninja_with_jobserver_pipe(args):
291291
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
292292
self.assertEqual(max_overlaps, 1)
293293

294-
def test_jobserver_pool_mode(self):
294+
def test_jobserver_pool_mode_with_flag(self):
295295
task_count = 4
296296
build_plan = generate_build_plan(task_count)
297297
with BuildDir(build_plan) as b:
@@ -365,6 +365,57 @@ def test_jobserver_pool_mode_ignored_with_existing_pool(self):
365365
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
366366
self.assertEqual(max_overlaps, 2)
367367

368+
def test_jobserver_pool_mode_with_variable(self):
369+
task_count = 4
370+
build_plan = generate_build_plan(task_count, prefix = "enable_jobserver_pool = 1\n")
371+
with BuildDir(build_plan) as b:
372+
# First, run the full tasks with with {task_count} tokens, this should allow all
373+
# tasks to run in parallel.
374+
ret = b.ninja_run(
375+
ninja_args=["all"],
376+
)
377+
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
378+
self.assertEqual(max_overlaps, task_count)
379+
380+
# Second, use 2 tokens only, and verify that this was enforced by Ninja.
381+
b.ninja_clean()
382+
b.ninja_run(
383+
["-j2", "all"],
384+
)
385+
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
386+
self.assertEqual(max_overlaps, 2)
387+
388+
# Third, verify that --jobs=1 serializes all tasks.
389+
b.ninja_clean()
390+
b.ninja_run(
391+
["-j1", "all"],
392+
)
393+
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
394+
self.assertEqual(max_overlaps, 1)
395+
396+
# On Linux, use taskset to limit the number of available cores to 1
397+
# and verify that the jobserver overrides the default Ninja parallelism
398+
# and that {task_count} tasks are still spawned in parallel.
399+
if platform.system() == "Linux":
400+
# First, run without a jobserver, with a single CPU, Ninja will
401+
# use a parallelism of 2 in this case (GuessParallelism() in ninja.cc)
402+
b.ninja_clean()
403+
b.ninja_run(
404+
["all"],
405+
prefix_args=["taskset", "-c", "0"],
406+
)
407+
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
408+
self.assertEqual(max_overlaps, 2)
409+
410+
# Now with a jobserver with {task_count} tasks.
411+
b.ninja_clean()
412+
b.ninja_run(
413+
[f"-j{task_count}", "all"],
414+
prefix_args=["taskset", "-c", "0"],
415+
)
416+
max_overlaps = compute_max_overlapped_spans(b.path, task_count)
417+
self.assertEqual(max_overlaps, task_count)
418+
368419
def _test_MAKEFLAGS_value(
369420
self, ninja_args: T.List[str] = [], prefix_args: T.List[str] = []
370421
):

src/manifest_parser.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ ManifestParser::ManifestParser(State* state, FileReader* file_reader,
3535
env_ = &state->bindings_;
3636
}
3737

38+
std::string ManifestParser::LookupVariable(const std::string& varname) {
39+
return env_->LookupVariable(varname);
40+
}
41+
3842
bool ManifestParser::Parse(const string& filename, const string& input,
3943
string* err) {
4044
lexer_.Start(filename, input);

src/manifest_parser.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ struct ManifestParser : public Parser {
4848
return Parse("input", input, err);
4949
}
5050

51-
private:
51+
/// Retrieve the expanded value of a top-level variable from the
52+
/// manifest. Returns an empty string if the variable is not defined.
53+
/// Must be called only after a successful Load() call.
54+
std::string LookupVariable(const std::string& varname);
55+
56+
private:
5257
/// Parse a file, given its contents as a string.
5358
bool Parse(const std::string& filename, const std::string& input,
5459
std::string* err);

src/manifest_parser_test.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,3 +1136,28 @@ TEST_F(ParserTest, DyndepRuleInput) {
11361136
EXPECT_TRUE(edge->dyndep_->dyndep_pending());
11371137
EXPECT_EQ(edge->dyndep_->path(), "in");
11381138
}
1139+
1140+
struct ManifestParserTest : public testing::Test {
1141+
ManifestParserTest() : parser(&state, &fs_) {}
1142+
1143+
void AssertParse(const char* input) {
1144+
string err;
1145+
EXPECT_TRUE(parser.ParseTest(input, &err));
1146+
ASSERT_EQ("", err);
1147+
VerifyGraph(state);
1148+
}
1149+
1150+
State state;
1151+
VirtualFileSystem fs_;
1152+
ManifestParser parser;
1153+
};
1154+
1155+
TEST_F(ManifestParserTest, LookupVariable) {
1156+
ASSERT_NO_FATAL_FAILURE(
1157+
AssertParse("foo = World\n"
1158+
"bar = Hello $foo\n"));
1159+
1160+
ASSERT_EQ(parser.LookupVariable("foo"), "World");
1161+
ASSERT_EQ(parser.LookupVariable("bar"), "Hello World");
1162+
ASSERT_EQ(parser.LookupVariable("zoo"), "");
1163+
}

src/ninja.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,16 @@ NORETURN void real_main(int argc, char** argv) {
19641964
exit(1);
19651965
}
19661966

1967+
// If enable_jobserver_pool is set to 1, enable jobserver pool mode as
1968+
// if --jobserver-pool had been passed on the command line. Note that
1969+
// any other value is ignored (thus 0 does not disable the flag if it is
1970+
// used).
1971+
std::string enable_jobserver_pool =
1972+
parser.LookupVariable("enable_jobserver_pool");
1973+
if (enable_jobserver_pool == "1") {
1974+
const_cast<BuildConfig&>(ninja.config_).jobserver_pool = true;
1975+
}
1976+
19671977
ninja.ParsePreviousElapsedTimes();
19681978

19691979
ExitStatus result = ninja.RunBuild(argc, argv, status);

0 commit comments

Comments
 (0)