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 
24 namespace {
25 
26 // If |input| starts with |prefix|, return true and sets |*value| to the rest
27 // of the input. Otherwise return false.
28 bool 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.
42 bool 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 
69 bool 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 
186 bool 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
197  if (config->mode == Jobserver::Config::kModePosixFifo) {
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
int16_t value_
Definition: jobserver.h:101
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.
Definition: string_piece.h:25
const char * str_
Definition: string_piece.h:70
size_t len_
Definition: string_piece.h:71
std::string AsString() const
Convert the slice into a full-fledged std::string, copying the data into a new string.
Definition: string_piece.h:46
signed short int16_t
Definition: win32port.h:25