| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/main/cpp/startup_options.h" | |
| #include <assert.h> | |
| #include <cctype> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <map> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/main/cpp/blaze_util.h" | |
| #include "src/main/cpp/blaze_util_platform.h" | |
| #include "src/main/cpp/jvm_module_options.h" | |
| #include "src/main/cpp/util/errors.h" | |
| #include "src/main/cpp/util/exit_code.h" | |
| #include "src/main/cpp/util/file.h" | |
| #include "src/main/cpp/util/file_platform.h" | |
| #include "src/main/cpp/util/logging.h" | |
| #include "src/main/cpp/util/numbers.h" | |
| #include "src/main/cpp/util/path_platform.h" | |
| #include "src/main/cpp/util/strings.h" | |
| #include "absl/time/time.h" | |
| namespace blaze { | |
| using std::string; | |
| using std::vector; | |
| void StartupOptions::RegisterNullaryStartupFlag(const std::string& flag_name, | |
| bool* flag_value) { | |
| all_nullary_startup_flags_[std::string("--") + flag_name] = flag_value; | |
| all_nullary_startup_flags_[std::string("--no") + flag_name] = flag_value; | |
| } | |
| void StartupOptions::RegisterNullaryStartupFlagNoRc( | |
| const std::string& flag_name, bool* flag_value) { | |
| RegisterNullaryStartupFlag(flag_name, flag_value); | |
| no_rc_nullary_startup_flags_.insert(std::string("--") + flag_name); | |
| no_rc_nullary_startup_flags_.insert(std::string("--no") + flag_name); | |
| } | |
| void StartupOptions::RegisterSpecialNullaryStartupFlag( | |
| const std::string& flag_name, SpecialNullaryFlagHandler handler) { | |
| RegisterNullaryStartupFlag(flag_name, nullptr); | |
| special_nullary_startup_flags_[std::string("--") + flag_name] = handler; | |
| special_nullary_startup_flags_[std::string("--no") + flag_name] = handler; | |
| } | |
| void StartupOptions::RegisterUnaryStartupFlag(const std::string& flag_name) { | |
| valid_unary_startup_flags_.insert(std::string("--") + flag_name); | |
| } | |
| void StartupOptions::OverrideOptionSourcesKey(const std::string& flag_name, | |
| const std::string& new_name) { | |
| option_sources_key_override_[flag_name] = new_name; | |
| } | |
| StartupOptions::StartupOptions(const string& product_name, | |
| bool lock_install_base) | |
| : product_name(product_name), | |
| lock_install_base(lock_install_base), | |
| ignore_all_rc_files(false), | |
| block_for_lock_timeout(absl::InfiniteDuration()), | |
| host_jvm_debug(false), | |
| autodetect_server_javabase(true), | |
| batch(false), | |
| batch_cpu_scheduling(false), | |
| io_nice_level(-1), | |
| shutdown_on_low_sys_mem(false), | |
| oom_more_eagerly(false), | |
| oom_more_eagerly_threshold(100), | |
| write_command_log(true), | |
| fatal_event_bus_exceptions(false), | |
| command_port(0), | |
| connect_timeout_secs(30), | |
| local_startup_timeout_secs(120), | |
| have_invocation_policy_(false), | |
| quiet(false), | |
| client_debug(false), | |
| preemptible(false), | |
| java_logging_formatter( | |
| "com.google.devtools.build.lib.util.SingleLineFormatter"), | |
| digest_function(), | |
| idle_server_tasks(true), | |
| original_startup_options_(std::vector<RcStartupFlag>()), | |
| #if defined(__APPLE__) | |
| macos_qos_class(QOS_CLASS_UNSPECIFIED), | |
| #endif | |
| unlimit_coredumps(false), | |
| #ifdef __linux__ | |
| cgroup_parent(), | |
| run_in_user_cgroup(false), | |
| #endif | |
| windows_enable_symlinks(false), | |
| remote_repo_contents_cache(false), | |
| use_compact_object_headers_(false) { | |
| #if defined(_WIN32) || defined(__CYGWIN__) | |
| string windows_unix_root = DetectBashAndExportBazelSh(); | |
| if (!windows_unix_root.empty()) { | |
| host_jvm_args.push_back(string("-Dbazel.windows_unix_root=") + | |
| windows_unix_root); | |
| } | |
| #endif // defined(_WIN32) || defined(__CYGWIN__) | |
| // Use a smaller default idle timer when in a test. | |
| max_idle_secs = blaze::IsRunningWithinTest() ? 15 : 3 * 3600; | |
| // IMPORTANT: Before modifying the statements below please contact a Bazel | |
| // core team member that knows the internal procedure for adding/deprecating | |
| // startup flags. | |
| RegisterNullaryStartupFlag("batch", &batch); | |
| RegisterNullaryStartupFlag("batch_cpu_scheduling", &batch_cpu_scheduling); | |
| RegisterSpecialNullaryStartupFlag("block_for_lock", [this](bool enabled) { | |
| this->block_for_lock_timeout = | |
| enabled ? absl::InfiniteDuration() : absl::ZeroDuration(); | |
| }); | |
| RegisterUnaryStartupFlag("block_for_lock"); | |
| RegisterNullaryStartupFlag("quiet", &quiet); | |
| RegisterNullaryStartupFlag("client_debug", &client_debug); | |
| RegisterNullaryStartupFlag("preemptible", &preemptible); | |
| RegisterNullaryStartupFlag("fatal_event_bus_exceptions", | |
| &fatal_event_bus_exceptions); | |
| RegisterNullaryStartupFlag("host_jvm_debug", &host_jvm_debug); | |
| RegisterNullaryStartupFlag("autodetect_server_javabase", | |
| &autodetect_server_javabase); | |
| RegisterNullaryStartupFlag("idle_server_tasks", &idle_server_tasks); | |
| RegisterNullaryStartupFlag("shutdown_on_low_sys_mem", | |
| &shutdown_on_low_sys_mem); | |
| RegisterNullaryStartupFlagNoRc("ignore_all_rc_files", &ignore_all_rc_files); | |
| RegisterNullaryStartupFlag("unlimit_coredumps", &unlimit_coredumps); | |
| RegisterNullaryStartupFlag("write_command_log", &write_command_log); | |
| RegisterNullaryStartupFlag("windows_enable_symlinks", | |
| &windows_enable_symlinks); | |
| RegisterNullaryStartupFlag("experimental_remote_repo_contents_cache", | |
| &remote_repo_contents_cache); | |
| RegisterNullaryStartupFlag("experimental_use_compact_object_headers", | |
| &use_compact_object_headers_); | |
| #ifdef __linux__ | |
| RegisterNullaryStartupFlag("experimental_run_in_user_cgroup", | |
| &run_in_user_cgroup); | |
| #endif | |
| RegisterUnaryStartupFlag("command_port"); | |
| RegisterUnaryStartupFlag("connect_timeout_secs"); | |
| RegisterUnaryStartupFlag("local_startup_timeout_secs"); | |
| RegisterUnaryStartupFlag("digest_function"); | |
| RegisterUnaryStartupFlag("unix_digest_hash_attribute_name"); | |
| RegisterUnaryStartupFlag("server_javabase"); | |
| RegisterUnaryStartupFlag("host_jvm_args"); | |
| RegisterUnaryStartupFlag("host_jvm_profile"); | |
| RegisterUnaryStartupFlag("invocation_policy"); | |
| RegisterUnaryStartupFlag("io_nice_level"); | |
| RegisterUnaryStartupFlag("install_base"); | |
| RegisterUnaryStartupFlag("macos_qos_class"); | |
| RegisterUnaryStartupFlag("max_idle_secs"); | |
| RegisterUnaryStartupFlag("output_base"); | |
| RegisterUnaryStartupFlag("output_user_root"); | |
| RegisterUnaryStartupFlag("server_jvm_out"); | |
| RegisterUnaryStartupFlag("failure_detail_out"); | |
| RegisterUnaryStartupFlag("experimental_cgroup_parent"); | |
| RegisterUnaryStartupFlag("extra_classpath"); | |
| } | |
| StartupOptions::~StartupOptions() {} | |
| string StartupOptions::GetLowercaseProductName() const { | |
| return blaze_util::ToLower(product_name); | |
| } | |
| bool StartupOptions::IsUnary(const string& arg) const { | |
| std::string::size_type i = arg.find_first_of('='); | |
| if (i == std::string::npos) { | |
| if (arg == "--block_for_lock") { | |
| return false; | |
| } | |
| return valid_unary_startup_flags_.find(arg) != | |
| valid_unary_startup_flags_.end(); | |
| } else { | |
| return valid_unary_startup_flags_.find(arg.substr(0, i)) != | |
| valid_unary_startup_flags_.end(); | |
| } | |
| } | |
| bool StartupOptions::MaybeCheckValidNullary(const string& arg, bool* result, | |
| std::string* error) const { | |
| std::string::size_type i = arg.find_first_of('='); | |
| if (i == std::string::npos) { | |
| *result = all_nullary_startup_flags_.find(arg) != | |
| all_nullary_startup_flags_.end(); | |
| return true; | |
| } | |
| std::string f = arg.substr(0, i); | |
| if (f == "--block_for_lock") { | |
| *result = false; | |
| return true; | |
| } | |
| if (all_nullary_startup_flags_.find(f) == all_nullary_startup_flags_.end()) { | |
| *result = false; | |
| return true; | |
| } | |
| blaze_util::StringPrintf( | |
| error, "In argument '%s': option '%s' does not take a value.", | |
| arg.c_str(), f.c_str()); | |
| return false; | |
| } | |
| static bool ParseBlockForLock(const string& value, | |
| absl::Duration* block_for_lock_timeout, | |
| string* error) { | |
| if (value == "true") { | |
| *block_for_lock_timeout = absl::InfiniteDuration(); | |
| return true; | |
| } | |
| if (value == "false") { | |
| *block_for_lock_timeout = absl::ZeroDuration(); | |
| return true; | |
| } | |
| absl::Duration duration; | |
| if (value.empty() || isdigit(value.back()) || | |
| !absl::ParseDuration(value, &duration) || | |
| duration < absl::ZeroDuration() || | |
| (duration > absl::ZeroDuration() && duration < absl::Milliseconds(1)) || | |
| duration == absl::InfiniteDuration()) { | |
| blaze_util::StringPrintf( | |
| error, | |
| "Invalid argument to --block_for_lock: '%s'. " | |
| "Expected boolean or duration (e.g. 'true', 'false', '30s', '1m').", | |
| value.c_str()); | |
| return false; | |
| } | |
| *block_for_lock_timeout = | |
| absl::Milliseconds(absl::ToInt64Milliseconds(duration)); | |
| return true; | |
| } | |
| void StartupOptions::AddExtraOptions(vector<string>* result) const {} | |
| blaze_exit_code::ExitCode StartupOptions::ProcessArg(const string& argstr, | |
| const string& next_argstr, | |
| const string& rcfile, | |
| bool* is_space_separated, | |
| string* error) { | |
| // We have to parse a specific option syntax, so GNU getopts won't do. All | |
| // options begin with "--" or "-". Values are given together with the option | |
| // delimited by '=' or in the next option. | |
| const char* arg = argstr.c_str(); | |
| const char* next_arg = next_argstr.empty() ? nullptr : next_argstr.c_str(); | |
| const char* value = nullptr; | |
| bool is_nullary; | |
| if (!MaybeCheckValidNullary(argstr, &is_nullary, error)) { | |
| *is_space_separated = false; | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| if (is_nullary) { | |
| // 'enabled' is true if 'argstr' is "--foo", and false if it's "--nofoo". | |
| bool enabled = (argstr.compare(0, 4, "--no") != 0); | |
| if (no_rc_nullary_startup_flags_.find(argstr) != | |
| no_rc_nullary_startup_flags_.end()) { | |
| // no_rc_nullary_startup_flags_ are forbidden in .bazelrc files. | |
| if (!rcfile.empty()) { | |
| *error = std::string("Can't specify ") + argstr + " in the " + | |
| GetRcFileBaseName() + " file."; | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| } | |
| if (special_nullary_startup_flags_.find(argstr) != | |
| special_nullary_startup_flags_.end()) { | |
| // 'argstr' is either "--foo" or "--nofoo", and the map entry is the | |
| // lambda that handles setting the flag's value. | |
| special_nullary_startup_flags_[argstr](enabled); | |
| } else { | |
| // 'argstr' is either "--foo" or "--nofoo", and the map entry is the | |
| // pointer to the bool storing the flag's value. | |
| *all_nullary_startup_flags_[argstr] = enabled; | |
| } | |
| // Use the key "foo" for 'argstr' of "--foo" / "--nofoo", unless there's an | |
| // overridden name we must use. | |
| std::string key = argstr.substr(enabled ? 2 : 4); | |
| if (option_sources_key_override_.find(key) != | |
| option_sources_key_override_.end()) { | |
| key = option_sources_key_override_[key]; | |
| } | |
| option_sources[key] = rcfile; | |
| *is_space_separated = false; | |
| return blaze_exit_code::SUCCESS; | |
| } | |
| if ((value = blaze_util::var_strprefix(arg, "--block_for_lock=")) != | |
| nullptr) { | |
| if (!ParseBlockForLock(value, &block_for_lock_timeout, error)) { | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["block_for_lock"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--output_base")) != | |
| nullptr) { | |
| output_base = blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["output_base"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--install_base")) != | |
| nullptr) { | |
| install_base = blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["install_base"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--output_user_root")) != | |
| nullptr) { | |
| output_user_root = blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["output_user_root"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--server_jvm_out")) != | |
| nullptr) { | |
| server_jvm_out = blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["server_jvm_out"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--failure_detail_out")) != | |
| nullptr) { | |
| failure_detail_out = blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["failure_detail_out"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--server_javabase")) != | |
| nullptr) { | |
| // TODO(bazel-team): Consider examining the javabase and re-execing in case | |
| // of architecture mismatch. | |
| explicit_server_javabase_ = | |
| blaze_util::Path(blaze::AbsolutePathFromFlag(value)); | |
| option_sources["server_javabase"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--host_jvm_args")) != | |
| nullptr) { | |
| host_jvm_args.push_back(value); | |
| option_sources["host_jvm_args"] = rcfile; // NB: This is incorrect | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--extra_classpath"))) { | |
| extra_classpath = value; | |
| option_sources["extra_classpath"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--io_nice_level")) != | |
| nullptr) { | |
| if (!blaze_util::safe_strto32(value, &io_nice_level) || io_nice_level > 7) { | |
| blaze_util::StringPrintf( | |
| error, | |
| "Invalid argument to --io_nice_level: '%s'. Must not exceed 7.", | |
| value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["io_nice_level"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--max_idle_secs")) != | |
| nullptr) { | |
| if (!blaze_util::safe_strto32(value, &max_idle_secs) || max_idle_secs < 0) { | |
| blaze_util::StringPrintf( | |
| error, "Invalid argument to --max_idle_secs: '%s'.", value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["max_idle_secs"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--macos_qos_class")) != | |
| nullptr) { | |
| // We parse the value of this flag on all platforms even if it is | |
| // macOS-specific to ensure that rc files mentioning it are valid. | |
| // There is also apparently "QOS_CLASS_MAINTENANCE", but this doesn't | |
| // appear to have been exposed in the public headers as of macOS 11.1. | |
| if (strcmp(value, "default") == 0) { | |
| #if defined(__APPLE__) | |
| macos_qos_class = QOS_CLASS_UNSPECIFIED; | |
| #endif | |
| } else if (strcmp(value, "utility") == 0) { | |
| #if defined(__APPLE__) | |
| macos_qos_class = QOS_CLASS_UTILITY; | |
| #endif | |
| } else if (strcmp(value, "background") == 0) { | |
| #if defined(__APPLE__) | |
| macos_qos_class = QOS_CLASS_BACKGROUND; | |
| #endif | |
| } else { | |
| blaze_util::StringPrintf( | |
| error, "Invalid argument to --macos_qos_class: '%s'.", value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["macos_qos_class"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, | |
| "--connect_timeout_secs")) != nullptr) { | |
| if (!blaze_util::safe_strto32(value, &connect_timeout_secs) || | |
| connect_timeout_secs < 1 || connect_timeout_secs > 3600) { | |
| blaze_util::StringPrintf( | |
| error, | |
| "Invalid argument to --connect_timeout_secs: '%s'.\n" | |
| "Must be an integer between 1 and 3600.\n", | |
| value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["connect_timeout_secs"] = rcfile; | |
| } else if ((value = GetUnaryOption( | |
| arg, next_arg, "--local_startup_timeout_secs")) != nullptr) { | |
| if (!blaze_util::safe_strto32(value, &local_startup_timeout_secs) || | |
| local_startup_timeout_secs < 1) { | |
| blaze_util::StringPrintf( | |
| error, | |
| "Invalid argument to --local_startup_timeout_secs: '%s'.\n" | |
| "Must be a positive integer.\n", | |
| value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["local_startup_timeout_secs"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--digest_function")) != | |
| nullptr) { | |
| digest_function = value; | |
| option_sources["digest_function"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, | |
| "--unix_digest_hash_attribute_name")) != | |
| nullptr) { | |
| unix_digest_hash_attribute_name = value; | |
| option_sources["unix_digest_hash_attribute_name"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--command_port")) != | |
| nullptr) { | |
| if (!blaze_util::safe_strto32(value, &command_port) || command_port < 0 || | |
| command_port > 65535) { | |
| blaze_util::StringPrintf(error, | |
| "Invalid argument to --command_port: '%s'.\n" | |
| "Must be a valid port number or 0.\n", | |
| value); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| option_sources["command_port"] = rcfile; | |
| } else if ((value = GetUnaryOption(arg, next_arg, "--invocation_policy")) != | |
| nullptr) { | |
| if (!have_invocation_policy_) { | |
| have_invocation_policy_ = true; | |
| invocation_policy = value; | |
| option_sources["invocation_policy"] = rcfile; | |
| } else { | |
| *error = | |
| "The startup flag --invocation_policy cannot be specified " | |
| "multiple times."; | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| } else if ((value = GetUnaryOption( | |
| arg, next_arg, "--experimental_cgroup_parent")) != nullptr) { | |
| #ifdef __linux__ | |
| cgroup_parent = value; | |
| option_sources["cgroup_parent"] = rcfile; | |
| #endif | |
| } else { | |
| bool extra_argument_processed; | |
| blaze_exit_code::ExitCode process_extra_arg_exit_code = ProcessArgExtra( | |
| arg, next_arg, rcfile, &value, &extra_argument_processed, error); | |
| if (process_extra_arg_exit_code != blaze_exit_code::SUCCESS) { | |
| return process_extra_arg_exit_code; | |
| } | |
| if (!extra_argument_processed) { | |
| blaze_util::StringPrintf( | |
| error, | |
| "Unknown startup option: '%s'.\n" | |
| " For more info, run '%s help startup_options'.", | |
| arg, GetLowercaseProductName().c_str()); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| } | |
| *is_space_separated = ((value == next_arg) && (value != nullptr)); | |
| return blaze_exit_code::SUCCESS; | |
| } | |
| blaze_exit_code::ExitCode StartupOptions::ProcessArgs( | |
| const std::vector<RcStartupFlag>& rcstartup_flags, std::string* error) { | |
| std::vector<RcStartupFlag>::size_type i = 0; | |
| while (i < rcstartup_flags.size()) { | |
| bool is_space_separated = false; | |
| const std::string next_value = | |
| (i == rcstartup_flags.size() - 1) ? "" : rcstartup_flags[i + 1].value; | |
| const blaze_exit_code::ExitCode process_arg_exit_code = | |
| ProcessArg(rcstartup_flags[i].value, next_value, | |
| rcstartup_flags[i].source, &is_space_separated, error); | |
| // Store the provided option in --flag(=value)? form. Store these before | |
| // propagating any error code, since we want to have the correct | |
| // information for the output. The fact that the options aren't parseable | |
| // doesn't matter for this step. | |
| if (is_space_separated) { | |
| const std::string combined_value = | |
| rcstartup_flags[i].value + "=" + next_value; | |
| original_startup_options_.push_back( | |
| RcStartupFlag(rcstartup_flags[i].source, combined_value)); | |
| i += 2; | |
| } else { | |
| original_startup_options_.push_back( | |
| RcStartupFlag(rcstartup_flags[i].source, rcstartup_flags[i].value)); | |
| i++; | |
| } | |
| if (process_arg_exit_code != blaze_exit_code::SUCCESS) { | |
| return process_arg_exit_code; | |
| } | |
| } | |
| return blaze_exit_code::SUCCESS; | |
| } | |
| blaze_util::Path StartupOptions::GetSystemJavabase() const { | |
| return blaze_util::Path(blaze::GetSystemJavabase()); | |
| } | |
| blaze_util::Path StartupOptions::GetEmbeddedJavabase() const { | |
| blaze_util::Path bundled_jre_path = | |
| install_base.GetRelative("embedded_tools").GetRelative("jdk"); | |
| if (blaze_util::CanExecuteFile( | |
| bundled_jre_path.GetRelative(GetJavaBinaryUnderJavabase()))) { | |
| return bundled_jre_path; | |
| } | |
| return blaze_util::Path(); | |
| } | |
| std::pair<blaze_util::Path, StartupOptions::JavabaseType> | |
| StartupOptions::GetServerJavabaseAndType() const { | |
| // 1) Allow overriding the server_javabase via --server_javabase. | |
| if (!explicit_server_javabase_.IsEmpty()) { | |
| return std::pair<blaze_util::Path, JavabaseType>(explicit_server_javabase_, | |
| JavabaseType::EXPLICIT); | |
| } | |
| if (default_server_javabase_.first.IsEmpty()) { | |
| blaze_util::Path bundled_jre_path = GetEmbeddedJavabase(); | |
| if (!bundled_jre_path.IsEmpty()) { | |
| // 2) Use a bundled JVM if we have one. | |
| default_server_javabase_ = std::pair<blaze_util::Path, JavabaseType>( | |
| bundled_jre_path, JavabaseType::EMBEDDED); | |
| } else if (!autodetect_server_javabase) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "Could not find embedded or explicit server javabase, and " | |
| "--noautodetect_server_javabase is set."; | |
| } else { | |
| // 3) Otherwise fall back to using the default system JVM. | |
| blaze_util::Path system_javabase = GetSystemJavabase(); | |
| if (system_javabase.IsEmpty()) { | |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) | |
| << "Could not find system javabase. Ensure JAVA_HOME is set, or " | |
| "java is on your PATH."; | |
| } | |
| default_server_javabase_ = std::pair<blaze_util::Path, JavabaseType>( | |
| system_javabase, JavabaseType::SYSTEM); | |
| } | |
| } | |
| return default_server_javabase_; | |
| } | |
| blaze_util::Path StartupOptions::GetServerJavabase() const { | |
| return GetServerJavabaseAndType().first; | |
| } | |
| blaze_util::Path StartupOptions::GetExplicitServerJavabase() const { | |
| return explicit_server_javabase_; | |
| } | |
| blaze_util::Path StartupOptions::GetJvm() const { | |
| auto javabase_and_type = GetServerJavabaseAndType(); | |
| blaze_exit_code::ExitCode sanity_check_code = | |
| SanityCheckJavabase(javabase_and_type.first, javabase_and_type.second); | |
| if (sanity_check_code != blaze_exit_code::SUCCESS) { | |
| exit(sanity_check_code); | |
| } | |
| return javabase_and_type.first.GetRelative(GetJavaBinaryUnderJavabase()); | |
| } | |
| // Prints an appropriate error message and returns an appropriate error exit | |
| // code for a server javabase which failed sanity checks. | |
| static blaze_exit_code::ExitCode BadServerJavabaseError( | |
| StartupOptions::JavabaseType javabase_type, | |
| const std::map<string, string>& option_sources) { | |
| switch (javabase_type) { | |
| case StartupOptions::JavabaseType::EXPLICIT: { | |
| auto source = option_sources.find("server_javabase"); | |
| string rc_file; | |
| if (source != option_sources.end() && !source->second.empty()) { | |
| rc_file = source->second; | |
| } | |
| BAZEL_LOG(ERROR) | |
| << " The java path was specified by a '--server_javabase' option " + | |
| (rc_file.empty() ? "on the command line" : "in " + rc_file); | |
| return blaze_exit_code::BAD_ARGV; | |
| } | |
| case StartupOptions::JavabaseType::EMBEDDED: | |
| BAZEL_LOG(ERROR) << " Internal error: embedded JDK fails sanity check."; | |
| return blaze_exit_code::INTERNAL_ERROR; | |
| case StartupOptions::JavabaseType::SYSTEM: | |
| return blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR; | |
| default: | |
| BAZEL_LOG(ERROR) | |
| << " Internal error: server javabase type was not initialized."; | |
| // Fall through. | |
| } | |
| return blaze_exit_code::INTERNAL_ERROR; | |
| } | |
| blaze_exit_code::ExitCode StartupOptions::SanityCheckJavabase( | |
| const blaze_util::Path& javabase, | |
| StartupOptions::JavabaseType javabase_type) const { | |
| blaze_util::Path java_program = | |
| javabase.GetRelative(GetJavaBinaryUnderJavabase()); | |
| if (!blaze_util::CanExecuteFile(java_program)) { | |
| if (!blaze_util::PathExists(java_program)) { | |
| BAZEL_LOG(ERROR) << "Couldn't find java at '" | |
| << java_program.AsPrintablePath() << "'."; | |
| } else { | |
| BAZEL_LOG(ERROR) << "Java at '" << java_program.AsPrintablePath() | |
| << "' exists but is not executable: " | |
| << blaze_util::GetLastErrorString(); | |
| } | |
| return BadServerJavabaseError(javabase_type, option_sources); | |
| } | |
| if ( // If the full JDK is installed | |
| blaze_util::CanReadFile(javabase.GetRelative("jre/lib/rt.jar")) || | |
| // If just the JRE is installed | |
| blaze_util::CanReadFile(javabase.GetRelative("lib/rt.jar")) || | |
| // rt.jar does not exist in java 9+ so check for java instead | |
| blaze_util::CanReadFile(javabase.GetRelative("bin/java")) || | |
| blaze_util::CanReadFile(javabase.GetRelative("bin/java.exe"))) { | |
| return blaze_exit_code::SUCCESS; | |
| } | |
| BAZEL_LOG(ERROR) << "Problem with java installation: couldn't find/access " | |
| "rt.jar or java in " | |
| << javabase.AsPrintablePath(); | |
| return BadServerJavabaseError(javabase_type, option_sources); | |
| } | |
| blaze_util::Path StartupOptions::GetExe(const blaze_util::Path& jvm, | |
| const string& jar_path) const { | |
| return jvm; | |
| } | |
| void StartupOptions::AddJVMArgumentPrefix(const blaze_util::Path& javabase, | |
| std::vector<string>* result) const {} | |
| void StartupOptions::AddJVMArgumentSuffix( | |
| const blaze_util::Path& real_install_dir, const string& jar_path, | |
| std::vector<string>* result) const { | |
| if (extra_classpath.empty()) { | |
| result->push_back("-jar"); | |
| result->push_back(real_install_dir.GetRelative(jar_path).AsJvmArgument()); | |
| } else { | |
| string classpath = real_install_dir.GetRelative(jar_path).AsJvmArgument() + | |
| kListSeparator + extra_classpath; | |
| // Since we're not executing the server jar directly, we must manually set | |
| // module opening directives on the command line. | |
| for (const auto& option : getJvmModuleOptions()) { | |
| result->push_back(option); | |
| } | |
| result->push_back("-cp"); | |
| result->push_back(classpath); | |
| result->push_back("com.google.devtools.build.lib.bazel.Bazel"); | |
| } | |
| } | |
| blaze_exit_code::ExitCode StartupOptions::AddJVMArguments( | |
| const blaze_util::Path& server_javabase, std::vector<string>* result, | |
| const vector<string>& user_options, string* error) const { | |
| AddJVMLoggingArguments(result); | |
| // Disable the JVM's own unlimiting of file descriptors. We do this | |
| // ourselves in blaze.cc so we want our setting to propagate to the JVM. | |
| // | |
| // The reason to do this is that the JVM's unlimiting is suboptimal on | |
| // macOS. Under that platform, the JVM limits the open file descriptors | |
| // to the OPEN_MAX constant... which is much lower than the per-process | |
| // kernel allowed limit of kern.maxfilesperproc (which is what we set | |
| // ourselves to). | |
| result->push_back("-XX:-MaxFDLimit"); | |
| result->push_back( | |
| "-XX:OnOutOfMemoryError=touch " + | |
| GetOOMFilePath(blaze_util::Path(output_base)).AsJvmArgument()); | |
| bool use_compact_headers = use_compact_object_headers_; | |
| if (use_compact_headers) { | |
| // If it is true, but it was NOT explicitly set by the user (i.e. it's the | |
| // default), only enable it if we are using the embedded JDK to avoid | |
| // crashes on older system JDKs. | |
| if (option_sources.find("experimental_use_compact_object_headers") == | |
| option_sources.end()) { | |
| auto javabase_and_type = GetServerJavabaseAndType(); | |
| if (javabase_and_type.second != JavabaseType::EMBEDDED) { | |
| use_compact_headers = false; | |
| } | |
| } | |
| } | |
| if (use_compact_headers) { | |
| result->push_back("-XX:+UnlockExperimentalVMOptions"); | |
| result->push_back("-XX:+UseCompactObjectHeaders"); | |
| } else if (option_sources.find("experimental_use_compact_object_headers") != | |
| option_sources.end()) { | |
| // JDK 27 enables compact object headers by default, so an explicit opt-out | |
| // must also be passed to the JVM. | |
| result->push_back("-XX:-UseCompactObjectHeaders"); | |
| } | |
| return AddJVMMemoryArguments(server_javabase, result, user_options, error); | |
| } | |
| static std::string GetSimpleLogHandlerProps( | |
| const blaze_util::Path& java_log, | |
| const std::string& java_logging_formatter) { | |
| return "handlers=com.google.devtools.build.lib.util.SimpleLogHandler\n" | |
| ".level=INFO\n" | |
| "com.google.devtools.build.lib.util.SimpleLogHandler.level=INFO\n" | |
| "com.google.devtools.build.lib.util.SimpleLogHandler.prefix=" + | |
| java_log.AsJvmArgument() + | |
| "\n" | |
| "com.google.devtools.build.lib.util." | |
| "SimpleLogHandler.rotate_limit_bytes=5242880\n" | |
| "com.google.devtools.build.lib.util." | |
| "SimpleLogHandler.total_limit_bytes=20971520\n" | |
| "com.google.devtools.build.lib.util.SimpleLogHandler.formatter=" + | |
| java_logging_formatter + "\n"; | |
| } | |
| void StartupOptions::AddJVMLoggingArguments(std::vector<string>* result) const { | |
| // Configure logging | |
| const blaze_util::Path propFile = | |
| output_base.GetRelative("javalog.properties"); | |
| const blaze_util::Path java_log = output_base.GetRelative("java.log"); | |
| const std::string loggingProps = | |
| GetSimpleLogHandlerProps(java_log, java_logging_formatter); | |
| if (!blaze_util::WriteFile(loggingProps, propFile)) { | |
| perror( | |
| ("Couldn't write logging file " + propFile.AsPrintablePath()).c_str()); | |
| } else { | |
| result->push_back("-Djava.util.logging.config.file=" + | |
| propFile.AsJvmArgument()); | |
| result->push_back( | |
| "-Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=" | |
| "com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier"); | |
| } | |
| } | |
| blaze_exit_code::ExitCode StartupOptions::AddJVMMemoryArguments( | |
| const blaze_util::Path&, std::vector<string>*, const vector<string>&, | |
| string*) const { | |
| return blaze_exit_code::SUCCESS; | |
| } | |
| void StartupOptions::UpdateConfiguration(const string& install_md5, | |
| const string& workspace, | |
| const bool server_mode) { | |
| if (output_user_root.IsEmpty()) { | |
| // The default production output_user_root is | |
| // <default_output_root>/_<product_name>_<username>. | |
| // In a test, use a subdirectory of TEST_TMPDIR to be hermetic. | |
| blaze_util::Path output_root = | |
| blaze::IsRunningWithinTest() | |
| ? blaze_util::Path(blaze::GetPathEnv("TEST_TMPDIR")) | |
| : GetDefaultOutputRoot(); | |
| output_user_root = output_root.GetRelative("_" + GetLowercaseProductName() + | |
| "_" + GetUserName()); | |
| } | |
| if (install_base.IsEmpty()) { | |
| // The default install_base is <output_user_root>/install/<md5(blaze)>. | |
| // However, exec-server requires it to be explicitly set. | |
| if (server_mode) { | |
| BAZEL_DIE(blaze_exit_code::BAD_ARGV) | |
| << "exec-server requires --install_base"; | |
| } | |
| install_base = | |
| output_user_root.GetRelative("install").GetRelative(install_md5); | |
| } | |
| if (output_base.IsEmpty()) { | |
| // The default output_base is <output_user_root>/<md5(workspace)>. | |
| // However, exec-server requires it to be explicitly set. | |
| if (server_mode) { | |
| BAZEL_DIE(blaze_exit_code::BAD_ARGV) | |
| << "exec-server requires --output_base"; | |
| } | |
| output_base = blaze::GetHashedBaseDir(output_user_root, workspace); | |
| } | |
| if (failure_detail_out.IsEmpty()) { | |
| failure_detail_out = output_base.GetRelative("failure_detail.rawproto"); | |
| } | |
| } | |
| } // namespace blaze |