Ninja
jobserver.cc
Go to the documentation of this file.
1// Copyright 2024 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "jobserver.h"
16
17#include <assert.h>
18#include <stdio.h>
19
20#include <vector>
21
22#include "string_piece.h"
23
24namespace {
25
26// If |input| starts with |prefix|, return true and sets |*value| to the rest
27// of the input. Otherwise return false.
28bool GetPrefixedValue(StringPiece input, StringPiece prefix,
29 StringPiece* value) {
30 assert(prefix.len_ > 0);
31 if (input.len_ < prefix.len_ || memcmp(prefix.str_, input.str_, prefix.len_))
32 return false;
33
34 *value = StringPiece(input.str_ + prefix.len_, input.len_ - prefix.len_);
35 return true;
36}
37
38// Try to read a comma-separated pair of file descriptors from |input|.
39// On success return true and set |config->mode| accordingly. Otherwise return
40// false if the input doesn't follow the appropriate format. Note that the
41// values are not saved since pipe mode is not supported.
42bool GetFileDescriptorPair(StringPiece input, Jobserver::Config* config) {
43 int read_fd = 1, write_fd = -1;
44 std::string pair = input.AsString();
45 if (sscanf(pair.c_str(), "%d,%d", &read_fd, &write_fd) != 2)
46 return false;
47
48 // From
49 // https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html Any
50 // negative descriptor means the feature is disabled.
51 if (read_fd < 0 || write_fd < 0)
53 else
55
56 return true;
57}
58
59} // namespace
60
61// static
63
65 assert(IsExplicit());
66 return static_cast<uint8_t>(value_);
67}
68
69bool Jobserver::ParseMakeFlagsValue(const char* makeflags_env,
70 Jobserver::Config* config,
71 std::string* error) {
72 *config = Config();
73
74 if (!makeflags_env || !makeflags_env[0]) {
75 /// Return default Config instance with kModeNone if input is null or empty.
76 return true;
77 }
78
79 // Decompose input into vector of space or tab separated string pieces.
80 std::vector<StringPiece> args;
81 const char* p = makeflags_env;
82 while (*p) {
83 const char* next_space = strpbrk(p, " \t");
84 if (!next_space) {
85 args.emplace_back(p);
86 break;
87 }
88
89 if (next_space > p)
90 args.emplace_back(p, next_space - p);
91
92 p = next_space + 1;
93 }
94
95 // clang-format off
96 //
97 // From:
98 // https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html
99 //
100 // """
101 // Your tool may also examine the first word of the MAKEFLAGS variable and
102 // look for the character n. If this character is present then make was
103 // invoked with the ā€˜-n’ option and your tool may want to stop without
104 // performing any operations.
105 // """
106 //
107 // Where according to
108 // https://www.gnu.org/software/make/manual/html_node/Options_002fRecursion.html
109 // MAKEFLAGS begins with all "flag letters" passed to make.
110 //
111 // Experimentation shows that GNU Make 4.3, at least, will set MAKEFLAGS with
112 // an initial space if no letter flag are passed to its invocation (except -j),
113 // i.e.:
114 //
115 // make -ks --> MAKEFLAGS="ks"
116 // make -j --> MAKEFLAGS=" -j"
117 // make -ksj --> MAKEFLAGS="ks -j"
118 // make -ks -j3 --> MAKEFLAGS="ks -j3 --jobserver-auth=3,4"
119 // make -j3 --> MAKEFLAGS=" -j3 --jobserver-auth=3,4"
120 //
121 // However, other jobserver implementation will not, for example the one
122 // at https://github.com/rust-lang/jobserver-rs will set MAKEFLAGS to just
123 // "--jobserver-fds=R,W --jobserver-auth=R,W" instead, without an initial
124 // space.
125 //
126 // Another implementation is from Rust's Cargo itself which will set it to
127 // "-j --jobserver-fds=R,W --jobserver-auth=R,W".
128 //
129 // For the record --jobserver-fds=R,W is an old undocumented and deprecated
130 // version of --jobserver-auth=R,W that was implemented by GNU Make before 4.2
131 // was released, and some tooling may depend on it. Hence it makes sense to
132 // define both --jobserver-fds and --jobserver-auth at the same time, since
133 // the last recognized one should win in client code.
134 //
135 // The initial space will have been stripped by the loop above, but we can
136 // still support the requirement by ignoring the first arg if it begins with a
137 // dash (-).
138 //
139 // clang-format on
140 if (!args.empty() && args[0][0] != '-' &&
141 memchr(args[0].str_, 'n', args[0].len_) != nullptr) {
142 return true;
143 }
144
145 // Loop over all arguments, the last one wins, except in case of errors.
146 for (const auto& arg : args) {
147 StringPiece value;
148
149 // Handle --jobserver-auth=... here.
150 if (GetPrefixedValue(arg, "--jobserver-auth=", &value)) {
151 if (GetFileDescriptorPair(value, config)) {
152 continue;
153 }
154 StringPiece fifo_path;
155 if (GetPrefixedValue(value, "fifo:", &fifo_path)) {
157 config->path = fifo_path.AsString();
158 } else {
160 config->path = value.AsString();
161 }
162 continue;
163 }
164
165 // Handle --jobserver-fds which is an old undocumented variant of
166 // --jobserver-auth that only accepts a pair of file descriptor.
167 // This was replaced by --jobserver-auth=R,W in GNU Make 4.2.
168 if (GetPrefixedValue(arg, "--jobserver-fds=", &value)) {
169 if (!GetFileDescriptorPair(value, config)) {
170 *error = "Invalid file descriptor pair [" + value.AsString() + "]";
171 return false;
172 }
174 continue;
175 }
176
177 // Ignore this argument. This assumes that MAKEFLAGS does not
178 // use spaces to separate the option from its argument, e.g.
179 // `--jobserver-auth <something>`, which has been confirmed with
180 // Make 4.3, even if it receives such a value in its own env.
181 }
182
183 return true;
184}
185
186bool Jobserver::ParseNativeMakeFlagsValue(const char* makeflags_env,
187 Jobserver::Config* config,
188 std::string* error) {
189 if (!ParseMakeFlagsValue(makeflags_env, config, error))
190 return false;
191
192 if (config->mode == Jobserver::Config::kModePipe) {
193 *error = "Pipe-based protocol is not supported!";
194 return false;
195 }
196#ifdef _WIN32
198 *error = "FIFO mode is not supported on Windows!";
199 return false;
200 }
201#else // !_WIN32
203 *error = "Semaphore mode is not supported on Posix!";
204 return false;
205 }
206#endif // !_WIN32
207 return true;
208}
A Jobserver::Config models how to access or implement a GNU jobserver implementation.
Definition jobserver.h:106
std::string path
For kModeFifo, this is the path to the Unix FIFO to use.
Definition jobserver.h:146
Mode mode
Implementation mode for the pool.
Definition jobserver.h:142
uint8_t GetExplicitValue() const
Return value of an explicit slot.
Definition jobserver.cc:64
bool IsExplicit() const
Return true if this instance represents an explicit job slot.
Definition jobserver.h:82
static constexpr int16_t kImplicitValue
Definition jobserver.h:99
static bool ParseNativeMakeFlagsValue(const char *makeflags_env, Config *config, std::string *error)
A variant of ParseMakeFlagsValue() that will return an error if the parsed result is not compatible w...
Definition jobserver.cc:186
static bool ParseMakeFlagsValue(const char *makeflags_env, Config *config, std::string *error)
Parse the value of a MAKEFLAGS environment variable.
Definition jobserver.cc:69
StringPiece represents a slice of a string whose memory is managed externally.
const char * str_
std::string AsString() const
Convert the slice into a full-fledged std::string, copying the data into a new string.
signed short int16_t
Definition win32port.h:25