blob: 9a702ffcd41fdcf56986a04c99948811bebdd17a [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.
110 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 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700204 string user_bazelrc_path = internal::FindLegacyUserBazelrc(
laszlocsomor392f24a2019-08-07 05:56:23 -0700205 SearchUnaryOption(startup_args, "--bazelrc", /* warn_if_dupe */ true),
206 workspace);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700207 if (!user_bazelrc_path.empty()) {
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800208 candidate_bazelrc_paths.push_back(user_bazelrc_path);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700209 }
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800210 // DedupeBlazercPaths returns paths whose canonical path could be computed,
211 // therefore these paths must exist.
212 const std::vector<std::string> deduped_existing_blazerc_paths =
213 internal::DedupeBlazercPaths(candidate_bazelrc_paths);
214 return std::set<std::string>(deduped_existing_blazerc_paths.begin(),
215 deduped_existing_blazerc_paths.end());
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700216}
lpinoc00ba522017-07-10 19:17:40 +0200217
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800218// Deduplicates the given paths based on their canonical form.
219// Computes the canonical form using blaze_util::MakeCanonical.
220// Returns the unique paths in their original form (not the canonical one).
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700221std::vector<std::string> DedupeBlazercPaths(
222 const std::vector<std::string>& paths) {
223 std::set<std::string> canonical_paths;
224 std::vector<std::string> result;
225 for (const std::string& path : paths) {
226 const std::string canonical_path = blaze_util::MakeCanonical(path.c_str());
lpinob379f652017-04-07 12:05:09 +0000227 if (canonical_path.empty()) {
228 // MakeCanonical returns an empty string when it fails. We ignore this
229 // failure since blazerc paths may point to invalid locations.
230 } else if (canonical_paths.find(canonical_path) == canonical_paths.end()) {
231 result.push_back(path);
232 canonical_paths.insert(canonical_path);
233 }
234 }
235 return result;
236}
lpinoc00ba522017-07-10 19:17:40 +0200237
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700238std::string FindSystemWideRc(const std::string& system_bazelrc_path) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700239 const std::string path =
Andrew Suffield49107ad2019-03-08 09:57:38 -0800240 blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path);
ccalvarinbccf9c62018-06-20 15:05:23 -0700241 if (blaze_util::CanReadFile(path)) {
242 return path;
243 }
ccalvarinbccf9c62018-06-20 15:05:23 -0700244 return "";
245}
246
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700247std::string FindRcAlongsideBinary(const std::string& cwd,
248 const std::string& path_to_binary) {
249 const std::string path = blaze_util::IsAbsolute(path_to_binary)
250 ? path_to_binary
251 : blaze_util::JoinPath(cwd, path_to_binary);
252 const std::string base = blaze_util::Basename(path_to_binary);
253 const std::string binary_blazerc_path = path + "." + base + "rc";
ccalvarind8dfd782018-04-19 08:47:28 -0700254 if (blaze_util::CanReadFile(binary_blazerc_path)) {
255 return binary_blazerc_path;
256 }
257 return "";
258}
259
Googlerebd28a92018-02-07 08:46:31 -0800260blaze_exit_code::ExitCode ParseErrorToExitCode(RcFile::ParseError parse_error) {
261 switch (parse_error) {
262 case RcFile::ParseError::NONE:
263 return blaze_exit_code::SUCCESS;
264 case RcFile::ParseError::UNREADABLE_FILE:
ccalvarin8ceaa652018-08-24 12:44:31 -0700265 // We check readability before parsing, so this is unexpected for
266 // top-level rc files, so is an INTERNAL_ERROR. It can happen for imported
267 // files, however, which should be BAD_ARGV, but we don't currently
268 // differentiate.
269 // TODO(bazel-team): fix RcFile to reclassify unreadable files that were
270 // read from a recursive call due to a malformed import.
Googlerebd28a92018-02-07 08:46:31 -0800271 return blaze_exit_code::INTERNAL_ERROR;
272 case RcFile::ParseError::INVALID_FORMAT:
273 case RcFile::ParseError::IMPORT_LOOP:
274 return blaze_exit_code::BAD_ARGV;
275 default:
276 return blaze_exit_code::INTERNAL_ERROR;
277 }
278}
279
ccalvarin8ceaa652018-08-24 12:44:31 -0700280void WarnAboutDuplicateRcFiles(const std::set<std::string>& read_files,
Googler1980fdb2020-09-01 13:49:34 -0700281 const std::vector<std::string>& loaded_rcs) {
ccalvarin8ceaa652018-08-24 12:44:31 -0700282 // The first rc file in the queue is the top-level one, the one that would
283 // have imported all the others in the queue. The top-level rc is one of the
284 // default locations (system, workspace, home) or the explicit path passed by
285 // --bazelrc.
286 const std::string& top_level_rc = loaded_rcs.front();
287
288 const std::set<std::string> unique_loaded_rcs(loaded_rcs.begin(),
289 loaded_rcs.end());
290 // First check if each of the newly loaded rc files was already read.
291 for (const std::string& loaded_rc : unique_loaded_rcs) {
292 if (read_files.count(loaded_rc) > 0) {
293 if (loaded_rc == top_level_rc) {
294 BAZEL_LOG(WARNING)
295 << "Duplicate rc file: " << loaded_rc
296 << " is read multiple times, it is a standard rc file location "
jingwen68c57f02018-11-21 16:17:17 -0800297 "but must have been unnecessarily imported earlier.";
ccalvarin8ceaa652018-08-24 12:44:31 -0700298 } else {
299 BAZEL_LOG(WARNING)
300 << "Duplicate rc file: " << loaded_rc
301 << " is read multiple times, most recently imported from "
302 << top_level_rc;
303 }
304 }
305 // Now check if the top-level rc file loads up its own duplicates (it can't
306 // be a cycle, since that would be an error and we would have already
307 // exited, but it could have a diamond dependency of some sort.)
308 if (std::count(loaded_rcs.begin(), loaded_rcs.end(), loaded_rc) > 1) {
309 BAZEL_LOG(WARNING) << "Duplicate rc file: " << loaded_rc
310 << " is imported multiple times from " << top_level_rc;
311 }
312 }
313}
314
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800315std::vector<std::string> GetLostFiles(
316 const std::set<std::string>& old_files,
317 const std::set<std::string>& read_files_canon) {
318 std::vector<std::string> result;
319 for (const auto& old : old_files) {
320 std::string old_canon = blaze_util::MakeCanonical(old.c_str());
321 if (!old_canon.empty() &&
322 read_files_canon.find(old_canon) == read_files_canon.end()) {
323 result.push_back(old);
324 }
325 }
326 return result;
327}
328
lpinob379f652017-04-07 12:05:09 +0000329} // namespace internal
330
ccalvarind8dfd782018-04-19 08:47:28 -0700331// TODO(#4502) Consider simplifying result_rc_files to a vector of RcFiles, no
332// unique_ptrs.
333blaze_exit_code::ExitCode OptionProcessor::GetRcFiles(
334 const WorkspaceLayout* workspace_layout, const std::string& workspace,
335 const std::string& cwd, const CommandLine* cmd_line,
336 std::vector<std::unique_ptr<RcFile>>* result_rc_files,
337 std::string* error) const {
338 assert(cmd_line != nullptr);
339 assert(result_rc_files != nullptr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100340
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700341 std::vector<std::string> rc_files;
342
343 // Get the system rc (unless --nosystem_rc).
344 if (SearchNullaryOption(cmd_line->startup_args, "system_rc", true)) {
Andrew Suffield49107ad2019-03-08 09:57:38 -0800345 // MakeAbsoluteAndResolveEnvvars will standardize the form of the
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700346 // provided path. This also means we accept relative paths, which is
347 // is convenient for testing.
348 const std::string system_rc =
Andrew Suffield49107ad2019-03-08 09:57:38 -0800349 blaze_util::MakeAbsoluteAndResolveEnvvars(system_bazelrc_path_);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700350 rc_files.push_back(system_rc);
ccalvarind8dfd782018-04-19 08:47:28 -0700351 }
352
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700353 // Get the workspace rc: %workspace%/.bazelrc (unless --noworkspace_rc), but
354 // only if we are in a workspace: invoking commands like "help" from outside
355 // a workspace should work.
356 if (!workspace.empty() &&
357 SearchNullaryOption(cmd_line->startup_args, "workspace_rc", true)) {
358 const std::string workspaceRcFile =
359 blaze_util::JoinPath(workspace, kRcBasename);
360 rc_files.push_back(workspaceRcFile);
ccalvarind8dfd782018-04-19 08:47:28 -0700361 }
362
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700363 // Get the user rc: $HOME/.bazelrc (unless --nohome_rc)
364 if (SearchNullaryOption(cmd_line->startup_args, "home_rc", true)) {
365 const std::string home = blaze::GetHomeDir();
philwob8d57082018-11-22 07:33:53 -0800366 if (!home.empty()) {
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700367 rc_files.push_back(blaze_util::JoinPath(home, kRcBasename));
ccalvarind8dfd782018-04-19 08:47:28 -0700368 }
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700369 }
370
371 // Get the command-line provided rc, passed as --bazelrc or nothing if the
372 // flag is absent.
373 const char* cmd_line_rc_file =
laszlocsomor392f24a2019-08-07 05:56:23 -0700374 SearchUnaryOption(cmd_line->startup_args, "--bazelrc",
375 /* warn_if_dupe */ true);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700376 if (cmd_line_rc_file != nullptr) {
377 string absolute_cmd_line_rc = blaze::AbsolutePathFromFlag(cmd_line_rc_file);
378 // Unlike the previous 3 paths, where we ignore it if the file does not
379 // exist or is unreadable, since this path is explicitly passed, this is an
380 // error. Check this condition here.
381 if (!blaze_util::CanReadFile(absolute_cmd_line_rc)) {
382 BAZEL_LOG(ERROR) << "Error: Unable to read .bazelrc file '"
383 << absolute_cmd_line_rc << "'.";
384 return blaze_exit_code::BAD_ARGV;
385 }
386 rc_files.push_back(absolute_cmd_line_rc);
387 }
388
389 // Log which files we're looking for before removing duplicates and
390 // non-existent files, so that this can serve to debug why a certain file is
391 // not being read. The final files which are read will be logged as they are
392 // parsed, and can be found using --announce_rc.
393 std::string joined_rcs;
394 blaze_util::JoinStrings(rc_files, ',', &joined_rcs);
395 BAZEL_LOG(INFO) << "Looking for the following rc files: " << joined_rcs;
396
397 // It's possible that workspace == home, that files are symlinks for each
398 // other, or that the --bazelrc flag is a duplicate. Dedupe them to minimize
399 // the likelihood of repeated options. Since bazelrcs can include one another,
ccalvarin8ceaa652018-08-24 12:44:31 -0700400 // this isn't sufficient to prevent duplicate options, so we also warn if we
401 // discover duplicate loads later. This also has the effect of removing paths
402 // that don't point to real files.
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700403 rc_files = internal::DedupeBlazercPaths(rc_files);
404
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800405 std::set<std::string> read_files_canonical_paths;
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700406 // Parse these potential files, in priority order;
ccalvarin8ceaa652018-08-24 12:44:31 -0700407 for (const std::string& top_level_bazelrc_path : rc_files) {
ccalvarind8dfd782018-04-19 08:47:28 -0700408 std::unique_ptr<RcFile> parsed_rc;
409 blaze_exit_code::ExitCode parse_rcfile_exit_code = ParseRcFile(
ccalvarin8ceaa652018-08-24 12:44:31 -0700410 workspace_layout, workspace, top_level_bazelrc_path, &parsed_rc, error);
ccalvarind8dfd782018-04-19 08:47:28 -0700411 if (parse_rcfile_exit_code != blaze_exit_code::SUCCESS) {
412 return parse_rcfile_exit_code;
413 }
ccalvarin8ceaa652018-08-24 12:44:31 -0700414
415 // Check that none of the rc files loaded this time are duplicate.
Googler1980fdb2020-09-01 13:49:34 -0700416 const auto& sources = parsed_rc->canonical_source_paths();
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800417 internal::WarnAboutDuplicateRcFiles(read_files_canonical_paths, sources);
418 read_files_canonical_paths.insert(sources.begin(), sources.end());
ccalvarin8ceaa652018-08-24 12:44:31 -0700419
ccalvarind8dfd782018-04-19 08:47:28 -0700420 result_rc_files->push_back(std::move(parsed_rc));
421 }
422
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700423 // Provide a warning for any old file that might have been missed with the new
424 // expectations. This compares "canonical" paths to one another, so should not
425 // require additional transformation.
426 // TODO(b/36168162): Remove this warning along with
427 // internal::GetOldRcPaths and internal::FindLegacyUserBazelrc after
428 // the transition period has passed.
Yannic Bonenberger45d52002018-09-14 16:11:36 -0700429 const std::set<std::string> old_files = internal::GetOldRcPaths(
430 workspace_layout, workspace, cwd, cmd_line->path_to_binary,
431 cmd_line->startup_args, internal::FindSystemWideRc(system_bazelrc_path_));
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700432
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800433 std::vector<std::string> lost_files =
434 internal::GetLostFiles(old_files, read_files_canonical_paths);
ccalvarin5e5ee0d2018-08-23 08:56:01 -0700435 if (!lost_files.empty()) {
436 std::string joined_lost_rcs;
437 blaze_util::JoinStrings(lost_files, '\n', &joined_lost_rcs);
438 BAZEL_LOG(WARNING)
439 << "The following rc files are no longer being read, please transfer "
440 "their contents or import their path into one of the standard rc "
441 "files:\n"
442 << joined_lost_rcs;
443 }
444
ccalvarind8dfd782018-04-19 08:47:28 -0700445 return blaze_exit_code::SUCCESS;
446}
447
448blaze_exit_code::ExitCode ParseRcFile(const WorkspaceLayout* workspace_layout,
449 const std::string& workspace,
450 const std::string& rc_file_path,
451 std::unique_ptr<RcFile>* result_rc_file,
452 std::string* error) {
453 assert(!rc_file_path.empty());
454 assert(result_rc_file != nullptr);
455
456 RcFile::ParseError parse_error;
457 std::unique_ptr<RcFile> parsed_file = RcFile::Parse(
458 rc_file_path, workspace_layout, workspace, &parse_error, error);
459 if (parsed_file == nullptr) {
460 return internal::ParseErrorToExitCode(parse_error);
461 }
462 *result_rc_file = std::move(parsed_file);
463 return blaze_exit_code::SUCCESS;
464}
465
466blaze_exit_code::ExitCode OptionProcessor::ParseOptions(
467 const vector<string>& args, const string& workspace, const string& cwd,
468 string* error) {
michajlo319cd4f2019-05-20 11:17:37 -0700469 assert(!parse_options_called_);
michajloeaf066b2019-05-15 14:12:57 -0700470 parse_options_called_ = true;
lpinoc00ba522017-07-10 19:17:40 +0200471 cmd_line_ = SplitCommandLine(args, error);
472 if (cmd_line_ == nullptr) {
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000473 return blaze_exit_code::BAD_ARGV;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100474 }
ccalvarind8dfd782018-04-19 08:47:28 -0700475
ccalvarin90e116c2018-05-15 16:41:19 -0700476 // Read the rc files, unless --ignore_all_rc_files was provided on the command
477 // line. This depends on the startup options in argv since these may contain
478 // other rc-modifying options. For all other options, the precedence of
ccalvarind8dfd782018-04-19 08:47:28 -0700479 // options will be rc first, then command line options, though, despite this
480 // exception.
jmmv64553792018-04-19 12:32:59 -0700481 std::vector<std::unique_ptr<RcFile>> rc_files;
ccalvarin90e116c2018-05-15 16:41:19 -0700482 if (!SearchNullaryOption(cmd_line_->startup_args, "ignore_all_rc_files",
483 false)) {
484 const blaze_exit_code::ExitCode rc_parsing_exit_code = GetRcFiles(
485 workspace_layout_, workspace, cwd, cmd_line_.get(), &rc_files, error);
486 if (rc_parsing_exit_code != blaze_exit_code::SUCCESS) {
487 return rc_parsing_exit_code;
488 }
Luis Fernando Pino Duqued5e008c2016-12-08 16:59:16 +0000489 }
490
Googler94833922020-09-02 12:09:40 -0700491 // The helpers expect regular pointers, not unique_ptrs.
492 std::vector<RcFile*> rc_file_ptrs;
493 rc_file_ptrs.reserve(rc_files.size());
494 for (auto& rc_file : rc_files) { rc_file_ptrs.push_back(rc_file.get()); }
495
ccalvarind8dfd782018-04-19 08:47:28 -0700496 // Parse the startup options in the correct priority order.
497 const blaze_exit_code::ExitCode parse_startup_options_exit_code =
Googler94833922020-09-02 12:09:40 -0700498 ParseStartupOptions(rc_file_ptrs, error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100499 if (parse_startup_options_exit_code != blaze_exit_code::SUCCESS) {
500 return parse_startup_options_exit_code;
501 }
502
Googlerebd28a92018-02-07 08:46:31 -0800503 blazerc_and_env_command_args_ =
Googler94833922020-09-02 12:09:40 -0700504 GetBlazercAndEnvCommandArgs(cwd, rc_file_ptrs, GetProcessedEnv());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100505 return blaze_exit_code::SUCCESS;
506}
507
ccalvarin1cbe62a2017-08-14 21:09:07 +0200508static void PrintStartupOptions(const std::string& source,
509 const std::vector<std::string>& options) {
510 if (!source.empty()) {
511 std::string startup_args;
512 blaze_util::JoinStrings(options, ' ', &startup_args);
513 fprintf(stderr, "INFO: Reading 'startup' options from %s: %s\n",
514 source.c_str(), startup_args.c_str());
515 }
516}
517
518void OptionProcessor::PrintStartupOptionsProvenanceMessage() const {
519 StartupOptions* parsed_startup_options = GetParsedStartupOptions();
520
521 // Print the startup flags in the order they are parsed, to keep the
jingwen68c57f02018-11-21 16:17:17 -0800522 // precedence clear. In order to minimize the number of lines of output in
ccalvarin1cbe62a2017-08-14 21:09:07 +0200523 // the terminal, group sequential flags by origin. Note that an rc file may
524 // turn up multiple times in this list, if, for example, it imports another
525 // rc file and contains startup options on either side of the import
526 // statement. This is done intentionally to make option priority clear.
527 std::string command_line_source;
528 std::string& most_recent_blazerc = command_line_source;
529 std::vector<std::string> accumulated_options;
Googler80eb1932018-08-05 22:21:34 -0700530 for (const auto& flag : parsed_startup_options->original_startup_options_) {
ccalvarin1cbe62a2017-08-14 21:09:07 +0200531 if (flag.source == most_recent_blazerc) {
532 accumulated_options.push_back(flag.value);
533 } else {
534 PrintStartupOptions(most_recent_blazerc, accumulated_options);
535 // Start accumulating again.
536 accumulated_options.clear();
537 accumulated_options.push_back(flag.value);
538 most_recent_blazerc = flag.source;
539 }
540 }
541 // Don't forget to print out the last ones.
542 PrintStartupOptions(most_recent_blazerc, accumulated_options);
543}
544
lpinoc00ba522017-07-10 19:17:40 +0200545blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(
Googler94833922020-09-02 12:09:40 -0700546 const std::vector<RcFile*> &rc_files, std::string *error) {
ccalvarin1cbe62a2017-08-14 21:09:07 +0200547 // Rc files can import other files at any point, and these imported rcs are
jmmv64553792018-04-19 12:32:59 -0700548 // expanded in place. Here, we isolate just the startup options but keep the
549 // file they came from attached for the option_sources tracking and for
550 // sending to the server.
lpinoc00ba522017-07-10 19:17:40 +0200551 std::vector<RcStartupFlag> rcstartup_flags;
552
Googler94833922020-09-02 12:09:40 -0700553 for (const auto* blazerc : rc_files) {
Googlerebd28a92018-02-07 08:46:31 -0800554 const auto iter = blazerc->options().find("startup");
555 if (iter == blazerc->options().end()) continue;
556
557 for (const RcOption& option : iter->second) {
Googler1980fdb2020-09-01 13:49:34 -0700558 const std::string& source_path =
559 blazerc->canonical_source_paths()[option.source_index];
560 rcstartup_flags.push_back({source_path, option.option});
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100561 }
562 }
563
lpinoc00ba522017-07-10 19:17:40 +0200564 for (const std::string& arg : cmd_line_->startup_args) {
565 if (!IsArg(arg)) {
566 break;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100567 }
lpinoc00ba522017-07-10 19:17:40 +0200568 rcstartup_flags.push_back(RcStartupFlag("", arg));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100569 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100570
michajloeaf066b2019-05-15 14:12:57 -0700571 return startup_options_->ProcessArgs(rcstartup_flags, error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100572}
573
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200574static bool IsValidEnvName(const char* p) {
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700575#if defined(_WIN32) || defined(__CYGWIN__)
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200576 for (; *p && *p != '='; ++p) {
577 if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
Nikolay Shelukhina9b82972020-01-28 07:55:18 -0800578 (*p >= '0' && *p <= '9') || *p == '_' || *p == '(' || *p == ')')) {
Laszlo Csomor0a936d12017-05-31 09:47:43 +0200579 return false;
580 }
581 }
582#endif
583 return true;
584}
585
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700586#if defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000587static void PreprocessEnvString(string* env_str) {
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800588 static constexpr const char* vars_to_uppercase[] = {"PATH", "SYSTEMROOT",
pcloudy8c21b0b2018-09-19 07:19:09 -0700589 "SYSTEMDRIVE",
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800590 "TEMP", "TEMPDIR", "TMP"};
Dmitry Lomov49501112017-03-07 22:55:42 +0000591
592 int pos = env_str->find_first_of('=');
593 if (pos == string::npos) return;
594
595 string name = env_str->substr(0, pos);
596 // We do not care about locale. All variable names are ASCII.
597 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
Loo Rong Jie2b9ab772018-03-01 02:35:23 -0800598 if (std::find(std::begin(vars_to_uppercase), std::end(vars_to_uppercase),
599 name) != std::end(vars_to_uppercase)) {
Dmitry Lomov49501112017-03-07 22:55:42 +0000600 env_str->assign(name + "=" + env_str->substr(pos + 1));
601 }
602}
603
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700604#elif defined(__CYGWIN__) // not defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000605
606static void PreprocessEnvString(string* env_str) {
607 int pos = env_str->find_first_of('=');
608 if (pos == string::npos) return;
609 string name = env_str->substr(0, pos);
610 if (name == "PATH") {
Loo Rong Jief049cde2018-01-26 06:29:32 -0800611 env_str->assign("PATH=" + env_str->substr(pos + 1));
Dmitry Lomov49501112017-03-07 22:55:42 +0000612 } else if (name == "TMP") {
613 // A valid Windows path "c:/foo" is also a valid Unix path list of
laszlocsomorf7f1a8f2017-08-03 17:21:44 +0200614 // ["c", "/foo"] so must use ConvertPath here. See GitHub issue #1684.
ccalvarinac69da02018-06-05 15:27:26 -0700615 env_str->assign("TMP=" + blaze_util::ConvertPath(env_str->substr(pos + 1)));
Dmitry Lomov49501112017-03-07 22:55:42 +0000616 }
617}
618
619#else // Non-Windows platforms.
620
621static void PreprocessEnvString(const string* env_str) {
622 // do nothing.
623}
Loo Rong Jie4022bac2018-06-11 02:04:52 -0700624#endif // defined(_WIN32)
Dmitry Lomov49501112017-03-07 22:55:42 +0000625
Googlerebd28a92018-02-07 08:46:31 -0800626static std::vector<std::string> GetProcessedEnv() {
627 std::vector<std::string> processed_env;
628 for (char** env = environ; *env != NULL; env++) {
629 string env_str(*env);
630 if (IsValidEnvName(*env)) {
631 PreprocessEnvString(&env_str);
632 processed_env.push_back(std::move(env_str));
633 }
634 }
635 return processed_env;
636}
637
ccalvarin35ce88d2017-12-11 10:57:03 -0800638// IMPORTANT: The options added here do not come from the user. In order for
639// their source to be correctly tracked, the options must either be passed
640// as --default_override=0, 0 being "client", or must be listed in
641// BlazeOptionHandler.INTERNAL_COMMAND_OPTIONS!
lpinoc00ba522017-07-10 19:17:40 +0200642std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs(
Googlerebd28a92018-02-07 08:46:31 -0800643 const std::string& cwd,
Googler94833922020-09-02 12:09:40 -0700644 const std::vector<RcFile*>& blazercs,
Googlerebd28a92018-02-07 08:46:31 -0800645 const std::vector<std::string>& env) {
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000646 // Provide terminal options as coming from the least important rc file.
lpinoc00ba522017-07-10 19:17:40 +0200647 std::vector<std::string> result = {
ccalvarin35ce88d2017-12-11 10:57:03 -0800648 "--rc_source=client",
Googlerd22eddc2018-09-11 15:03:17 -0700649 "--default_override=0:common=--isatty=" +
michajlo479c9042020-03-27 11:43:39 -0700650 blaze_util::ToString(IsStandardTerminal()),
ccalvarin35ce88d2017-12-11 10:57:03 -0800651 "--default_override=0:common=--terminal_columns=" +
michajlo479c9042020-03-27 11:43:39 -0700652 blaze_util::ToString(GetTerminalColumns())};
ccalvarin35ce88d2017-12-11 10:57:03 -0800653 if (IsEmacsTerminal()) {
654 result.push_back("--default_override=0:common=--emacs");
655 }
Klaus Aehlig3c9a2262016-04-20 15:13:51 +0000656
Yun Peng213a52a2017-09-14 11:11:48 +0200657 EnsurePythonPathOption(&result);
658
Googlerebd28a92018-02-07 08:46:31 -0800659 // Map .blazerc numbers to filenames. The indexes here start at 1 because #0
660 // is reserved the "client" options created by this function.
661 int cur_index = 1;
662 std::map<std::string, int> rcfile_indexes;
Googler94833922020-09-02 12:09:40 -0700663 for (const auto* blazerc : blazercs) {
Laszlo Csomor9b83bd72018-12-17 08:42:44 -0800664 for (const std::string& source_path : blazerc->canonical_source_paths()) {
Googlerebd28a92018-02-07 08:46:31 -0800665 // Deduplicate the rc_source list because the same file might be included
666 // from multiple places.
667 if (rcfile_indexes.find(source_path) != rcfile_indexes.end()) continue;
668
ccalvarinac69da02018-06-05 15:27:26 -0700669 result.push_back("--rc_source=" + blaze_util::ConvertPath(source_path));
Googlerebd28a92018-02-07 08:46:31 -0800670 rcfile_indexes[source_path] = cur_index;
671 cur_index++;
672 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100673 }
674
Googlerebd28a92018-02-07 08:46:31 -0800675 // Add RcOptions as default_overrides.
Googler94833922020-09-02 12:09:40 -0700676 for (const auto* blazerc : blazercs) {
Googlerebd28a92018-02-07 08:46:31 -0800677 for (const auto& command_options : blazerc->options()) {
678 const string& command = command_options.first;
679 // Skip startup flags, which are already parsed by the client.
680 if (command == "startup") continue;
681
682 for (const RcOption& rcoption : command_options.second) {
Googler1980fdb2020-09-01 13:49:34 -0700683 const std::string& source_path =
684 blazerc->canonical_source_paths()[rcoption.source_index];
Googlerebd28a92018-02-07 08:46:31 -0800685 std::ostringstream oss;
Googler1980fdb2020-09-01 13:49:34 -0700686 oss << "--default_override=" << rcfile_indexes[source_path] << ':'
687 << command << '=' << rcoption.option;
Googlerebd28a92018-02-07 08:46:31 -0800688 result.push_back(oss.str());
lpinoc00ba522017-07-10 19:17:40 +0200689 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100690 }
691 }
692
Dmitry Lomov31fab292017-03-07 18:33:08 +0000693 // Pass the client environment to the server.
Googlerebd28a92018-02-07 08:46:31 -0800694 for (const string& env_var : env) {
Googlerebd28a92018-02-07 08:46:31 -0800695 result.push_back("--client_env=" + env_var);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100696 }
ccalvarinac69da02018-06-05 15:27:26 -0700697 result.push_back("--client_cwd=" + blaze_util::ConvertPath(cwd));
lpinoc00ba522017-07-10 19:17:40 +0200698 return result;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100699}
700
lpinoc00ba522017-07-10 19:17:40 +0200701std::vector<std::string> OptionProcessor::GetCommandArguments() const {
702 assert(cmd_line_ != nullptr);
703 // When the user didn't specify a command, the server expects the command
704 // arguments to be empty in order to display the help message.
705 if (cmd_line_->command.empty()) {
706 return {};
707 }
708
709 std::vector<std::string> command_args = blazerc_and_env_command_args_;
710 command_args.insert(command_args.end(),
711 cmd_line_->command_args.begin(),
712 cmd_line_->command_args.end());
713 return command_args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100714}
715
lpino233b72d2017-07-05 11:08:40 -0400716std::vector<std::string> OptionProcessor::GetExplicitCommandArguments() const {
lpinoc00ba522017-07-10 19:17:40 +0200717 assert(cmd_line_ != nullptr);
718 return cmd_line_->command_args;
lpino233b72d2017-07-05 11:08:40 -0400719}
720
lpinoc00ba522017-07-10 19:17:40 +0200721std::string OptionProcessor::GetCommand() const {
722 assert(cmd_line_ != nullptr);
723 return cmd_line_->command;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100724}
725
Julio Merino28774852016-09-14 16:59:46 +0000726StartupOptions* OptionProcessor::GetParsedStartupOptions() const {
michajloeaf066b2019-05-15 14:12:57 -0700727 assert(parse_options_called_);
728 return startup_options_.get();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100729}
Han-Wen Nienhuys255cf092015-05-22 14:38:59 +0000730
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100731} // namespace blaze