blob: 9fb942277771fa62e4303e39d933efb9f5fd1fda [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"
ccalvarin5e5ee0d2018-08-23 08:56:01 -070016#include "src/main/cpp/option_processor-internal.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021#include <algorithm>
22#include <cassert>
Googler9cda2ec2019-03-25 14:48:26 -070023#include <iterator>
Nathan Harmata7d300b22015-12-21 16:10:05 +000024#include <set>
Googlerebd28a92018-02-07 08:46:31 -080025#include <sstream>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026#include <utility>
27
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000028#include "src/main/cpp/blaze_util.h"
29#include "src/main/cpp/blaze_util_platform.h"
30#include "src/main/cpp/util/file.h"
Chloe Calvarin78f1c852016-11-22 21:58:50 +000031#include "src/main/cpp/util/logging.h"
ccalvarinac69da02018-06-05 15:27:26 -070032#include "src/main/cpp/util/path.h"
33#include "src/main/cpp/util/path_platform.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000034#include "src/main/cpp/util/strings.h"
Julio Merino211a95c2016-08-29 11:01:35 +000035#include "src/main/cpp/workspace_layout.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037// On OSX, there apparently is no header that defines this.
Loo Rong Jieafbab992018-02-12 03:02:31 -080038#ifndef environ
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039extern char **environ;
Loo Rong Jieafbab992018-02-12 03:02:31 -080040#endif
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041
42namespace blaze {
43
Thiago Farina80bb0f22016-10-17 15:57:13 +000044using std::map;
45using std::set;
46using std::string;
47using std::vector;
48
Julio Merino211a95c2016-08-29 11:01:35 +000049constexpr char WorkspaceLayout::WorkspacePrefix[];
ccalvarin5e5ee0d2018-08-23 08:56:01 -070050static constexpr const char* kRcBasename = ".bazelrc";
Googlerebd28a92018-02-07 08:46:31 -080051static std::vector<std::string> GetProcessedEnv();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010052
Julio Merino28774852016-09-14 16:59:46 +000053OptionProcessor::OptionProcessor(
Julio Merinoe3e3bfa2016-12-08 22:22:12 +000054 const WorkspaceLayout* workspace_layout,
55 std::unique_ptr<StartupOptions> default_startup_options)
lpinoc00ba522017-07-10 19:17:40 +020056 : workspace_layout_(workspace_layout),
michajloeaf066b2019-05-15 14:12:57 -070057 startup_options_(std::move(default_startup_options)),
58 parse_options_called_(false),
Yannic Bonenberger45d52002018-09-14 16:11:36 -070059 system_bazelrc_path_(BAZEL_SYSTEM_BAZELRC_PATH) {}
60
61OptionProcessor::OptionProcessor(
62 const WorkspaceLayout* workspace_layout,
63 std::unique_ptr<StartupOptions> default_startup_options,
64 const std::string& system_bazelrc_path)
65 : workspace_layout_(workspace_layout),
michajloeaf066b2019-05-15 14:12:57 -070066 startup_options_(std::move(default_startup_options)),
67 parse_options_called_(false),
Yannic Bonenberger45d52002018-09-14 16:11:36 -070068 system_bazelrc_path_(system_bazelrc_path) {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069
michajlo371a2e32019-05-23 13:14:39 -070070std::string OptionProcessor::GetLowercaseProductName() const {
71 return startup_options_->GetLowercaseProductName();
72}
73
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +000074std::unique_ptr<CommandLine> OptionProcessor::SplitCommandLine(
Googler9cda2ec2019-03-25 14:48:26 -070075 vector<string> args, string* error) const {
michajlo371a2e32019-05-23 13:14:39 -070076 const string lowercase_product_name = GetLowercaseProductName();
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +000077
78 if (args.empty()) {
79 blaze_util::StringPrintf(error,
80 "Unable to split command line, args is empty");
81 return nullptr;
82 }
83
Googler9cda2ec2019-03-25 14:48:26 -070084 string& path_to_binary = args[0];
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +000085
86 // Process the startup options.
87 vector<string> startup_args;
88 vector<string>::size_type i = 1;
89 while (i < args.size() && IsArg(args[i])) {
Googler9cda2ec2019-03-25 14:48:26 -070090 string& current_arg = args[i];
nharmatabe4b2dc2020-03-14 00:52:56 -070091
92 bool is_nullary;
93 if (!startup_options_->MaybeCheckValidNullary(current_arg, &is_nullary,
94 error)) {
95 return nullptr;
96 }
97
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +000098 // If the current argument is a valid nullary startup option such as
99 // --master_bazelrc or --nomaster_bazelrc proceed to examine the next
100 // argument.
nharmatabe4b2dc2020-03-14 00:52:56 -0700101 if (is_nullary) {
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000102 startup_args.push_back(current_arg);
103 i++;
michajloeaf066b2019-05-15 14:12:57 -0700104 } else if (startup_options_->IsUnary(current_arg)) {
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000105 // If the current argument is a valid unary startup option such as
106 // --bazelrc there are two cases to consider.
107
108 // The option is of the form '--bazelrc=value', hence proceed to
109 // examine the next argument.
ckennelly86095992020-12-16 07:47:03 -0800110 if (current_arg.find('=') != string::npos) {
Googler9cda2ec2019-03-25 14:48:26 -0700111 startup_args.push_back(std::move(current_arg));
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000112 i++;
113 } else {
114 // Otherwise, the option is of the form '--bazelrc value', hence
115 // skip the next argument and proceed to examine the argument located
116 // two places after.
117 if (i + 1 >= args.size()) {
118 blaze_util::StringPrintf(
119 error,
120 "Startup option '%s' expects a value.\n"
121 "Usage: '%s=somevalue' or '%s somevalue'.\n"
122 " For more info, run '%s help startup_options'.",
123 current_arg.c_str(), current_arg.c_str(), current_arg.c_str(),
124 lowercase_product_name.c_str());
125 return nullptr;
126 }
127 // In this case we transform it to the form '--bazelrc=value'.
Googler9cda2ec2019-03-25 14:48:26 -0700128 startup_args.push_back(std::move(current_arg) + "=" +
129 std::move(args[i + 1]));
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000130 i += 2;
131 }
132 } else {
133 // If the current argument is not a valid unary or nullary startup option
134 // then fail.
135 blaze_util::StringPrintf(
136 error,
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000137 "Unknown startup option: '%s'.\n"
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000138 " For more info, run '%s help startup_options'.",
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000139 current_arg.c_str(), lowercase_product_name.c_str());
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000140 return nullptr;
141 }
142 }
143
144 // The command is the arg right after the startup options.
145 if (i == args.size()) {
Googler9cda2ec2019-03-25 14:48:26 -0700146 return std::unique_ptr<CommandLine>(new CommandLine(
147 std::move(path_to_binary), std::move(startup_args), "", {}));
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000148 }
Googler9cda2ec2019-03-25 14:48:26 -0700149 string& command = args[i];
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000150
151 // The rest are the command arguments.
Googler9cda2ec2019-03-25 14:48:26 -0700152 vector<string> command_args(std::make_move_iterator(args.begin() + i + 1),
153 std::make_move_iterator(args.end()));
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000154
155 return std::unique_ptr<CommandLine>(
Googler9cda2ec2019-03-25 14:48:26 -0700156 new CommandLine(std::move(path_to_binary), std::move(startup_args),
157 std::move(command), std::move(command_args)));
Luis Fernando Pino Duque0311fc52016-11-21 13:20:50 +0000158}
159
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700160namespace internal {
Julio Merino726ca5a2017-01-20 14:05:26 +0000161
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700162std::string FindLegacyUserBazelrc(const char* cmd_line_rc_file,
163 const std::string& workspace) {
ccalvarind8dfd782018-04-19 08:47:28 -0700164 if (cmd_line_rc_file != nullptr) {
ccalvarin755278d2018-06-07 11:05:54 -0700165 string rcFile = blaze::AbsolutePathFromFlag(cmd_line_rc_file);
Laszlo Csomor00549b42017-01-11 09:12:10 +0000166 if (!blaze_util::CanReadFile(rcFile)) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700167 // The actual rc file reading will catch this - we ignore this here in the
168 // legacy version since this is just a warning. Exit eagerly though.
169 return "";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100170 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700171 return rcFile;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 }
173
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700174 string workspaceRcFile = blaze_util::JoinPath(workspace, kRcBasename);
Laszlo Csomor00549b42017-01-11 09:12:10 +0000175 if (blaze_util::CanReadFile(workspaceRcFile)) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700176 return workspaceRcFile;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177 }
178
László Csomorfa27b502017-03-23 15:09:34 +0000179 string home = blaze::GetHomeDir();
ccalvarind8dfd782018-04-19 08:47:28 -0700180 if (!home.empty()) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700181 string userRcFile = blaze_util::JoinPath(home, kRcBasename);
ccalvarind8dfd782018-04-19 08:47:28 -0700182 if (blaze_util::CanReadFile(userRcFile)) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700183 return userRcFile;
ccalvarind8dfd782018-04-19 08:47:28 -0700184 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700186 return "";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187}
188
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700189std::set<std::string> GetOldRcPaths(
190 const WorkspaceLayout* workspace_layout, const std::string& workspace,
191 const std::string& cwd, const std::string& path_to_binary,
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700192 const std::vector<std::string>& startup_args,
193 const std::string& system_bazelrc_path) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700194 // Find the old list of rc files that would have been loaded here, so we can
195 // provide a useful warning about old rc files that might no longer be read.
196 std::vector<std::string> candidate_bazelrc_paths;
197 if (SearchNullaryOption(startup_args, "master_bazelrc", true)) {
198 const std::string workspace_rc =
199 workspace_layout->GetWorkspaceRcPath(workspace, startup_args);
200 const std::string binary_rc =
201 internal::FindRcAlongsideBinary(cwd, path_to_binary);
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700202 candidate_bazelrc_paths = {workspace_rc, binary_rc, system_bazelrc_path};
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700203 }
wisechengyi61da1d22021-03-22 14:20:33 -0700204 vector<std::string> cmd_line_rc_files =
205 GetAllUnaryOptionValues(startup_args, "--bazelrc", "/dev/null");
206 // Divide the cases where the vector is empty vs not, as
207 // `FindLegacyUserBazelrc` has a case for rc_file to be a nullptr.
208 if (cmd_line_rc_files.empty()) {
209 string user_bazelrc_path =
210 internal::FindLegacyUserBazelrc(nullptr, workspace);
211 if (!user_bazelrc_path.empty()) {
212 candidate_bazelrc_paths.push_back(user_bazelrc_path);
213 }
214 } else {
215 for (auto& rc_file : cmd_line_rc_files) {
216 string user_bazelrc_path =
217 internal::FindLegacyUserBazelrc(rc_file.c_str(), workspace);
218 if (!user_bazelrc_path.empty()) {
219 candidate_bazelrc_paths.push_back(user_bazelrc_path);
220 }
221 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700222 }
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800223 // DedupeBlazercPaths returns paths whose canonical path could be computed,
224 // therefore these paths must exist.
225 const std::vector<std::string> deduped_existing_blazerc_paths =
226 internal::DedupeBlazercPaths(candidate_bazelrc_paths);
227 return std::set<std::string>(deduped_existing_blazerc_paths.begin(),
228 deduped_existing_blazerc_paths.end());
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700229}
lpinoc00ba522017-07-10 19:17:40 +0200230
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800231// Deduplicates the given paths based on their canonical form.
232// Computes the canonical form using blaze_util::MakeCanonical.
233// Returns the unique paths in their original form (not the canonical one).
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700234std::vector<std::string> DedupeBlazercPaths(
235 const std::vector<std::string>& paths) {
236 std::set<std::string> canonical_paths;
237 std::vector<std::string> result;
238 for (const std::string& path : paths) {
239 const std::string canonical_path = blaze_util::MakeCanonical(path.c_str());
lpinob379f652017-04-07 12:05:09 +0000240 if (canonical_path.empty()) {
241 // MakeCanonical returns an empty string when it fails. We ignore this
242 // failure since blazerc paths may point to invalid locations.
243 } else if (canonical_paths.find(canonical_path) == canonical_paths.end()) {
244 result.push_back(path);
245 canonical_paths.insert(canonical_path);
246 }
247 }
248 return result;
249}
lpinoc00ba522017-07-10 19:17:40 +0200250
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700251std::string FindSystemWideRc(const std::string& system_bazelrc_path) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700252 const std::string path =
Andrew Suffield49107ad2019-03-08 09:57:38 -0800253 blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path);
ccalvarinbccf9c62018-06-20 15:05:23 -0700254 if (blaze_util::CanReadFile(path)) {
255 return path;
256 }
ccalvarinbccf9c62018-06-20 15:05:23 -0700257 return "";
258}
259
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700260std::string FindRcAlongsideBinary(const std::string& cwd,
261 const std::string& path_to_binary) {
262 const std::string path = blaze_util::IsAbsolute(path_to_binary)
263 ? path_to_binary
264 : blaze_util::JoinPath(cwd, path_to_binary);
265 const std::string base = blaze_util::Basename(path_to_binary);
266 const std::string binary_blazerc_path = path + "." + base + "rc";
ccalvarind8dfd782018-04-19 08:47:28 -0700267 if (blaze_util::CanReadFile(binary_blazerc_path)) {
268 return binary_blazerc_path;
269 }
270 return "";
271}
272
Googlerebd28a92018-02-07 08:46:31 -0800273blaze_exit_code::ExitCode ParseErrorToExitCode(RcFile::ParseError parse_error) {
274 switch (parse_error) {
275 case RcFile::ParseError::NONE:
276 return blaze_exit_code::SUCCESS;
277 case RcFile::ParseError::UNREADABLE_FILE:
ccalvarin8ceaa652018-08-24 12:44:31 -0700278 // We check readability before parsing, so this is unexpected for
279 // top-level rc files, so is an INTERNAL_ERROR. It can happen for imported
280 // files, however, which should be BAD_ARGV, but we don't currently
281 // differentiate.
282 // TODO(bazel-team): fix RcFile to reclassify unreadable files that were
283 // read from a recursive call due to a malformed import.
Googlerebd28a92018-02-07 08:46:31 -0800284 return blaze_exit_code::INTERNAL_ERROR;
285 case RcFile::ParseError::INVALID_FORMAT:
286 case RcFile::ParseError::IMPORT_LOOP:
287 return blaze_exit_code::BAD_ARGV;
288 default:
289 return blaze_exit_code::INTERNAL_ERROR;
290 }
291}
292
ccalvarin8ceaa652018-08-24 12:44:31 -0700293void WarnAboutDuplicateRcFiles(const std::set<std::string>& read_files,
Googler1980fdb2020-09-01 13:49:34 -0700294 const std::vector<std::string>& loaded_rcs) {
ccalvarin8ceaa652018-08-24 12:44:31 -0700295 // The first rc file in the queue is the top-level one, the one that would
296 // have imported all the others in the queue. The top-level rc is one of the
297 // default locations (system, workspace, home) or the explicit path passed by
298 // --bazelrc.
299 const std::string& top_level_rc = loaded_rcs.front();
300
301 const std::set<std::string> unique_loaded_rcs(loaded_rcs.begin(),
302 loaded_rcs.end());
303 // First check if each of the newly loaded rc files was already read.
304 for (const std::string& loaded_rc : unique_loaded_rcs) {
305 if (read_files.count(loaded_rc) > 0) {
306 if (loaded_rc == top_level_rc) {
307 BAZEL_LOG(WARNING)
308 << "Duplicate rc file: " << loaded_rc
309 << " is read multiple times, it is a standard rc file location "
jingwen68c57f02018-11-21 16:17:17 -0800310 "but must have been unnecessarily imported earlier.";
ccalvarin8ceaa652018-08-24 12:44:31 -0700311 } else {
312 BAZEL_LOG(WARNING)
313 << "Duplicate rc file: " << loaded_rc
314 << " is read multiple times, most recently imported from "
315 << top_level_rc;
316 }
317 }
318 // Now check if the top-level rc file loads up its own duplicates (it can't
319 // be a cycle, since that would be an error and we would have already
320 // exited, but it could have a diamond dependency of some sort.)
321 if (std::count(loaded_rcs.begin(), loaded_rcs.end(), loaded_rc) > 1) {
322 BAZEL_LOG(WARNING) << "Duplicate rc file: " << loaded_rc
323 << " is imported multiple times from " << top_level_rc;
324 }
325 }
326}
327
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800328std::vector<std::string> GetLostFiles(
329 const std::set<std::string>& old_files,
330 const std::set<std::string>& read_files_canon) {
331 std::vector<std::string> result;
332 for (const auto& old : old_files) {
333 std::string old_canon = blaze_util::MakeCanonical(old.c_str());
334 if (!old_canon.empty() &&
335 read_files_canon.find(old_canon) == read_files_canon.end()) {
336 result.push_back(old);
337 }
338 }
339 return result;
340}
341
lpinob379f652017-04-07 12:05:09 +0000342} // namespace internal
343
ccalvarind8dfd782018-04-19 08:47:28 -0700344// TODO(#4502) Consider simplifying result_rc_files to a vector of RcFiles, no
345// unique_ptrs.
346blaze_exit_code::ExitCode OptionProcessor::GetRcFiles(
347 const WorkspaceLayout* workspace_layout, const std::string& workspace,
348 const std::string& cwd, const CommandLine* cmd_line,
349 std::vector<std::unique_ptr<RcFile>>* result_rc_files,
350 std::string* error) const {
351 assert(cmd_line != nullptr);
352 assert(result_rc_files != nullptr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100353
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700354 std::vector<std::string> rc_files;
355
356 // Get the system rc (unless --nosystem_rc).
357 if (SearchNullaryOption(cmd_line->startup_args, "system_rc", true)) {
Andrew Suffield49107ad2019-03-08 09:57:38 -0800358 // MakeAbsoluteAndResolveEnvvars will standardize the form of the
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700359 // provided path. This also means we accept relative paths, which is
360 // is convenient for testing.
361 const std::string system_rc =
Andrew Suffield49107ad2019-03-08 09:57:38 -0800362 blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path_);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700363 rc_files.push_back(system_rc);
ccalvarind8dfd782018-04-19 08:47:28 -0700364 }
365
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700366 // Get the workspace rc: %workspace%/.bazelrc (unless --noworkspace_rc), but
367 // only if we are in a workspace: invoking commands like "help" from outside
368 // a workspace should work.
369 if (!workspace.empty() &&
370 SearchNullaryOption(cmd_line->startup_args, "workspace_rc", true)) {
371 const std::string workspaceRcFile =
372 blaze_util::JoinPath(workspace, kRcBasename);
373 rc_files.push_back(workspaceRcFile);
ccalvarind8dfd782018-04-19 08:47:28 -0700374 }
375
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700376 // Get the user rc: $HOME/.bazelrc (unless --nohome_rc)
377 if (SearchNullaryOption(cmd_line->startup_args, "home_rc", true)) {
378 const std::string home = blaze::GetHomeDir();
philwob8d57082018-11-22 07:33:53 -0800379 if (!home.empty()) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700380 rc_files.push_back(blaze_util::JoinPath(home, kRcBasename));
ccalvarind8dfd782018-04-19 08:47:28 -0700381 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700382 }
383
384 // Get the command-line provided rc, passed as --bazelrc or nothing if the
385 // flag is absent.
wisechengyi61da1d22021-03-22 14:20:33 -0700386 vector<std::string> cmd_line_rc_files =
387 GetAllUnaryOptionValues(cmd_line->startup_args, "--bazelrc", "/dev/null");
388 for (auto& rc_file : cmd_line_rc_files) {
389 string absolute_cmd_line_rc = blaze::AbsolutePathFromFlag(rc_file);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700390 // Unlike the previous 3 paths, where we ignore it if the file does not
391 // exist or is unreadable, since this path is explicitly passed, this is an
392 // error. Check this condition here.
393 if (!blaze_util::CanReadFile(absolute_cmd_line_rc)) {
394 BAZEL_LOG(ERROR) << "Error: Unable to read .bazelrc file '"
395 << absolute_cmd_line_rc << "'.";
396 return blaze_exit_code::BAD_ARGV;
397 }
398 rc_files.push_back(absolute_cmd_line_rc);
399 }
400
401 // Log which files we're looking for before removing duplicates and
402 // non-existent files, so that this can serve to debug why a certain file is
403 // not being read. The final files which are read will be logged as they are
404 // parsed, and can be found using --announce_rc.
405 std::string joined_rcs;
406 blaze_util::JoinStrings(rc_files, ',', &joined_rcs);
407 BAZEL_LOG(INFO) << "Looking for the following rc files: " << joined_rcs;
408
409 // It's possible that workspace == home, that files are symlinks for each
410 // other, or that the --bazelrc flag is a duplicate. Dedupe them to minimize
411 // the likelihood of repeated options. Since bazelrcs can include one another,
ccalvarin8ceaa652018-08-24 12:44:31 -0700412 // this isn't sufficient to prevent duplicate options, so we also warn if we
413 // discover duplicate loads later. This also has the effect of removing paths
414 // that don't point to real files.
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700415 rc_files = internal::DedupeBlazercPaths(rc_files);
416
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800417 std::set<std::string> read_files_canonical_paths;
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700418 // Parse these potential files, in priority order;
ccalvarin8ceaa652018-08-24 12:44:31 -0700419 for (const std::string& top_level_bazelrc_path : rc_files) {
ccalvarind8dfd782018-04-19 08:47:28 -0700420 std::unique_ptr<RcFile> parsed_rc;
421 blaze_exit_code::ExitCode parse_rcfile_exit_code = ParseRcFile(
ccalvarin8ceaa652018-08-24 12:44:31 -0700422 workspace_layout, workspace, top_level_bazelrc_path, &parsed_rc, error);
ccalvarind8dfd782018-04-19 08:47:28 -0700423 if (parse_rcfile_exit_code != blaze_exit_code::SUCCESS) {
424 return parse_rcfile_exit_code;
425 }
ccalvarin8ceaa652018-08-24 12:44:31 -0700426
427 // Check that none of the rc files loaded this time are duplicate.
Googler1980fdb2020-09-01 13:49:34 -0700428 const auto& sources = parsed_rc->canonical_source_paths();
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800429 internal::WarnAboutDuplicateRcFiles(read_files_canonical_paths, sources);
430 read_files_canonical_paths.insert(sources.begin(), sources.end());
ccalvarin8ceaa652018-08-24 12:44:31 -0700431
ccalvarind8dfd782018-04-19 08:47:28 -0700432 result_rc_files->push_back(std::move(parsed_rc));
433 }
434
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700435 // Provide a warning for any old file that might have been missed with the new
436 // expectations. This compares "canonical" paths to one another, so should not
437 // require additional transformation.
438 // TODO(b/36168162): Remove this warning along with
439 // internal::GetOldRcPaths and internal::FindLegacyUserBazelrc after
440 // the transition period has passed.
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700441 const std::set<std::string> old_files = internal::GetOldRcPaths(
442 workspace_layout, workspace, cwd, cmd_line->path_to_binary,
443 cmd_line->startup_args, internal::FindSystemWideRc(system_bazelrc_path_));
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700444
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800445 std::vector<std::string> lost_files =
446 internal::GetLostFiles(old_files, read_files_canonical_paths);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700447 if (!lost_files.empty()) {
448 std::string joined_lost_rcs;
449 blaze_util::JoinStrings(lost_files, '\n', &joined_lost_rcs);
450 BAZEL_LOG(WARNING)
451 << "The following rc files are no longer being read, please transfer "
452 "their contents or import their path into one of the standard rc "
453 "files:\n"
454 << joined_lost_rcs;
455 }
456
ccalvarind8dfd782018-04-19 08:47:28 -0700457 return blaze_exit_code::SUCCESS;
458}
459
460blaze_exit_code::ExitCode ParseRcFile(const WorkspaceLayout* workspace_layout,
461 const std::string& workspace,
462 const std::string& rc_file_path,
463 std::unique_ptr<RcFile>* result_rc_file,
464 std::string* error) {
465 assert(!rc_file_path.empty());
466 assert(result_rc_file != nullptr);
467
468 RcFile::ParseError parse_error;
469 std::unique_ptr<RcFile> parsed_file = RcFile::Parse(
470 rc_file_path, workspace_layout, workspace, &parse_error, error);
471 if (parsed_file == nullptr) {
472 return internal::ParseErrorToExitCode(parse_error);
473 }
474 *result_rc_file = std::move(parsed_file);
475 return blaze_exit_code::SUCCESS;
476}
477
478blaze_exit_code::ExitCode OptionProcessor::ParseOptions(
479 const vector<string>& args, const string& workspace, const string& cwd,
480 string* error) {
michajlo319cd4f2019-05-20 11:17:37 -0700481 assert(!parse_options_called_);
michajloeaf066b2019-05-15 14:12:57 -0700482 parse_options_called_ = true;
lpinoc00ba522017-07-10 19:17:40 +0200483 cmd_line_ = SplitCommandLine(args, error);
484 if (cmd_line_ == nullptr) {
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000485 return blaze_exit_code::BAD_ARGV;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100486 }
ccalvarind8dfd782018-04-19 08:47:28 -0700487
ccalvarin90e116c2018-05-15 16:41:19 -0700488 // Read the rc files, unless --ignore_all_rc_files was provided on the command
489 // line. This depends on the startup options in argv since these may contain
490 // other rc-modifying options. For all other options, the precedence of
ccalvarind8dfd782018-04-19 08:47:28 -0700491 // options will be rc first, then command line options, though, despite this
492 // exception.
jmmv64553792018-04-19 12:32:59 -0700493 std::vector<std::unique_ptr<RcFile>> rc_files;
ccalvarin90e116c2018-05-15 16:41:19 -0700494 if (!SearchNullaryOption(cmd_line_->startup_args, "ignore_all_rc_files",
495 false)) {
496 const blaze_exit_code::ExitCode rc_parsing_exit_code = GetRcFiles(
497 workspace_layout_, workspace, cwd, cmd_line_.get(), &rc_files, error);
498 if (rc_parsing_exit_code != blaze_exit_code::SUCCESS) {
499 return rc_parsing_exit_code;
500 }
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000501 }
502
Googler94833922020-09-02 12:09:40 -0700503 // The helpers expect regular pointers, not unique_ptrs.
504 std::vector<RcFile*> rc_file_ptrs;
505 rc_file_ptrs.reserve(rc_files.size());
506 for (auto& rc_file : rc_files) { rc_file_ptrs.push_back(rc_file.get()); }
507
ccalvarind8dfd782018-04-19 08:47:28 -0700508 // Parse the startup options in the correct priority order.
509 const blaze_exit_code::ExitCode parse_startup_options_exit_code =
Googler94833922020-09-02 12:09:40 -0700510 ParseStartupOptions(rc_file_ptrs, error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100511 if (parse_startup_options_exit_code != blaze_exit_code::SUCCESS) {
512 return parse_startup_options_exit_code;
513 }
514
Googlerebd28a92018-02-07 08:46:31 -0800515 blazerc_and_env_command_args_ =
Googler94833922020-09-02 12:09:40 -0700516 GetBlazercAndEnvCommandArgs(cwd, rc_file_ptrs, GetProcessedEnv());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100517 return blaze_exit_code::SUCCESS;
518}
519
ccalvarin1cbe62a2017-08-14 21:09:07 +0200520static void PrintStartupOptions(const std::string& source,
521 const std::vector<std::string>& options) {
522 if (!source.empty()) {
523 std::string startup_args;
524 blaze_util::JoinStrings(options, ' ', &startup_args);
525 fprintf(stderr, "INFO: Reading 'startup' options from %s: %s\n",
526 source.c_str(), startup_args.c_str());
527 }
528}
529
530void OptionProcessor::PrintStartupOptionsProvenanceMessage() const {
531 StartupOptions* parsed_startup_options = GetParsedStartupOptions();
532
533 // Print the startup flags in the order they are parsed, to keep the
jingwen68c57f02018-11-21 16:17:17 -0800534 // precedence clear. In order to minimize the number of lines of output in
ccalvarin1cbe62a2017-08-14 21:09:07 +0200535 // the terminal, group sequential flags by origin. Note that an rc file may
536 // turn up multiple times in this list, if, for example, it imports another
537 // rc file and contains startup options on either side of the import
538 // statement. This is done intentionally to make option priority clear.
539 std::string command_line_source;
540 std::string& most_recent_blazerc = command_line_source;
541 std::vector<std::string> accumulated_options;
Googler80eb1932018-08-05 22:21:34 -0700542 for (const auto& flag : parsed_startup_options->original_startup_options_) {
ccalvarin1cbe62a2017-08-14 21:09:07 +0200543 if (flag.source == most_recent_blazerc) {
544 accumulated_options.push_back(flag.value);
545 } else {
546 PrintStartupOptions(most_recent_blazerc, accumulated_options);
547 // Start accumulating again.
548 accumulated_options.clear();
549 accumulated_options.push_back(flag.value);
550 most_recent_blazerc = flag.source;
551 }
552 }
553 // Don't forget to print out the last ones.
554 PrintStartupOptions(most_recent_blazerc, accumulated_options);
555}
556
lpinoc00ba522017-07-10 19:17:40 +0200557blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(
Googler94833922020-09-02 12:09:40 -0700558 const std::vector<RcFile*> &rc_files, std::string *error) {
ccalvarin1cbe62a2017-08-14 21:09:07 +0200559 // Rc files can import other files at any point, and these imported rcs are
jmmv64553792018-04-19 12:32:59 -0700560 // expanded in place. Here, we isolate just the startup options but keep the
561 // file they came from attached for the option_sources tracking and for
562 // sending to the server.
lpinoc00ba522017-07-10 19:17:40 +0200563 std::vector<RcStartupFlag> rcstartup_flags;
564
Googler94833922020-09-02 12:09:40 -0700565 for (const auto* blazerc : rc_files) {
Googlerebd28a92018-02-07 08:46:31 -0800566 const auto iter = blazerc->options().find("startup");
567 if (iter == blazerc->options().end()) continue;
568
569 for (const RcOption& option : iter->second) {
Googler1980fdb2020-09-01 13:49:34 -0700570 const std::string& source_path =
571 blazerc->canonical_source_paths()[option.source_index];
572 rcstartup_flags.push_back({source_path, option.option});
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100573 }
574 }
575
lpinoc00ba522017-07-10 19:17:40 +0200576 for (const std::string& arg : cmd_line_->startup_args) {
577 if (!IsArg(arg)) {
578 break;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100579 }
lpinoc00ba522017-07-10 19:17:40 +0200580 rcstartup_flags.push_back(RcStartupFlag("", arg));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100581 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100582
michajloeaf066b2019-05-15 14:12:57 -0700583 return startup_options_->ProcessArgs(rcstartup_flags, error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100584}
585
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200586static bool IsValidEnvName(const char* p) {
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700587#if defined(_WIN32) || defined(__CYGWIN__)
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200588 for (; *p && *p != '='; ++p) {
589 if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
Nikolay Shelukhina9b82972020-01-28 07:55:18 -0800590 (*p >= '0' && *p <= '9') || *p == '_' || *p == '(' || *p == ')')) {
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200591 return false;
592 }
593 }
594#endif
595 return true;
596}
597
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700598#if defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000599static void PreprocessEnvString(string* env_str) {
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800600 static constexpr const char* vars_to_uppercase[] = {"PATH", "SYSTEMROOT",
pcloudy8c21b0b2018-09-19 07:19:09 -0700601 "SYSTEMDRIVE",
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800602 "TEMP", "TEMPDIR", "TMP"};
Dmitry Lomov49501112017-03-07 22:55:42 +0000603
604 int pos = env_str->find_first_of('=');
605 if (pos == string::npos) return;
606
607 string name = env_str->substr(0, pos);
608 // We do not care about locale. All variable names are ASCII.
609 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800610 if (std::find(std::begin(vars_to_uppercase), std::end(vars_to_uppercase),
611 name) != std::end(vars_to_uppercase)) {
Dmitry Lomov49501112017-03-07 22:55:42 +0000612 env_str->assign(name + "=" + env_str->substr(pos + 1));
613 }
614}
615
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700616#elif defined(__CYGWIN__) // not defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000617
618static void PreprocessEnvString(string* env_str) {
619 int pos = env_str->find_first_of('=');
620 if (pos == string::npos) return;
621 string name = env_str->substr(0, pos);
622 if (name == "PATH") {
Loo Rong Jief049cde2018-01-26 06:29:32 -0800623 env_str->assign("PATH=" + env_str->substr(pos + 1));
Dmitry Lomov49501112017-03-07 22:55:42 +0000624 } else if (name == "TMP") {
625 // A valid Windows path "c:/foo" is also a valid Unix path list of
laszlocsomorf7f1a8f2017-08-03 17:21:44 +0200626 // ["c", "/foo"] so must use ConvertPath here. See GitHub issue #1684.
ccalvarinac69da02018-06-05 15:27:26 -0700627 env_str->assign("TMP=" + blaze_util::ConvertPath(env_str->substr(pos + 1)));
Dmitry Lomov49501112017-03-07 22:55:42 +0000628 }
629}
630
631#else // Non-Windows platforms.
632
633static void PreprocessEnvString(const string* env_str) {
634 // do nothing.
635}
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700636#endif // defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000637
Googlerebd28a92018-02-07 08:46:31 -0800638static std::vector<std::string> GetProcessedEnv() {
639 std::vector<std::string> processed_env;
Vertexwahn26c7e102021-03-10 07:25:59 -0800640 for (char** env = environ; *env != nullptr; env++) {
Googlerebd28a92018-02-07 08:46:31 -0800641 string env_str(*env);
642 if (IsValidEnvName(*env)) {
643 PreprocessEnvString(&env_str);
644 processed_env.push_back(std::move(env_str));
645 }
646 }
647 return processed_env;
648}
649
ccalvarin35ce88d2017-12-11 10:57:03 -0800650// IMPORTANT: The options added here do not come from the user. In order for
651// their source to be correctly tracked, the options must either be passed
652// as --default_override=0, 0 being "client", or must be listed in
653// BlazeOptionHandler.INTERNAL_COMMAND_OPTIONS!
lpinoc00ba522017-07-10 19:17:40 +0200654std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs(
Googlerebd28a92018-02-07 08:46:31 -0800655 const std::string& cwd,
Googler94833922020-09-02 12:09:40 -0700656 const std::vector<RcFile*>& blazercs,
Googlerebd28a92018-02-07 08:46:31 -0800657 const std::vector<std::string>& env) {
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000658 // Provide terminal options as coming from the least important rc file.
lpinoc00ba522017-07-10 19:17:40 +0200659 std::vector<std::string> result = {
ccalvarin35ce88d2017-12-11 10:57:03 -0800660 "--rc_source=client",
Googlerd22eddc2018-09-11 15:03:17 -0700661 "--default_override=0:common=--isatty=" +
michajlo479c9042020-03-27 11:43:39 -0700662 blaze_util::ToString(IsStandardTerminal()),
ccalvarin35ce88d2017-12-11 10:57:03 -0800663 "--default_override=0:common=--terminal_columns=" +
michajlo479c9042020-03-27 11:43:39 -0700664 blaze_util::ToString(GetTerminalColumns())};
ccalvarin35ce88d2017-12-11 10:57:03 -0800665 if (IsEmacsTerminal()) {
666 result.push_back("--default_override=0:common=--emacs");
667 }
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000668
Yun Peng213a52a2017-09-14 11:11:48 +0200669 EnsurePythonPathOption(&result);
670
Googlerebd28a92018-02-07 08:46:31 -0800671 // Map .blazerc numbers to filenames. The indexes here start at 1 because #0
672 // is reserved the "client" options created by this function.
673 int cur_index = 1;
674 std::map<std::string, int> rcfile_indexes;
Googler94833922020-09-02 12:09:40 -0700675 for (const auto* blazerc : blazercs) {
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800676 for (const std::string& source_path : blazerc->canonical_source_paths()) {
Googlerebd28a92018-02-07 08:46:31 -0800677 // Deduplicate the rc_source list because the same file might be included
678 // from multiple places.
679 if (rcfile_indexes.find(source_path) != rcfile_indexes.end()) continue;
680
ccalvarinac69da02018-06-05 15:27:26 -0700681 result.push_back("--rc_source=" + blaze_util::ConvertPath(source_path));
Googlerebd28a92018-02-07 08:46:31 -0800682 rcfile_indexes[source_path] = cur_index;
683 cur_index++;
684 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100685 }
686
Googlerebd28a92018-02-07 08:46:31 -0800687 // Add RcOptions as default_overrides.
Googler94833922020-09-02 12:09:40 -0700688 for (const auto* blazerc : blazercs) {
Googlerebd28a92018-02-07 08:46:31 -0800689 for (const auto& command_options : blazerc->options()) {
690 const string& command = command_options.first;
691 // Skip startup flags, which are already parsed by the client.
692 if (command == "startup") continue;
693
694 for (const RcOption& rcoption : command_options.second) {
Googler1980fdb2020-09-01 13:49:34 -0700695 const std::string& source_path =
696 blazerc->canonical_source_paths()[rcoption.source_index];
Googlerebd28a92018-02-07 08:46:31 -0800697 std::ostringstream oss;
Googler1980fdb2020-09-01 13:49:34 -0700698 oss << "--default_override=" << rcfile_indexes[source_path] << ':'
699 << command << '=' << rcoption.option;
Googlerebd28a92018-02-07 08:46:31 -0800700 result.push_back(oss.str());
lpinoc00ba522017-07-10 19:17:40 +0200701 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100702 }
703 }
704
Dmitry Lomov31fab292017-03-07 18:33:08 +0000705 // Pass the client environment to the server.
Googlerebd28a92018-02-07 08:46:31 -0800706 for (const string& env_var : env) {
Googlerebd28a92018-02-07 08:46:31 -0800707 result.push_back("--client_env=" + env_var);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100708 }
ccalvarinac69da02018-06-05 15:27:26 -0700709 result.push_back("--client_cwd=" + blaze_util::ConvertPath(cwd));
lpinoc00ba522017-07-10 19:17:40 +0200710 return result;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100711}
712
lpinoc00ba522017-07-10 19:17:40 +0200713std::vector<std::string> OptionProcessor::GetCommandArguments() const {
714 assert(cmd_line_ != nullptr);
715 // When the user didn't specify a command, the server expects the command
716 // arguments to be empty in order to display the help message.
717 if (cmd_line_->command.empty()) {
718 return {};
719 }
720
721 std::vector<std::string> command_args = blazerc_and_env_command_args_;
722 command_args.insert(command_args.end(),
723 cmd_line_->command_args.begin(),
724 cmd_line_->command_args.end());
725 return command_args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100726}
727
lpino233b72d2017-07-05 11:08:40 -0400728std::vector<std::string> OptionProcessor::GetExplicitCommandArguments() const {
lpinoc00ba522017-07-10 19:17:40 +0200729 assert(cmd_line_ != nullptr);
730 return cmd_line_->command_args;
lpino233b72d2017-07-05 11:08:40 -0400731}
732
lpinoc00ba522017-07-10 19:17:40 +0200733std::string OptionProcessor::GetCommand() const {
734 assert(cmd_line_ != nullptr);
735 return cmd_line_->command;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100736}
737
Julio Merino28774852016-09-14 16:59:46 +0000738StartupOptions* OptionProcessor::GetParsedStartupOptions() const {
michajloeaf066b2019-05-15 14:12:57 -0700739 assert(parse_options_called_);
740 return startup_options_.get();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100741}
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000742
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100743} // namespace blaze