blob: 6ea04e490926d3c4c34751535c5898d72b6fa823 [file]
// Copyright 2026 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/server_launcher.h"
#include <climits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#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/option_processor.h"
#include "src/main/cpp/startup_options.h"
#include "src/main/cpp/util/bazel_log_handler.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 "src/main/cpp/workspace_layout.h"
#include "absl/time/time.h"
using blaze_util::GetLastErrorString;
namespace blaze {
using std::map;
using std::set;
using std::string;
using std::vector;
// Determines the maximum import depth for rc files based on the environment.
static int SelectMaxImportDepth() {
return ExistsEnv("BAZEL_UNLIMITED_IMPORT_DEPTH")
? INT_MAX
: OptionProcessor::MaxImportDepth;
}
std::pair<ClientLocks, DurationMillis> AcquireClientLocks(
const StartupOptions& startup_options) {
ClientLocks locks;
DurationMillis wait_time;
if (startup_options.lock_install_base) {
// Take a shared lock on the install base, because two simultaneous commands
// may run against the same install base. The lock file is a sibling of the
// install base directory to make it possible to synchronize concurrent
// creation through an atomic rename. An exclusive lock is only required to
// run automatic garbage collection of stale install bases.
// Always block waiting for the lock: in the event that the install base is
// currently being garbage collected, we want to recreate it.
blaze_util::Path install_base_parent =
startup_options.install_base.GetParent();
blaze_util::MakeDirectories(install_base_parent, 0777);
auto install_base_result = blaze::AcquireLock(
"install base",
install_base_parent.GetRelative(
startup_options.install_base.GetBaseName() + ".lock"),
LockMode::kShared, startup_options.batch,
/* timeout= */ absl::InfiniteDuration());
locks.install_base_lock = install_base_result.first;
wait_time += install_base_result.second;
}
// Take an exclusive lock on the output base, because two simultaneous
// commands may not run against the same output base.
auto output_base_result = blaze::AcquireLock(
"output base", startup_options.output_base.GetRelative("lock"),
LockMode::kExclusive, startup_options.batch,
startup_options.block_for_lock_timeout);
locks.output_base_lock = output_base_result.first;
wait_time += output_base_result.second;
return std::make_pair(std::move(locks), wait_time);
}
void ReleaseClientLocks(bool lock_install_base, ClientLocks* locks) {
if (!locks->output_base_lock.has_value()) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "ReleaseLocks() called but the output base lock has already been "
"released.";
}
blaze::ReleaseLock(*locks->output_base_lock);
locks->output_base_lock = std::nullopt;
if (lock_install_base) {
if (!locks->install_base_lock.has_value()) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "ReleaseLocks() called but the install base lock has already been "
"released.";
}
blaze::ReleaseLock(*locks->install_base_lock);
locks->install_base_lock = std::nullopt;
}
}
// Escapes colons by replacing them with '_C' and underscores by replacing
// them with '_U'. E.g. "name:foo_bar" becomes "name_Cfoo_Ubar"
static string EscapeForOptionSource(const string& input) {
string result = input;
blaze_util::Replace("_", "_U", &result);
blaze_util::Replace(":", "_C", &result);
return result;
}
// Returns the JVM command argument array.
vector<string> GetServerExeArgs(const blaze_util::Path& jvm_path,
const string& server_jar_path,
const string& install_md5,
const WorkspaceLayout& workspace_layout,
const string& workspace,
const StartupOptions& startup_options) {
vector<string> result;
// e.g. A Blaze server process running in ~/src/build_root (where there's a
// ~/src/build_root/WORKSPACE file) will appear in ps(1) as "blaze(src)".
result.push_back(startup_options.GetLowercaseProductName() + "(" +
workspace_layout.GetPrettyWorkspaceName(workspace) + ")");
startup_options.AddJVMArgumentPrefix(jvm_path.GetParent().GetParent(),
&result);
// com.google.devtools.build.lib.unsafe.StringUnsafe uses reflection to
// access private fields in java.lang.String. The Bazel server requires Java
// 11, so this option is known to be supported.
result.push_back("--add-opens=java.base/java.lang=ALL-UNNAMED");
result.push_back("-XX:+UnlockDiagnosticVMOptions");
result.push_back("-XX:-BytecodeVerificationLocal");
result.push_back("-XX:-BytecodeVerificationRemote");
vector<string> user_options = startup_options.host_jvm_args;
// Add JVM arguments particular to building blaze64 and particular JVM
// versions.
string error;
blaze_exit_code::ExitCode jvm_args_exit_code =
startup_options.AddJVMArguments(startup_options.GetServerJavabase(),
&result, user_options, &error);
if (jvm_args_exit_code != blaze_exit_code::SUCCESS) {
BAZEL_DIE(jvm_args_exit_code) << error;
}
// We put all directories on java.library.path that contain .so/.dll files.
set<string> java_library_paths;
std::stringstream java_library_path;
java_library_path << "-Djava.library.path=";
blaze_util::Path real_install_dir =
blaze_util::Path(startup_options.install_base);
vector<blaze_util::Path> extracted_files;
blaze_util::GetAllFilesUnder(real_install_dir, &extracted_files);
for (const auto& it : extracted_files) {
if (IsSharedLibrary(it.GetBaseName())) {
java_library_paths.insert(it.GetParent().AsJvmArgument());
}
}
bool first_libpath = true;
for (const auto& libpath : java_library_paths) {
if (!first_libpath) {
java_library_path << kListSeparator;
}
first_libpath = false;
java_library_path << libpath;
}
result.push_back(java_library_path.str());
// TODO: Investigate whether this still has any effect. File name encoding
// is governed by sun.jnu.encoding in JDKs with JEP 400, which can't be
// influenced by setting a property.
result.push_back("-Dfile.encoding=ISO-8859-1");
// Force into the root locale to ensure consistent behavior of string
// operations across machines (e.g. in the tr_TR locale, capital ASCII 'I'
// turns into a special Unicode 'i' when converted to lower case).
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html#ROOT
result.push_back("-Duser.country=");
result.push_back("-Duser.language=");
result.push_back("-Duser.variant=");
// Allow more files to be watched per directory than the default limit of 500.
// The limit of 10,000 is arbitrary, but should be sufficient for most cases
// and can always be increased by the user if necessary.
// https://github.com/openjdk/jdk/blob/2faf8b8d582183275b1fdc92313a1c63c1753e80/src/java.base/share/classes/sun/nio/fs/AbstractWatchKey.java#L40
result.push_back("-Djdk.nio.file.WatchService.maxEventsPerPoll=10000");
// Disable warnings about unsafe memory access, which still occurs in
// protobuf.
// TODO: Drop this when protobuf uses VarHandle.
result.push_back("-Dsun.misc.unsafe.memory.access=allow");
// Let the system decide whether to prefer IPv6
result.push_back("-Djava.net.preferIPv6Addresses=system");
#if defined(_WIN32)
// See and use more than 64 CPUs on Windows.
// https://bugs.openjdk.org/browse/JDK-6942632
result.push_back("-XX:+IgnoreUnrecognizedVMOptions");
result.push_back("-XX:+UseAllWindowsProcessorGroups");
#endif
if (startup_options.host_jvm_debug) {
BAZEL_LOG(USER)
<< "Running host JVM under debugger (listening on TCP port 5005).";
// Start JVM so that it listens for a connection from a
// JDWP-compliant debugger:
result.push_back(
"-agentlib:jdwp=transport=dt_socket,server=y,address=5005");
}
result.insert(result.end(), user_options.begin(), user_options.end());
startup_options.AddJVMArgumentSuffix(real_install_dir, server_jar_path,
&result);
// JVM arguments are complete. Now pass in Blaze startup options.
// Note that we always use the --flag=ARG form (instead of the --flag ARG
// one) so that BlazeRuntime#splitStartupOptions has an easy job.
// TODO(b/152047869): Test that whatever the list constructed after this
// line is actually a list of parseable startup options.
if (!startup_options.batch) {
result.push_back("--max_idle_secs=" +
blaze_util::ToString(startup_options.max_idle_secs));
if (startup_options.shutdown_on_low_sys_mem) {
result.push_back("--shutdown_on_low_sys_mem");
} else {
result.push_back("--noshutdown_on_low_sys_mem");
}
} else {
// --batch must come first in the arguments to Java main() because
// the code expects it to be at args[0] if it's been set.
result.push_back("--batch");
}
if (startup_options.command_port != 0) {
result.push_back("--command_port=" +
blaze_util::ToString(startup_options.command_port));
}
result.push_back("--connect_timeout_secs=" +
blaze_util::ToString(startup_options.connect_timeout_secs));
result.push_back("--output_user_root=" +
startup_options.output_user_root.AsCommandLineArgument());
result.push_back("--install_base=" +
startup_options.install_base.AsCommandLineArgument());
result.push_back("--install_md5=" + install_md5);
if (startup_options.lock_install_base) {
// This flag is not user-settable. Its sole purpose is to alter the behavior
// for Blaze and Bazel. Do *not* explicitly set it to disabled, because at
// Google we rely on the ability to run the client code against a server
// built before the flag was added.
result.push_back("--lock_install_base");
}
result.push_back("--output_base=" +
startup_options.output_base.AsCommandLineArgument());
result.push_back("--workspace_directory=" +
blaze_util::ConvertPath(workspace));
if (startup_options.autodetect_server_javabase) {
result.push_back("--default_system_javabase=" + GetSystemJavabase());
}
if (!startup_options.server_jvm_out.IsEmpty()) {
result.push_back("--server_jvm_out=" +
startup_options.server_jvm_out.AsCommandLineArgument());
}
if (!startup_options.failure_detail_out.IsEmpty()) {
result.push_back(
"--failure_detail_out=" +
startup_options.failure_detail_out.AsCommandLineArgument());
}
if (!startup_options.digest_function.empty()) {
// Only include this if a value is requested - we rely on the empty case
// being "null" to set the programmatic default in the server.
result.push_back("--digest_function=" + startup_options.digest_function);
}
if (!startup_options.unix_digest_hash_attribute_name.empty()) {
result.push_back("--unix_digest_hash_attribute_name=" +
startup_options.unix_digest_hash_attribute_name);
}
if (startup_options.idle_server_tasks) {
result.push_back("--idle_server_tasks");
} else {
result.push_back("--noidle_server_tasks");
}
if (startup_options.write_command_log) {
result.push_back("--write_command_log");
} else {
result.push_back("--nowrite_command_log");
}
if (startup_options.fatal_event_bus_exceptions) {
result.push_back("--fatal_event_bus_exceptions");
} else {
result.push_back("--nofatal_event_bus_exceptions");
}
if (startup_options.windows_enable_symlinks) {
result.push_back("--windows_enable_symlinks");
} else {
result.push_back("--nowindows_enable_symlinks");
}
if (startup_options.delete_output_base_on_shutdown) {
result.push_back("--delete_output_base_on_shutdown");
} else {
result.push_back("--nodelete_output_base_on_shutdown");
}
if (startup_options.remote_repo_contents_cache) {
result.push_back("--experimental_remote_repo_contents_cache");
// Don't set the flag to false if it's not set - non-OSS Blaze does not know
// about this flag.
}
// We use this syntax so that the logic in AreStartupOptionsDifferent() that
// decides whether the server needs killing is simpler. This is parsed by
// the Java code where --noclient_debug and --client_debug=false are
// equivalent. Note that --client_debug false (separated by space) won't
// work either, because the logic in AreStartupOptionsDifferent() assumes
// that every argument is in the --arg=value form.
if (startup_options.client_debug) {
result.push_back("--client_debug=true");
} else {
result.push_back("--client_debug=false");
}
// These flags are passed to the java process only for Blaze reporting
// purposes; the real interpretation of the jvm flags occurs when we set up
// the java command line.
if (!startup_options.GetExplicitServerJavabase().IsEmpty()) {
result.push_back(
"--server_javabase=" +
startup_options.GetExplicitServerJavabase().AsCommandLineArgument());
}
if (startup_options.host_jvm_debug) {
result.push_back("--host_jvm_debug");
}
if (!startup_options.host_jvm_args.empty()) {
for (const auto& arg : startup_options.host_jvm_args) {
result.push_back("--host_jvm_args=" + arg);
}
}
if (!startup_options.extra_classpath.empty()) {
result.push_back("--extra_classpath=" + startup_options.extra_classpath);
}
// Pass in invocation policy as a startup argument for batch mode only.
if (startup_options.batch && !startup_options.invocation_policy.empty()) {
result.push_back("--invocation_policy=" +
startup_options.invocation_policy);
}
result.push_back("--product_name=" + startup_options.product_name);
#ifdef __linux__
if (!startup_options.cgroup_parent.empty()) {
result.push_back("--experimental_cgroup_parent=" +
startup_options.cgroup_parent);
}
if (startup_options.run_in_user_cgroup) {
result.push_back("--experimental_run_in_user_cgroup");
}
#endif
startup_options.AddExtraOptions(&result);
// The option sources are transmitted in the following format:
// --option_sources=option1:source1:option2:source2:...
string option_sources = "--option_sources=";
bool first = true;
for (const auto& it : startup_options.option_sources) {
if (!first) {
option_sources += ':';
}
first = false;
option_sources += EscapeForOptionSource(it.first) + ":" +
EscapeForOptionSource(it.second);
}
result.push_back(option_sources);
#if defined(__OpenBSD__)
// When spawning the server's JVM process, we normally set argv[0] to
// "bazel(workspace)". On OpenBSD, doing so causes the JVM process to fail
// during startup; ld.so fails to find a shared library that exists in
// /usr/local/jdk-1.8.0/jre/lib/amd64. Setting LD_LIBRARY_PATH does not help,
// but setting argv[0] to the JVM binary's path
// (/usr/local/jdk-1.8.0/bin/java) allows the JVM process to run. The JVM
// process apparently tries to compute a path to where the shared libraries
// should be, via a relative path from the JVM executable's path -- but
// OpenBSD does not provide a way for a process to determine a path to its
// own executable, and so the JVM falls back to searching the PATH for
// argv[0], which of course fails when argv[0] looks like "bazel(workspace)".
//
// TODO(aldersondrive): This hack is unnecessary on FreeBSD, but the relevant
// OpenJDK code doesn't seem to include anything FreeBSD-specific.
// Investigate why and possibly remove this.
result[0] = startup_options.GetExe(jvm_path, server_jar_path).AsNativePath();
#endif
return result;
}
// Join the elements of the specified array with NUL's (\0's), akin to the
// format of /proc/$PID/cmdline.
string GetArgumentString(const vector<string>& argument_array) {
string result;
blaze_util::JoinStrings(argument_array, '\0', &result);
return result;
}
void EnsureServerDir(const blaze_util::Path& server_dir) {
// The server dir has the connection info - don't allow access by other users.
if (!blaze_util::MakeDirectories(server_dir, 0700)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "server directory '" << server_dir.AsPrintablePath()
<< "' could not be created: " << GetLastErrorString();
}
}
// Do a chdir into the workspace, and die if it fails.
void GoToWorkspace(const WorkspaceLayout& workspace_layout,
const string& workspace) {
if (workspace_layout.InWorkspace(workspace) &&
!blaze_util::ChangeDirectory(workspace)) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "changing directory into " << workspace
<< " failed: " << GetLastErrorString();
}
}
bool IsServerMode(const string& command) { return "exec-server" == command; }
// Keep this in sync with built-in commands annotated with
// @Command(mustRunInWorkspace = false).
static bool CanRunOutsideWorkspace(const string& command) {
return command == "canonicalize-flags" || command == "dump" ||
command == "help" || command == "info" || command == "license" ||
command == "shutdown" || command == "version";
}
// Returns the path of the file that the server JVM reads its arguments from on
// Windows.
blaze_util::Path GetServerJvmArgFilePath(const blaze_util::Path& output_base) {
return output_base.GetRelative("server_jvm.args");
}
// Returns the server's PID from the pid file, or -1 if there was an error.
int GetServerPid(const blaze_util::Path& server_dir) {
// Note: there is no race here on startup since the server creates
// the pid file strictly before it binds the socket.
blaze_util::Path pid_file = server_dir.GetRelative(kServerPidFile);
string bufstr;
int result;
if (!blaze_util::ReadFile(pid_file, &bufstr, 32) ||
!blaze_util::safe_strto32(bufstr, &result)) {
return -1;
}
return result;
}
bool NeedsNewInstallationSymlink(const StartupOptions& startup_options) {
const blaze_util::Path install_base_symlink =
startup_options.output_base.GetRelative("install");
blaze_util::Path prev_install_base;
return !blaze_util::ReadDirectorySymlink(install_base_symlink,
&prev_install_base) ||
!blaze_util::ArePathsEquivalent(prev_install_base,
startup_options.install_base);
}
void UpdateInstallationSymlink(const StartupOptions& startup_options) {
const blaze_util::Path install_base_symlink =
startup_options.output_base.GetRelative("install");
blaze_util::UnlinkPath(install_base_symlink);
if (!SymlinkDirectories(startup_options.install_base, install_base_symlink)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "failed to create installation symlink '"
<< install_base_symlink.AsPrintablePath()
<< "': " << GetLastErrorString();
}
// Update the mtime of the install base so that cleanup tools can
// find install bases that haven't been used for a long time.
// Ignore permissions errors (i.e. if the install base is not writable).
if (!SetMtimeToNowIfPossible(
blaze_util::Path(startup_options.install_base))) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "failed to set timestamp on '"
<< startup_options.install_base.AsPrintablePath()
<< "': " << GetLastErrorString();
}
}
// Parse the options.
static void ParseOptionsOrDie(const string& cwd, const string& workspace,
OptionProcessor& option_processor, int argc,
const char* const* argv, int max_import_depth) {
std::string error;
std::vector<std::string> args(argv, argv + argc);
const blaze_exit_code::ExitCode parse_exit_code =
option_processor.ParseOptions(args, workspace, cwd, &error,
max_import_depth);
if (parse_exit_code != blaze_exit_code::SUCCESS) {
option_processor.PrintStartupOptionsProvenanceMessage();
BAZEL_DIE(parse_exit_code) << error;
}
}
string GetCanonicalCwd() {
string result = blaze_util::MakeCanonical(blaze_util::GetCwd().c_str());
if (result.empty()) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "blaze_util::MakeCanonical('" << blaze_util::GetCwd()
<< "') failed: " << GetLastErrorString();
}
return result;
}
void PrepareDirectories(StartupOptions* startup_options) {
blaze::CreateSecureDirectory(
blaze_util::Path(startup_options->output_user_root));
if (!blaze_util::PathExists(startup_options->output_base)) {
if (!blaze_util::MakeDirectories(startup_options->output_base, 0777)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Output base directory '"
<< startup_options->output_base.AsPrintablePath()
<< "' could not be created: " << GetLastErrorString();
}
} else {
if (!blaze_util::IsDirectory(startup_options->output_base)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Output base directory '"
<< startup_options->output_base.AsPrintablePath()
<< "' could not be created. It exists but is not a directory.";
}
}
if (!blaze_util::CanAccessDirectory(startup_options->output_base)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Output base directory '"
<< startup_options->output_base.AsPrintablePath()
<< "' must be readable and writable.";
}
ExcludePathFromBackup(startup_options->output_base);
startup_options->output_base = startup_options->output_base.Canonicalize();
if (startup_options->output_base.IsEmpty()) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Output base directory could not be canonicalized: "
<< GetLastErrorString();
}
}
// Prepares the environment to be suitable to start a JVM.
// Changes made to the environment in this function *will not* be part
// of '--client_env'.
map<string, EnvVarValue> PrepareEnvironmentForJvm() {
map<string, EnvVarValue> result;
// Make sure all existing environment variables appear as part of the
// resulting map unless they are overridden below by UNSET values.
//
// Even though the map we return is intended to represent a "delta" of
// environment variables to modify the current process, we may actually use
// such map to configure a process from scratch (via interfaces like execvpe
// or posix_spawn), so we need to inherit any untouched variables.
for (const auto& var_value : blaze::GetProcessedEnv()) {
std::string::size_type equals = var_value.find('=');
if (equals == std::string::npos) {
// Ignore possibly-bad environment. We don't control what we see in this
// global variable, so it could be invalid.
continue;
}
const std::string var = var_value.substr(0, equals);
const std::string value = var_value.substr(equals + 1);
result[var] = EnvVarValue(EnvVarAction::SET, value);
}
if (blaze::ExistsEnv("LD_ASSUME_KERNEL")) {
// Fix for bug: if ulimit -s and LD_ASSUME_KERNEL are both
// specified, the JVM fails to create threads. See thread_stack_regtest.
// This is also provoked by LD_LIBRARY_PATH=/usr/lib/debug,
// or anything else that causes the JVM to use LinuxThreads.
BAZEL_LOG(WARNING) << "ignoring LD_ASSUME_KERNEL in environment.";
result["LD_ASSUME_KERNEL"] = EnvVarValue(EnvVarAction::UNSET, "");
}
if (blaze::ExistsEnv("LD_PRELOAD")) {
BAZEL_LOG(WARNING) << "ignoring LD_PRELOAD in environment.";
result["LD_PRELOAD"] = EnvVarValue(EnvVarAction::UNSET, "");
}
// These would override --host_jvm_args
for (const auto& var :
{"_JAVA_OPTIONS", "JDK_JAVA_OPTIONS", "JAVA_TOOL_OPTIONS"}) {
if (blaze::ExistsEnv(var)) {
BAZEL_LOG(WARNING) << "ignoring " << var << " in environment.";
result[var] = EnvVarValue(EnvVarAction::UNSET, "");
}
}
// TODO(bazel-team): We've also seen a failure during loading (creating
// threads?) when ulimit -Hs 8192. Characterize that and check for it here.
// Ensure that the JVM runs with a locale that supports Unicode characters in
// filenames, environment variables, etc. The approach differs by OS:
// - On macOS, the JVM always uses UTF-8, so we don't need to do anything.
// - On Windows, the JVM uses the system code page to determine the encoding.
// For the embedded JDK, we force this to UTF-8 in minimize_jdk.sh.
// - On Linux, the JVM goes through the regular locale mechanism. In
// particular, we can only force UTF-8 if we can find a locale that supports
// it. Furthermore, for backwards compatibility with setups using other
// encodings, we pick a Latin-1 locale if possible to support arbitrary byte
// sequences, not just valid UTF-8.
#ifdef __linux__
const char* want_locale = nullptr;
for (auto candidate_locale : {"en_US.ISO-8859-1", "C.UTF-8", "en_US.UTF-8"}) {
locale_t locale = newlocale(LC_CTYPE_MASK, candidate_locale, nullptr);
if (locale != nullptr) {
freelocale(locale);
want_locale = candidate_locale;
break;
}
}
if (want_locale != nullptr) {
result["LC_ALL"] = EnvVarValue(EnvVarAction::SET, want_locale);
}
#endif
return result;
}
// Replace this process with the blaze server. Does not exit.
void RunServerMode(const string& install_md5,
const WorkspaceLayout& workspace_layout,
const string& workspace,
const StartupOptions& startup_options) {
if (startup_options.batch) {
BAZEL_DIE(blaze_exit_code::BAD_ARGV)
<< "exec-server command is not compatible with --batch";
}
const blaze_util::Path jvm_path = startup_options.GetJvm();
const string server_jar_path = startup_options.GetServerJarFilename();
const blaze_util::Path server_exe =
startup_options.GetExe(jvm_path, server_jar_path);
vector<string> server_exe_args =
GetServerExeArgs(jvm_path, server_jar_path, install_md5, workspace_layout,
workspace, startup_options);
const blaze_util::Path server_dir =
blaze_util::Path(startup_options.output_base).GetRelative("server");
BAZEL_LOG(INFO) << "Running in server mode.";
int server_pid = GetServerPid(server_dir);
if (server_pid > 0 &&
VerifyServerProcess(server_pid, startup_options.output_base)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "exec-server failed, please shut down existing server pid="
<< server_pid << " and retry.";
}
if (NeedsNewInstallationSymlink(startup_options)) {
UpdateInstallationSymlink(startup_options);
}
EnsureServerDir(server_dir);
// Remove any stale server.starttime file from a previous daemon run so
// VerifyServerProcess checks /proc/<pid>/stat without comparing against an
// outdated start time.
(void)blaze_util::UnlinkPath(server_dir.GetRelative("server.starttime"));
blaze_util::WriteFile(blaze::GetProcessIdAsString(),
server_dir.GetRelative("server.pid.txt"));
blaze_util::WriteFile(GetArgumentString(server_exe_args),
server_dir.GetRelative("cmdline"));
GoToWorkspace(workspace_layout, workspace);
SetScheduling(startup_options.batch_cpu_scheduling,
startup_options.io_nice_level);
{
WithEnvVars env_obj(PrepareEnvironmentForJvm());
#ifdef __linux__
bool run_in_user_cgroup = startup_options.run_in_user_cgroup;
#else
bool run_in_user_cgroup = false;
#endif
ExecuteServerJvm(server_exe, server_exe_args,
GetServerJvmArgFilePath(startup_options.output_base),
run_in_user_cgroup);
}
}
StartupOptions* SetupAndParseOptions(int argc, const char* const* argv,
const string& cwd, const string& workspace,
WorkspaceLayout* workspace_layout,
OptionProcessor* option_processor) {
// Best-effort operation to raise the resource limits from soft to hard. We
// do this early during the main program instead of just before execing the
// Blaze server binary, because it's easier (for testing purposes) and because
// the Blaze client also benefits from this (e.g. during installation).
UnlimitResources();
#if defined(_WIN32) || defined(__CYGWIN__)
// Must be done before command line parsing.
// ParseOptionsOrDie already populate --client_env, so detect bash before it
// happens.
(void)DetectBashAndExportBazelSh();
#endif // if defined(_WIN32) || defined(__CYGWIN__)
if (blaze::IsRunningWithinTest()) {
BAZEL_LOG(USER) << "$TEST_TMPDIR defined, some defaults will be overridden";
}
int max_import_depth = blaze::SelectMaxImportDepth();
ParseOptionsOrDie(cwd, workspace, *option_processor, argc, argv,
max_import_depth);
StartupOptions* startup_options = option_processor->GetParsedStartupOptions();
startup_options->MaybeLogStartupOptionWarnings();
if (startup_options->client_debug) {
SetDebugLog(blaze_util::LOGGINGDETAIL_DEBUG);
} else if (startup_options->quiet) {
SetDebugLog(blaze_util::LOGGINGDETAIL_QUIET);
} else {
SetDebugLog(blaze_util::LOGGINGDETAIL_USER);
}
// If client_debug was false, this is ignored, so it's accurate.
BAZEL_LOG(INFO) << "Debug logging requested, sending all client log "
"statements to stderr";
BAZEL_LOG(INFO) << "Running (pid=" << GetProcessIdAsString() << ")";
if (startup_options->unlimit_coredumps) {
UnlimitCoredumps();
}
if (!workspace_layout->InWorkspace(workspace)) {
const string command = option_processor->GetCommand();
if (!CanRunOutsideWorkspace(command)) {
BAZEL_DIE(blaze_exit_code::BAD_ARGV)
<< startup_options->product_name
<< (command.empty() ? "" : " '" + command + "'")
<< " must be invoked from within a workspace (below a"
<< " directory having a MODULE.bazel file).\n"
<< "See documentation at"
<< " https://bazel.build/concepts/build-ref#workspace";
}
startup_options->batch = true;
}
return startup_options;
}
int ExecServer(int argc, const char* const* argv,
WorkspaceLayout* workspace_layout,
OptionProcessor* option_processor) {
blaze_util::InitializeStdOutErrForUtf8();
// Logging must be set first to assure no log statements are missed.
std::unique_ptr<blaze_util::BazelLogHandler> default_handler(
new blaze_util::BazelLogHandler());
blaze_util::SetLogHandler(std::move(default_handler));
string cwd = GetCanonicalCwd();
blaze::SetupStdStreams();
const string workspace = workspace_layout->GetWorkspace(cwd);
StartupOptions* startup_options = SetupAndParseOptions(
argc, argv, cwd, workspace, workspace_layout, option_processor);
if (!IsServerMode(option_processor->GetCommand())) {
BAZEL_DIE(blaze_exit_code::BAD_ARGV)
<< "Expected 'exec-server' command, got '"
<< option_processor->GetCommand() << "'";
}
startup_options->UpdateConfiguration("", workspace, /*server_mode=*/true);
PrepareDirectories(startup_options);
auto [client_locks, command_wait_duration] =
AcquireClientLocks(*startup_options);
BAZEL_LOG(INFO) << "Acquired the client lock, waited "
<< command_wait_duration.millis << " milliseconds";
WarnFilesystemType(startup_options->output_base);
if (!blaze_util::IsDirectory(startup_options->install_base)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Install base directory '"
<< startup_options->install_base.AsPrintablePath()
<< "' does not exist or is not a directory.";
}
string install_md5;
blaze_util::Path install_base_key_path =
startup_options->install_base.GetRelative("install_base_key");
if (!blaze_util::ReadFile(install_base_key_path, &install_md5)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Failed to read '" << install_base_key_path.AsPrintablePath()
<< "': " << GetLastErrorString();
}
RunServerMode(install_md5, *workspace_layout, workspace, *startup_options);
return 0;
}
} // namespace blaze