blob: 3e2f6622122d9d08ca5b3c136e1d5f55eb676a9e [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000015#include "src/main/cpp/option_processor.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <unistd.h>
21#include <algorithm>
22#include <cassert>
Nathan Harmata7d300b22015-12-21 16:10:05 +000023#include <set>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024#include <utility>
25
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000026#include "src/main/cpp/blaze_util.h"
27#include "src/main/cpp/blaze_util_platform.h"
28#include "src/main/cpp/util/file.h"
29#include "src/main/cpp/util/strings.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030
31using std::list;
32using std::map;
Nathan Harmata7d300b22015-12-21 16:10:05 +000033using std::set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034using std::vector;
35
36// On OSX, there apparently is no header that defines this.
37extern char **environ;
38
39namespace blaze {
40
Googlere3681d12015-12-02 22:26:59 +000041constexpr char BlazeStartupOptions::WorkspacePrefix[];
42
Thiago Farina2e4c2aa2015-06-12 07:18:20 +000043OptionProcessor::RcOption::RcOption(int rcfile_index, const string& option)
44 : rcfile_index_(rcfile_index), option_(option) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045}
46
Thiago Farina2e4c2aa2015-06-12 07:18:20 +000047OptionProcessor::RcFile::RcFile(const string& filename, int index)
48 : filename_(filename), index_(index) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049}
50
51blaze_exit_code::ExitCode OptionProcessor::RcFile::Parse(
Googlere3681d12015-12-02 22:26:59 +000052 const string& workspace,
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +000053 vector<RcFile*>* rcfiles,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054 map<string, vector<RcOption> >* rcoptions,
55 string* error) {
56 list<string> initial_import_stack;
57 initial_import_stack.push_back(filename_);
58 return Parse(
Googlere3681d12015-12-02 22:26:59 +000059 workspace, filename_, index_, rcfiles, rcoptions, &initial_import_stack,
60 error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061}
62
63blaze_exit_code::ExitCode OptionProcessor::RcFile::Parse(
Googlere3681d12015-12-02 22:26:59 +000064 const string& workspace,
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +000065 const string& filename_ref,
Thiago Farina2fd78902015-05-18 11:37:59 +000066 const int index,
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +000067 vector<RcFile*>* rcfiles,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068 map<string, vector<RcOption> >* rcoptions,
69 list<string>* import_stack,
70 string* error) {
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +000071 string filename(filename_ref); // file
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010072 string contents;
73 if (!ReadFile(filename, &contents)) {
74 // We checked for file readability before, so this is unexpected.
75 blaze_util::StringPrintf(error,
76 "Unexpected error reading .blazerc file '%s'", filename.c_str());
77 return blaze_exit_code::INTERNAL_ERROR;
78 }
79
80 // A '\' at the end of a line continues the line.
81 blaze_util::Replace("\\\r\n", "", &contents);
82 blaze_util::Replace("\\\n", "", &contents);
83 vector<string> startup_options;
84
85 vector<string> lines = blaze_util::Split(contents, '\n');
86 for (int line = 0; line < lines.size(); ++line) {
87 blaze_util::StripWhitespace(&lines[line]);
88
89 // Check for an empty line.
90 if (lines[line].empty()) {
91 continue;
92 }
93
94 vector<string> words;
95
96 // This will treat "#" as a comment, and properly
97 // quote single and double quotes, and treat '\'
98 // as an escape character.
99 // TODO(bazel-team): This function silently ignores
100 // dangling backslash escapes and missing end-quotes.
101 blaze_util::Tokenize(lines[line], '#', &words);
102
103 if (words.empty()) {
104 // Could happen if line starts with "#"
105 continue;
106 }
107
108 string command = words[0];
109
110 if (command == "import") {
Googlere3681d12015-12-02 22:26:59 +0000111 if (words.size() != 2
112 || (words[1].compare(0, BlazeStartupOptions::WorkspacePrefixLength,
113 BlazeStartupOptions::WorkspacePrefix) == 0
114 && !BlazeStartupOptions::WorkspaceRelativizeRcFilePath(
115 workspace, &words[1]))) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100116 blaze_util::StringPrintf(error,
117 "Invalid import declaration in .blazerc file '%s': '%s'",
118 filename.c_str(), lines[line].c_str());
119 return blaze_exit_code::BAD_ARGV;
120 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100121 if (std::find(import_stack->begin(), import_stack->end(), words[1]) !=
122 import_stack->end()) {
123 string loop;
124 for (list<string>::const_iterator imported_rc = import_stack->begin();
125 imported_rc != import_stack->end(); ++imported_rc) {
126 loop += " " + *imported_rc + "\n";
127 }
128 blaze_util::StringPrintf(error,
129 "Import loop detected:\n%s", loop.c_str());
130 return blaze_exit_code::BAD_ARGV;
131 }
132
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000133 rcfiles->push_back(new RcFile(words[1], rcfiles->size()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100134 import_stack->push_back(words[1]);
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000135 blaze_exit_code::ExitCode parse_exit_code =
Googlere3681d12015-12-02 22:26:59 +0000136 RcFile::Parse(workspace, rcfiles->back()->Filename(),
137 rcfiles->back()->Index(),
138 rcfiles, rcoptions, import_stack, error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100139 if (parse_exit_code != blaze_exit_code::SUCCESS) {
140 return parse_exit_code;
141 }
142 import_stack->pop_back();
143 } else {
144 for (int word = 1; word < words.size(); ++word) {
145 (*rcoptions)[command].push_back(RcOption(index, words[word]));
146 if (command == "startup") {
147 startup_options.push_back(words[word]);
148 }
149 }
150 }
151 }
152
153 if (!startup_options.empty()) {
154 string startup_args;
155 blaze_util::JoinStrings(startup_options, ' ', &startup_args);
156 fprintf(stderr, "INFO: Reading 'startup' options from %s: %s\n",
157 filename.c_str(), startup_args.c_str());
158 }
159 return blaze_exit_code::SUCCESS;
160}
161
162OptionProcessor::OptionProcessor()
163 : initialized_(false), parsed_startup_options_(new BlazeStartupOptions()) {
164}
165
166// Return the path of the depot .blazerc file.
167string OptionProcessor::FindDepotBlazerc(const string& workspace) {
168 // Package semantics are ignored here, but that's acceptable because
169 // blaze.blazerc is a configuration file.
170 vector<string> candidates;
171 BlazeStartupOptions::WorkspaceRcFileSearchPath(&candidates);
172 for (const auto& candidate : candidates) {
173 string blazerc = blaze_util::JoinPath(workspace, candidate);
174 if (!access(blazerc.c_str(), R_OK)) {
175 return blazerc;
176 }
177 }
178
179 return "";
180}
181
182// Return the path of the .blazerc file that sits alongside the binary.
183// This allows for canary or cross-platform Blazes operating on the same depot
184// to have customized behavior.
185string OptionProcessor::FindAlongsideBinaryBlazerc(const string& cwd,
186 const string& arg0) {
187 string path = arg0[0] == '/' ? arg0 : blaze_util::JoinPath(cwd, arg0);
188 string base = blaze_util::Basename(arg0);
189 string binary_blazerc_path = path + "." + base + "rc";
190 if (!access(binary_blazerc_path.c_str(), R_OK)) {
191 return binary_blazerc_path;
192 }
193 return "";
194}
195
Damien Martin-Guillerez43b2ea72015-06-15 10:40:07 +0000196// Return the path of the bazelrc file that sits in /etc.
197// This allows for installing Bazel on system-wide directory.
198string OptionProcessor::FindSystemWideBlazerc() {
199 string path = BlazeStartupOptions::SystemWideRcPath();
200 if (!path.empty() && !access(path.c_str(), R_OK)) {
201 return path;
202 }
203 return "";
204}
205
Thiago Farina9bc5c342016-03-21 08:39:48 +0000206// Return the path to the user's rc file. If cmdLineRcFile != NULL,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207// use it, dying if it is not readable. Otherwise, return the first
208// readable file called rc_basename from [workspace, $HOME]
209//
210// If no readable .blazerc file is found, return the empty string.
211blaze_exit_code::ExitCode OptionProcessor::FindUserBlazerc(
212 const char* cmdLineRcFile,
213 const string& rc_basename,
214 const string& workspace,
215 string* blaze_rc_file,
216 string* error) {
217 if (cmdLineRcFile != NULL) {
218 string rcFile = MakeAbsolute(cmdLineRcFile);
219 if (access(rcFile.c_str(), R_OK)) {
220 blaze_util::StringPrintf(error,
221 "Error: Unable to read .blazerc file '%s'.", rcFile.c_str());
222 return blaze_exit_code::BAD_ARGV;
223 }
224 *blaze_rc_file = rcFile;
225 return blaze_exit_code::SUCCESS;
226 }
227
228 string workspaceRcFile = blaze_util::JoinPath(workspace, rc_basename);
229 if (!access(workspaceRcFile.c_str(), R_OK)) {
230 *blaze_rc_file = workspaceRcFile;
231 return blaze_exit_code::SUCCESS;
232 }
233
234 const char* home = getenv("HOME");
235 if (home == NULL) {
236 *blaze_rc_file = "";
237 return blaze_exit_code::SUCCESS;
238 }
239
240 string userRcFile = blaze_util::JoinPath(home, rc_basename);
241 if (!access(userRcFile.c_str(), R_OK)) {
242 *blaze_rc_file = userRcFile;
243 return blaze_exit_code::SUCCESS;
244 }
245 *blaze_rc_file = "";
246 return blaze_exit_code::SUCCESS;
247}
248
249blaze_exit_code::ExitCode OptionProcessor::ParseOptions(
250 const vector<string>& args,
251 const string& workspace,
252 const string& cwd,
253 string* error) {
254 assert(!initialized_);
255 initialized_ = true;
256
257 const char* blazerc = NULL;
258 bool use_master_blazerc = true;
259
260 // Check if there is a blazerc related option given
261 args_ = args;
262 for (int i= 1; i < args.size(); i++) {
263 const char* arg_chr = args[i].c_str();
264 const char* next_arg_chr = (i + 1) < args.size()
265 ? args[i + 1].c_str()
266 : NULL;
267 if (blazerc == NULL) {
268 blazerc = GetUnaryOption(arg_chr, next_arg_chr, "--blazerc");
269 }
Damien Martin-Guillerez8cde69c2015-06-05 11:33:44 +0000270 if (blazerc == NULL) {
271 blazerc = GetUnaryOption(arg_chr, next_arg_chr, "--bazelrc");
272 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100273 if (use_master_blazerc &&
Damien Martin-Guillerez50d124b2015-06-24 15:20:09 +0000274 (GetNullaryOption(arg_chr, "--nomaster_blazerc") ||
275 GetNullaryOption(arg_chr, "--nomaster_bazelrc"))) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 use_master_blazerc = false;
277 }
278 }
279
280 // Parse depot and user blazerc files.
Nathan Harmata7d300b22015-12-21 16:10:05 +0000281 // This is a little inefficient (copying a multimap around), but it is a
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282 // small one and this way I don't have to care about memory management.
Nathan Harmata7d300b22015-12-21 16:10:05 +0000283 vector<string> candidate_blazerc_paths;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100284 if (use_master_blazerc) {
Nathan Harmata7d300b22015-12-21 16:10:05 +0000285 candidate_blazerc_paths.push_back(FindDepotBlazerc(workspace));
286 candidate_blazerc_paths.push_back(FindAlongsideBinaryBlazerc(cwd, args[0]));
287 candidate_blazerc_paths.push_back(FindSystemWideBlazerc());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100288 }
289
290 string user_blazerc_path;
291 blaze_exit_code::ExitCode find_blazerc_exit_code = FindUserBlazerc(
292 blazerc, BlazeStartupOptions::RcBasename(), workspace, &user_blazerc_path,
293 error);
294 if (find_blazerc_exit_code != blaze_exit_code::SUCCESS) {
295 return find_blazerc_exit_code;
296 }
Nathan Harmata7d300b22015-12-21 16:10:05 +0000297 candidate_blazerc_paths.push_back(user_blazerc_path);
298
299 // Throw away missing files, dedupe candidate blazerc paths, and parse the
300 // blazercs, all while preserving order. Duplicates can arise if e.g. the
301 // binary's path *is* the depot path.
302 set<string> blazerc_paths;
303 for (const auto& candidate_blazerc_path : candidate_blazerc_paths) {
304 if (!candidate_blazerc_path.empty()
305 && (blazerc_paths.insert(candidate_blazerc_path).second)) {
306 blazercs_.push_back(
307 new RcFile(candidate_blazerc_path, blazercs_.size()));
308 blaze_exit_code::ExitCode parse_exit_code =
309 blazercs_.back()->Parse(workspace, &blazercs_, &rcoptions_, error);
310 if (parse_exit_code != blaze_exit_code::SUCCESS) {
311 return parse_exit_code;
312 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100313 }
314 }
315
316 blaze_exit_code::ExitCode parse_startup_options_exit_code =
317 ParseStartupOptions(error);
318 if (parse_startup_options_exit_code != blaze_exit_code::SUCCESS) {
319 return parse_startup_options_exit_code;
320 }
321
322 // Determine command
323 if (startup_args_ + 1 >= args.size()) {
324 command_ = "";
325 return blaze_exit_code::SUCCESS;
326 }
327
328 command_ = args[startup_args_ + 1];
329
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100330 AddRcfileArgsAndOptions(parsed_startup_options_->batch, cwd);
331 for (unsigned int cmd_arg = startup_args_ + 2;
332 cmd_arg < args.size(); cmd_arg++) {
333 command_arguments_.push_back(args[cmd_arg]);
334 }
335 return blaze_exit_code::SUCCESS;
336}
337
338blaze_exit_code::ExitCode OptionProcessor::ParseOptions(
339 int argc,
340 const char* argv[],
341 const string& workspace,
342 const string& cwd,
343 string* error) {
344 vector<string> args(argc);
345 for (int arg = 0; arg < argc; arg++) {
346 args[arg] = argv[arg];
347 }
348
349 return ParseOptions(args, workspace, cwd, error);
350}
351
352static bool IsArg(const string& arg) {
353 return blaze_util::starts_with(arg, "-") && (arg != "--help")
354 && (arg != "-help") && (arg != "-h");
355}
356
357blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(string *error) {
358 // Process rcfile startup options
359 map< string, vector<RcOption> >::const_iterator it =
360 rcoptions_.find("startup");
361 blaze_exit_code::ExitCode process_arg_exit_code;
362 bool is_space_separated;
363 if (it != rcoptions_.end()) {
364 const vector<RcOption>& startup_options = it->second;
365 int i = 0;
366 // Process all elements except the last one.
367 for (; i < startup_options.size() - 1; i++) {
368 const RcOption& option = startup_options[i];
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000369 const string& blazerc = blazercs_[option.rcfile_index()]->Filename();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100370 process_arg_exit_code = parsed_startup_options_->ProcessArg(
371 option.option(), startup_options[i + 1].option(), blazerc,
372 &is_space_separated, error);
373 if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
374 return process_arg_exit_code;
375 }
376 if (is_space_separated) {
377 i++;
378 }
379 }
380 // Process last element, if any.
381 if (i < startup_options.size()) {
382 const RcOption& option = startup_options[i];
383 if (IsArg(option.option())) {
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000384 const string& blazerc = blazercs_[option.rcfile_index()]->Filename();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 process_arg_exit_code = parsed_startup_options_->ProcessArg(
386 option.option(), "", blazerc, &is_space_separated, error);
387 if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
388 return process_arg_exit_code;
389 }
390 }
391 }
392 }
393
394 // Process command-line args next, so they override any of the same options
395 // from .blazerc. Stop on first non-arg, this includes --help
396 unsigned int i = 1;
397 if (!args_.empty()) {
398 for (; (i < args_.size() - 1) && IsArg(args_[i]); i++) {
399 process_arg_exit_code = parsed_startup_options_->ProcessArg(
400 args_[i], args_[i + 1], "", &is_space_separated, error);
401 if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
402 return process_arg_exit_code;
403 }
404 if (is_space_separated) {
405 i++;
406 }
407 }
408 if (i < args_.size() && IsArg(args_[i])) {
409 process_arg_exit_code = parsed_startup_options_->ProcessArg(
410 args_[i], "", "", &is_space_separated, error);
411 if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
412 return process_arg_exit_code;
413 }
414 i++;
415 }
416 }
417 startup_args_ = i -1;
418
419 return blaze_exit_code::SUCCESS;
420}
421
422// Appends the command and arguments from argc/argv to the end of arg_vector,
423// and also splices in some additional terminal and environment options between
424// the command and the arguments. NB: Keep the options added here in sync with
425// BlazeCommandDispatcher.INTERNAL_COMMAND_OPTIONS!
426void OptionProcessor::AddRcfileArgsAndOptions(bool batch, const string& cwd) {
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000427 // Provide terminal options as coming from the least important rc file.
428 command_arguments_.push_back("--rc_source=client");
429 command_arguments_.push_back("--default_override=0:common=--isatty=" +
430 ToString(IsStandardTerminal()));
431 command_arguments_.push_back(
432 "--default_override=0:common=--terminal_columns=" +
433 ToString(GetTerminalColumns()));
434
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100435 // Push the options mapping .blazerc numbers to filenames.
436 for (int i_blazerc = 0; i_blazerc < blazercs_.size(); i_blazerc++) {
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000437 const RcFile* blazerc = blazercs_[i_blazerc];
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000438 command_arguments_.push_back("--rc_source=" +
439 blaze::ConvertPath(blazerc->Filename()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100440 }
441
442 // Push the option defaults
443 for (map<string, vector<RcOption> >::const_iterator it = rcoptions_.begin();
444 it != rcoptions_.end(); ++it) {
445 if (it->first == "startup") {
446 // Skip startup options, they are parsed in the C++ wrapper
447 continue;
448 }
449
450 for (int ii = 0; ii < it->second.size(); ii++) {
451 const RcOption& rcoption = it->second[ii];
Googler016baf32016-03-30 20:21:23 +0000452 command_arguments_.push_back(
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000453 "--default_override=" + ToString(rcoption.rcfile_index() + 1) + ":"
Googler016baf32016-03-30 20:21:23 +0000454 + it->first + "=" + rcoption.option());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 }
456 }
457
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100458 // Pass the client environment to the server in server mode.
459 if (batch) {
460 command_arguments_.push_back("--ignore_client_env");
461 } else {
462 for (char** env = environ; *env != NULL; env++) {
463 command_arguments_.push_back("--client_env=" + string(*env));
464 }
465 }
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000466 command_arguments_.push_back("--client_cwd=" + blaze::ConvertPath(cwd));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100467
468 const char *emacs = getenv("EMACS");
469 if (emacs != NULL && strcmp(emacs, "t") == 0) {
470 command_arguments_.push_back("--emacs");
471 }
472}
473
474void OptionProcessor::GetCommandArguments(vector<string>* result) const {
475 result->insert(result->end(),
476 command_arguments_.begin(),
477 command_arguments_.end());
478}
479
480const string& OptionProcessor::GetCommand() const {
481 return command_;
482}
483
484const BlazeStartupOptions& OptionProcessor::GetParsedStartupOptions() const {
485 return *parsed_startup_options_.get();
486}
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000487
488OptionProcessor::~OptionProcessor() {
489 for (auto it : blazercs_) {
490 delete it;
491 }
492}
493
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100494} // namespace blaze