blob: 04f7eab3cd7efd5abd8591d8e899eef516875df4 [file] [log] [blame]
// 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/blaze_util_platform.h"
#include <fcntl.h>
#include <stdarg.h> // va_start, va_end, va_list
#include <windows.h>
#include <lmcons.h> // UNLEN
#include <versionhelpers.h> // IsWindows8OrGreater
#include <io.h> // _open
#include <knownfolders.h> // FOLDERID_Profile
#include <objbase.h> // CoTaskMemFree
#include <shlobj.h> // SHGetKnownFolderPath
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <mutex> // NOLINT
#include <set>
#include <sstream>
#include <thread> // NOLINT (to silence Google-internal linter)
#include <type_traits> // static_assert
#include <vector>
#include "src/main/cpp/blaze_util.h"
#include "src/main/cpp/global_variables.h"
#include "src/main/cpp/startup_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/md5.h"
#include "src/main/cpp/util/numbers.h"
#include "src/main/cpp/util/path.h"
#include "src/main/cpp/util/path_platform.h"
#include "src/main/cpp/util/strings.h"
#include "src/main/native/windows/file.h"
#include "src/main/native/windows/util.h"
namespace blaze {
// Ensure we can safely cast (const) wchar_t* to LP(C)WSTR.
// This is true with MSVC but usually not with GCC.
static_assert(sizeof(wchar_t) == sizeof(WCHAR),
"wchar_t and WCHAR should be the same size");
// When using widechar Win32 API functions the maximum path length is 32K.
// Add 4 characters for potential UNC prefix and a couple more for safety.
static const size_t kWindowsPathBufferSize = 0x8010;
using bazel::windows::AutoAttributeList;
using bazel::windows::AutoHandle;
using bazel::windows::CreateJunction;
using bazel::windows::CreateJunctionResult;
// TODO(bazel-team): stop using BAZEL_DIE, handle errors on the caller side.
// BAZEL_DIE calls exit(exitcode), which makes it difficult to follow the
// control flow and does not call destructors on local variables on the call
// stack.
using blaze_util::GetLastErrorString;
using std::string;
using std::unique_ptr;
using std::wstring;
namespace embedded_binaries {
class WindowsDumper : public Dumper {
public:
static WindowsDumper* Create(string* error);
~WindowsDumper() { Finish(nullptr); }
void Dump(const void* data, const size_t size, const string& path) override;
bool Finish(string* error) override;
private:
WindowsDumper() : threadpool_(NULL), cleanup_group_(NULL) {}
PTP_POOL threadpool_;
PTP_CLEANUP_GROUP cleanup_group_;
TP_CALLBACK_ENVIRON threadpool_env_;
std::mutex dir_cache_lock_;
std::set<string> dir_cache_;
std::mutex error_lock_;
string error_msg_;
};
namespace {
class DumpContext {
public:
DumpContext(unique_ptr<uint8_t[]> data, const size_t size, const string path,
std::mutex* dir_cache_lock, std::set<string>* dir_cache,
std::mutex* error_lock_, string* error_msg);
void Run();
private:
void MaybeSignalError(const string& msg);
unique_ptr<uint8_t[]> data_;
const size_t size_;
const string path_;
std::mutex* dir_cache_lock_;
std::set<string>* dir_cache_;
std::mutex* error_lock_;
string* error_msg_;
};
VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance,
_Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work);
} // namespace
Dumper* Create(string* error) { return WindowsDumper::Create(error); }
WindowsDumper* WindowsDumper::Create(string* error) {
unique_ptr<WindowsDumper> result(new WindowsDumper());
result->threadpool_ = CreateThreadpool(NULL);
if (result->threadpool_ == NULL) {
if (error) {
string msg = GetLastErrorString();
*error = "CreateThreadpool failed: " + msg;
}
return nullptr;
}
result->cleanup_group_ = CreateThreadpoolCleanupGroup();
if (result->cleanup_group_ == NULL) {
string msg = GetLastErrorString();
CloseThreadpool(result->threadpool_);
if (error) {
string msg = GetLastErrorString();
*error = "CreateThreadpoolCleanupGroup failed: " + msg;
}
return nullptr;
}
// I (@laszlocsomor) experimented with different thread counts and found that
// 8 threads provide a significant advantage over 1 thread, but adding more
// threads provides only marginal speedup.
SetThreadpoolThreadMaximum(result->threadpool_, 16);
SetThreadpoolThreadMinimum(result->threadpool_, 8);
InitializeThreadpoolEnvironment(&result->threadpool_env_);
SetThreadpoolCallbackPool(&result->threadpool_env_, result->threadpool_);
SetThreadpoolCallbackCleanupGroup(&result->threadpool_env_,
result->cleanup_group_, NULL);
return result.release(); // release pointer ownership
}
void WindowsDumper::Dump(const void* data, const size_t size,
const string& path) {
{
std::lock_guard<std::mutex> g(error_lock_);
if (!error_msg_.empty()) {
return;
}
}
unique_ptr<uint8_t[]> data_copy(new uint8_t[size]);
memcpy(data_copy.get(), data, size);
unique_ptr<DumpContext> ctx(new DumpContext(std::move(data_copy), size, path,
&dir_cache_lock_, &dir_cache_,
&error_lock_, &error_msg_));
PTP_WORK w = CreateThreadpoolWork(WorkCallback, ctx.get(), &threadpool_env_);
if (w == NULL) {
string err = GetLastErrorString();
err = string("WindowsDumper::Dump() couldn't submit work: ") + err;
std::lock_guard<std::mutex> g(error_lock_);
error_msg_ = err;
} else {
ctx.release(); // release pointer ownership
SubmitThreadpoolWork(w);
}
}
bool WindowsDumper::Finish(string* error) {
if (threadpool_ == NULL) {
return true;
}
CloseThreadpoolCleanupGroupMembers(cleanup_group_, FALSE, NULL);
CloseThreadpoolCleanupGroup(cleanup_group_);
CloseThreadpool(threadpool_);
threadpool_ = NULL;
cleanup_group_ = NULL;
std::lock_guard<std::mutex> g(error_lock_);
if (!error_msg_.empty() && error) {
*error = error_msg_;
}
return error_msg_.empty();
}
namespace {
DumpContext::DumpContext(unique_ptr<uint8_t[]> data, const size_t size,
const string path, std::mutex* dir_cache_lock,
std::set<string>* dir_cache, std::mutex* error_lock_,
string* error_msg)
: data_(std::move(data)),
size_(size),
path_(path),
dir_cache_lock_(dir_cache_lock),
dir_cache_(dir_cache),
error_msg_(error_msg) {}
void DumpContext::Run() {
string dirname = blaze_util::Dirname(path_);
bool success = true;
// Performance optimization: memoize the paths we already created a
// directory for, to spare a stat in attempting to recreate an already
// existing directory. This optimization alone shaves off seconds from the
// extraction time on Windows.
{
std::lock_guard<std::mutex> guard(*dir_cache_lock_);
if (dir_cache_->insert(dirname).second) {
success = blaze_util::MakeDirectories(dirname, 0777);
}
}
if (!success) {
MaybeSignalError(string("Couldn't create directory '") + dirname + "'");
return;
}
if (!blaze_util::WriteFile(data_.get(), size_, path_, 0755)) {
MaybeSignalError(string("Failed to write zipped file '") + path_ + "'");
}
}
void DumpContext::MaybeSignalError(const string& msg) {
std::lock_guard<std::mutex> g(*error_lock_);
*error_msg_ = msg;
}
VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance,
_Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work) {
unique_ptr<DumpContext> ctx(reinterpret_cast<DumpContext*>(Context));
ctx->Run();
}
} // namespace
} // namespace embedded_binaries
SignalHandler SignalHandler::INSTANCE;
class WindowsClock {
public:
uint64_t GetMilliseconds() const;
static const WindowsClock INSTANCE;
private:
// Clock frequency per seconds.
// It's safe to cache this because (from QueryPerformanceFrequency on MSDN):
// "The frequency of the performance counter is fixed at system boot and is
// consistent across all processors. Therefore, the frequency need only be
// queried upon application initialization, and the result can be cached."
const LARGE_INTEGER kFrequency;
WindowsClock();
static LARGE_INTEGER GetFrequency();
static LARGE_INTEGER GetMillisecondsAsLargeInt(const LARGE_INTEGER& freq);
};
BOOL WINAPI ConsoleCtrlHandler(_In_ DWORD ctrlType) {
static volatile int sigint_count = 0;
switch (ctrlType) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
if (++sigint_count >= 3) {
SigPrintf(
"\n%s caught third Ctrl+C handler signal; killed.\n\n",
SignalHandler::Get().GetGlobals()->options->product_name.c_str());
if (SignalHandler::Get().GetGlobals()->server_pid != -1) {
KillServerProcess(
SignalHandler::Get().GetGlobals()->server_pid,
SignalHandler::Get().GetGlobals()->options->output_base);
}
_exit(1);
}
SigPrintf(
"\n%s Ctrl+C handler; shutting down.\n\n",
SignalHandler::Get().GetGlobals()->options->product_name.c_str());
SignalHandler::Get().CancelServer();
return TRUE;
case CTRL_CLOSE_EVENT:
SignalHandler::Get().CancelServer();
return TRUE;
}
return false;
}
void SignalHandler::Install(GlobalVariables* globals,
SignalHandler::Callback cancel_server) {
_globals = globals;
_cancel_server = cancel_server;
::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE);
}
ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) {
// We do not handle signals on Windows; always exit with exit_code.
exit(exit_code);
}
// A signal-safe version of fprintf(stderr, ...).
//
// WARNING: any output from the blaze client may be interleaved
// with output from the blaze server. In --curses mode,
// the Blaze server often erases the previous line of output.
// So, be sure to end each such message with TWO newlines,
// otherwise it may be erased by the next message from the
// Blaze server.
// Also, it's a good idea to start each message with a newline,
// in case the Blaze server has written a partial line.
void SigPrintf(const char *format, ...) {
int stderr_fileno = _fileno(stderr);
char buf[1024];
va_list ap;
va_start(ap, format);
int r = vsnprintf(buf, sizeof buf, format, ap);
va_end(ap);
if (write(stderr_fileno, buf, r) <= 0) {
// We don't care, just placate the compiler.
}
}
static void PrintErrorW(const wstring& op) {
DWORD last_error = ::GetLastError();
if (last_error == 0) {
return;
}
WCHAR* message_buffer;
FormatMessageW(
/* dwFlags */ FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
/* lpSource */ nullptr,
/* dwMessageId */ last_error,
/* dwLanguageId */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
/* lpBuffer */ message_buffer,
/* nSize */ 0,
/* Arguments */ nullptr);
fwprintf(stderr, L"ERROR: %s: %s (%d)\n", op.c_str(), message_buffer,
last_error);
LocalFree(message_buffer);
}
void WarnFilesystemType(const string& output_base) {
}
string GetProcessIdAsString() {
return ToString(GetCurrentProcessId());
}
string GetSelfPath() {
WCHAR buffer[kWindowsPathBufferSize] = {0};
if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "GetSelfPath: GetModuleFileNameW: " << GetLastErrorString();
}
return string(blaze_util::WstringToCstring(buffer).get());
}
string GetOutputRoot() {
string home = GetHomeDir();
if (home.empty()) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Cannot find a good output root.\n"
"Set the USERPROFILE or the HOME environment variable.\n"
"Example (in cmd.exe):\n"
" set USERPROFILE=c:\\_bazel\\<YOUR-USERNAME>\n"
"or:\n"
" set HOME=c:\\_bazel\\<YOUR-USERNAME>";
}
return home;
}
string GetHomeDir() {
if (IsRunningWithinTest()) {
// Bazel is running inside of a test. Respect $HOME that the test setup has
// set instead of using the actual home directory of the current user.
return GetPathEnv("HOME");
}
PWSTR wpath;
// Look up the user's home directory. The default value of "FOLDERID_Profile"
// is the same as %USERPROFILE%, but it does not require the envvar to be set.
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL,
&wpath))) {
string result = string(blaze_util::WstringToCstring(wpath).get());
::CoTaskMemFree(wpath);
return result;
}
// On Windows 2016 Server, Nano server: FOLDERID_Profile is unknown but
// %USERPROFILE% is set. See https://github.com/bazelbuild/bazel/issues/6701
string userprofile = GetPathEnv("USERPROFILE");
if (!userprofile.empty()) {
return userprofile;
}
return GetPathEnv("HOME"); // only defined in MSYS/Cygwin
}
string FindSystemWideBlazerc() {
// TODO(bazel-team): figure out a good path to return here.
return "";
}
string GetJavaBinaryUnderJavabase() { return "bin/java.exe"; }
uint64_t GetMillisecondsMonotonic() {
return WindowsClock::INSTANCE.GetMilliseconds();
}
void SetScheduling(bool batch_cpu_scheduling, int io_nice_level) {
// TODO(bazel-team): There should be a similar function on Windows.
}
string GetProcessCWD(int pid) {
// TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and
// implement or delete.
return "";
}
bool IsSharedLibrary(const string &filename) {
return blaze_util::ends_with(filename, ".dll");
}
string GetSystemJavabase() {
string javahome(GetPathEnv("JAVA_HOME"));
if (!javahome.empty()) {
string javac = blaze_util::JoinPath(javahome, "bin/javac.exe");
if (blaze_util::PathExists(javac.c_str())) {
return javahome;
}
BAZEL_LOG(WARNING)
<< "Ignoring JAVA_HOME, because it must point to a JDK, not a JRE.";
}
return "";
}
namespace {
// Max command line length is per CreateProcess documentation
// (https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx)
//
// Quoting rules are described here:
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
static const int MAX_CMDLINE_LENGTH = 32768;
struct CmdLine {
char cmdline[MAX_CMDLINE_LENGTH];
};
static void CreateCommandLine(CmdLine* result, const string& exe,
const std::vector<string>& args_vector) {
std::ostringstream cmdline;
string short_exe;
string error;
if (!blaze_util::AsShortWindowsPath(exe, &short_exe, &error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "CreateCommandLine: AsShortWindowsPath(" << exe << "): " << error;
}
bool first = true;
for (const auto& s : args_vector) {
if (first) {
first = false;
// Skip first argument, instead use quoted executable name.
cmdline << '\"' << short_exe << '\"';
continue;
} else {
cmdline << ' ';
}
bool has_space = s.find(" ") != string::npos;
if (has_space) {
cmdline << '\"';
}
std::string::const_iterator it = s.begin();
while (it != s.end()) {
char ch = *it++;
switch (ch) {
case '"':
// Escape double quotes
cmdline << "\\\"";
break;
case '\\':
if (it == s.end()) {
// Backslashes at the end of the string are quoted if we add quotes
cmdline << (has_space ? "\\\\" : "\\");
} else {
// Backslashes everywhere else are quoted if they are followed by a
// quote or a backslash
cmdline << (*it == '"' || *it == '\\' ? "\\\\" : "\\");
}
break;
default:
cmdline << ch;
}
}
if (has_space) {
cmdline << '\"';
}
}
string cmdline_str = cmdline.str();
if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "Command line too long (" << cmdline_str.size() << " > "
<< MAX_CMDLINE_LENGTH << "): " << cmdline_str;
}
// Copy command line into a mutable buffer.
// CreateProcess is allowed to mutate its command line argument.
strncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1);
result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0;
}
} // namespace
static bool GetProcessStartupTime(HANDLE process, uint64_t* result) {
FILETIME creation_time, dummy1, dummy2, dummy3;
// GetProcessTimes cannot handle NULL arguments.
if (process == INVALID_HANDLE_VALUE ||
!::GetProcessTimes(process, &creation_time, &dummy1, &dummy2, &dummy3)) {
return false;
}
*result = static_cast<uint64_t>(creation_time.dwHighDateTime) << 32 |
creation_time.dwLowDateTime;
return true;
}
static void WriteProcessStartupTime(const string& server_dir, HANDLE process) {
uint64_t start_time = 0;
if (!GetProcessStartupTime(process, &start_time)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "WriteProcessStartupTime(" << server_dir
<< "): GetProcessStartupTime failed: " << GetLastErrorString();
}
string start_time_file = blaze_util::JoinPath(server_dir, "server.starttime");
if (!blaze_util::WriteFile(ToString(start_time), start_time_file)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "WriteProcessStartupTime(" << server_dir << "): WriteFile("
<< start_time_file << ") failed: " << GetLastErrorString();
}
}
static HANDLE CreateJvmOutputFile(const wstring& path, LPSECURITY_ATTRIBUTES sa,
bool daemon_out_append) {
// If the previous server process was asked to be shut down (but not killed),
// it takes a while for it to comply, so wait until the JVM output file that
// it held open is closed. There seems to be no better way to wait for a file
// to be closed on Windows.
static const unsigned int timeout_sec = 60;
for (unsigned int waited = 0; waited < timeout_sec; ++waited) {
HANDLE handle = ::CreateFileW(
/* lpFileName */ path.c_str(),
/* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE,
/* dwShareMode */ FILE_SHARE_READ,
/* lpSecurityAttributes */ sa,
/* dwCreationDisposition */
daemon_out_append ? OPEN_ALWAYS : CREATE_ALWAYS,
/* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
/* hTemplateFile */ NULL);
if (handle != INVALID_HANDLE_VALUE) {
if (daemon_out_append
&& !SetFilePointerEx(handle, {0}, NULL, FILE_END)) {
fprintf(stderr, "Could not seek to end of file (%ls)\n", path.c_str());
return INVALID_HANDLE_VALUE;
}
return handle;
}
if (GetLastError() != ERROR_SHARING_VIOLATION &&
GetLastError() != ERROR_LOCK_VIOLATION) {
// Some other error occurred than the file being open; bail out.
break;
}
// The file is still held open, the server is shutting down. There's a
// chance that another process holds it open, we don't know; in that case
// we just exit after the timeout expires.
if (waited == 5 || waited == 10 || waited == 30) {
fprintf(stderr,
"Waiting for previous Bazel server's log file to close "
"(waited %d seconds, waiting at most %d)\n",
waited, timeout_sec);
}
Sleep(1000);
}
return INVALID_HANDLE_VALUE;
}
class ProcessHandleBlazeServerStartup : public BlazeServerStartup {
public:
ProcessHandleBlazeServerStartup(HANDLE _proc) : proc(_proc) {}
bool IsStillAlive() override {
FILETIME dummy1, exit_time, dummy2, dummy3;
return GetProcessTimes(proc, &dummy1, &exit_time, &dummy2, &dummy3) &&
exit_time.dwHighDateTime == 0 && exit_time.dwLowDateTime == 0;
}
private:
AutoHandle proc;
};
int ExecuteDaemon(const string& exe,
const std::vector<string>& args_vector,
const std::map<string, EnvVarValue>& env,
const string& daemon_output,
const bool daemon_out_append,
const string& binaries_dir,
const string& server_dir,
const StartupOptions* options,
BlazeServerStartup** server_startup) {
wstring wdaemon_output;
string error;
if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output,
&error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): AsAbsoluteWindowsPath("
<< daemon_output << ") failed: " << error;
}
SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES),
NULL, TRUE};
AutoHandle devnull(::CreateFileW(
L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
&inheritable_handle_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
if (!devnull.IsValid()) {
error = GetLastErrorString();
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): CreateFileA(NUL) failed: " << error;
}
AutoHandle stdout_file(CreateJvmOutputFile(
wdaemon_output.c_str(), &inheritable_handle_sa, daemon_out_append));
if (!stdout_file.IsValid()) {
error = GetLastErrorString();
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): CreateJvmOutputFile("
<< blaze_util::WstringToString(wdaemon_output) << ") failed: " << error;
}
HANDLE stderr_handle;
// We must duplicate the handle to stdout, otherwise "bazel clean --expunge"
// won't work, because when it tries to close stdout then stderr, the former
// will succeed but the latter will appear to be valid yet still fail to
// close.
if (!DuplicateHandle(
/* hSourceProcessHandle */ GetCurrentProcess(),
/* hSourceHandle */ stdout_file,
/* hTargetProcessHandle */ GetCurrentProcess(),
/* lpTargetHandle */ &stderr_handle,
/* dwDesiredAccess */ 0,
/* bInheritHandle */ TRUE,
/* dwOptions */ DUPLICATE_SAME_ACCESS)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): DuplicateHandle("
<< blaze_util::WstringToString(wdaemon_output)
<< ") failed: " << GetLastErrorString();
}
AutoHandle stderr_file(stderr_handle);
// Create an attribute list.
wstring werror;
std::unique_ptr<AutoAttributeList> lpAttributeList;
if (!AutoAttributeList::Create(devnull, stdout_file, stderr_handle,
&lpAttributeList, &werror)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): attribute list creation failed: "
<< blaze_util::WstringToString(werror);
}
PROCESS_INFORMATION processInfo = {0};
STARTUPINFOEXA startupInfoEx = {0};
lpAttributeList->InitStartupInfoExA(&startupInfoEx);
CmdLine cmdline;
CreateCommandLine(&cmdline, exe, args_vector);
BOOL ok;
{
WithEnvVars env_obj(env);
ok = CreateProcessA(
/* lpApplicationName */ NULL,
/* lpCommandLine */ cmdline.cmdline,
/* lpProcessAttributes */ NULL,
/* lpThreadAttributes */ NULL,
/* bInheritHandles */ TRUE,
/* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP |
EXTENDED_STARTUPINFO_PRESENT,
/* lpEnvironment */ NULL,
/* lpCurrentDirectory */ NULL,
/* lpStartupInfo */ &startupInfoEx.StartupInfo,
/* lpProcessInformation */ &processInfo);
}
if (!ok) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteDaemon(" << exe << "): CreateProcess(" << cmdline.cmdline
<< ") failed: " << GetLastErrorString();
}
WriteProcessStartupTime(server_dir, processInfo.hProcess);
// Pass ownership of processInfo.hProcess
*server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess);
string pid_string = ToString(processInfo.dwProcessId);
string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile);
if (!blaze_util::WriteFile(pid_string, pid_file)) {
// Not a lot we can do if this fails
fprintf(stderr, "Cannot write PID file %s\n", pid_file.c_str());
}
// Don't close processInfo.hProcess here, it's now owned by the
// ProcessHandleBlazeServerStartup instance.
CloseHandle(processInfo.hThread);
return processInfo.dwProcessId;
}
// Returns whether nested jobs are not available on the current system.
static bool NestedJobsSupported() {
// Nested jobs are supported from Windows 8
return IsWindows8OrGreater();
}
// Run the given program in the current working directory, using the given
// argument vector, wait for it to finish, then exit ourselves with the exitcode
// of that program.
void ExecuteProgram(const string& exe, const std::vector<string>& args_vector) {
CmdLine cmdline;
CreateCommandLine(&cmdline, exe, args_vector);
STARTUPINFOA startupInfo = {0};
startupInfo.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION processInfo = {0};
HANDLE job = INVALID_HANDLE_VALUE;
if (NestedJobsSupported()) {
job = CreateJobObject(NULL, NULL);
if (job == NULL) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteProgram(" << exe
<< "): CreateJobObject failed: " << GetLastErrorString();
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0};
job_info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation,
&job_info, sizeof(job_info))) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteProgram(" << exe
<< "): SetInformationJobObject failed: " << GetLastErrorString();
}
}
BOOL success = CreateProcessA(
/* lpApplicationName */ NULL,
/* lpCommandLine */ cmdline.cmdline,
/* lpProcessAttributes */ NULL,
/* lpThreadAttributes */ NULL,
/* bInheritHandles */ TRUE,
/* dwCreationFlags */ CREATE_SUSPENDED,
/* lpEnvironment */ NULL,
/* lpCurrentDirectory */ NULL,
/* lpStartupInfo */ &startupInfo,
/* lpProcessInformation */ &processInfo);
if (!success) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteProgram(" << exe << "): CreateProcess(" << cmdline.cmdline
<< ") failed: " << GetLastErrorString();
}
// On Windows versions that support nested jobs (Windows 8 and above), we
// assign the Bazel server to a job object. Every process that Bazel creates,
// as well as all their child processes, will be assigned to this job object.
// When the Bazel server terminates the OS can reliably kill the entire
// process tree under it. On Windows versions that don't support nested jobs
// (Windows 7), we don't assign the Bazel server to a big job object. Instead,
// when Bazel creates new processes, it does so using the JNI library. The
// library assigns individual job objects to each subprocess. This way when
// these processes terminate, the OS can kill all their subprocesses. Bazel's
// own subprocesses are not in a job object though, so we only create
// subprocesses via the JNI library.
if (job != INVALID_HANDLE_VALUE) {
if (!AssignProcessToJobObject(job, processInfo.hProcess)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteProgram(" << exe
<< "): AssignProcessToJobObject failed: " << GetLastErrorString();
}
}
// Now that we potentially put the process into a new job object, we can start
// running it.
if (ResumeThread(processInfo.hThread) == -1) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "ExecuteProgram(" << exe
<< "): ResumeThread failed: " << GetLastErrorString();
}
WaitForSingleObject(processInfo.hProcess, INFINITE);
DWORD exit_code;
GetExitCodeProcess(processInfo.hProcess, &exit_code);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
exit(exit_code);
}
const char kListSeparator = ';';
bool SymlinkDirectories(const string &posix_target, const string &posix_name) {
wstring name;
wstring target;
string error;
if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name, &error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "SymlinkDirectories(" << posix_target << ", " << posix_name
<< "): AsAbsoluteWindowsPath(" << posix_target << ") failed: " << error;
return false;
}
if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target, &error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "SymlinkDirectories(" << posix_target << ", " << posix_name
<< "): AsAbsoluteWindowsPath(" << posix_name << ") failed: " << error;
return false;
}
wstring werror;
if (CreateJunction(name, target, &werror) != CreateJunctionResult::kSuccess) {
string error(blaze_util::WstringToCstring(werror.c_str()).get());
BAZEL_LOG(ERROR) << "SymlinkDirectories(" << posix_target << ", "
<< posix_name << "): CreateJunction: " << error;
return false;
}
return true;
}
#ifndef STILL_ACTIVE
#define STILL_ACTIVE (259) // From MSDN about GetExitCodeProcess.
#endif
// On Windows (and Linux) we use a combination of PID and start time to identify
// the server process. That is supposed to be unique unless one can start more
// processes than there are PIDs available within a single jiffy.
bool VerifyServerProcess(int pid, const string& output_base) {
AutoHandle process(
::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
if (!process.IsValid()) {
// Cannot find the server process. Can happen if the PID file is stale.
return false;
}
DWORD exit_code = 0;
uint64_t start_time = 0;
if (!::GetExitCodeProcess(process, &exit_code) || exit_code != STILL_ACTIVE ||
!GetProcessStartupTime(process, &start_time)) {
// Process doesn't exist or died meantime, all is good. No stale server is
// present.
return false;
}
string recorded_start_time;
bool file_present = blaze_util::ReadFile(
blaze_util::JoinPath(output_base, "server/server.starttime"),
&recorded_start_time);
// If start time file got deleted, but PID file didn't, assume that this is an
// old Bazel process that doesn't know how to write start time files yet.
return !file_present || recorded_start_time == ToString(start_time);
}
bool KillServerProcess(int pid, const string& output_base) {
AutoHandle process(::OpenProcess(
PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
DWORD exitcode = 0;
if (!process.IsValid() || !::GetExitCodeProcess(process, &exitcode) ||
exitcode != STILL_ACTIVE) {
// Cannot find the server process (can happen if the PID file is stale) or
// it already exited.
return false;
}
BOOL result = TerminateProcess(process, /*uExitCode*/ 0);
if (!result || !AwaitServerProcessTermination(pid, output_base,
kPostKillGracePeriodSeconds)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Cannot terminate server process with PID " << pid
<< ", output_base=(" << output_base << "): " << GetLastErrorString();
}
return result;
}
void TrySleep(unsigned int milliseconds) {
Sleep(milliseconds);
}
// Not supported.
void ExcludePathFromBackup(const string &path) {
}
string GetHashedBaseDir(const string& root, const string& hashable) {
// Builds a shorter output base dir name for Windows.
// We create a path name representing the 128 bits of MD5 digest. To avoid
// platform incompatibilities we restrict the alphabet to ASCII letters and
// numbers. Windows paths are case-insensitive, so use only lower-case
// letters. These constraints yield a 5-bit alphabet.
// Since we only need 6 digits, ignore 0 and 1 because they look like
// upper-case "O" and lower-case "l".
static const char* alphabet = "abcdefghijklmnopqrstuvwxyz234567";
// 128 bits of data in base-32 require 128/5 = 25 digits with 3 bits lost.
// Maximum path length on Windows is only 259 characters, so we'll only use
// a few characters characters (base-32 digits) to represent the digest.
// Using only 8 characters we represent 40 bits of the original 128.
// Since the mapping is lossy and collisions are unlikely in practice, we'll
// keep the mapping simple and just use the lower 5 bits of the first 8 bytes.
static const unsigned char kLower5BitsMask = 0x1F;
static const int filename_length = 8;
unsigned char md5[blaze_util::Md5Digest::kDigestLength];
char coded_name[filename_length + 1];
blaze_util::Md5Digest digest;
digest.Update(hashable.data(), hashable.size());
digest.Finish(md5);
for (int i = 0; i < filename_length; ++i) {
coded_name[i] = alphabet[md5[i] & kLower5BitsMask];
}
coded_name[filename_length] = '\0';
return blaze_util::JoinPath(root, string(coded_name));
}
void CreateSecureOutputRoot(const string& path) {
// TODO(bazel-team): implement this properly, by mimicing whatever the POSIX
// implementation does.
const char* root = path.c_str();
if (!blaze_util::MakeDirectories(path, 0755)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "MakeDirectories(" << root << ") failed: " << GetLastErrorString();
}
if (!blaze_util::IsDirectory(path)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "'" << root << "' is not a directory";
}
ExcludePathFromBackup(root);
}
string GetEnv(const string& name) {
DWORD size = ::GetEnvironmentVariableA(name.c_str(), NULL, 0);
if (size == 0) {
return string(); // unset or empty envvar
}
unique_ptr<char[]> value(new char[size]);
::GetEnvironmentVariableA(name.c_str(), value.get(), size);
return string(value.get());
}
string GetPathEnv(const string& name) {
string value = GetEnv(name);
if (value.empty()) {
return value;
}
if (bazel::windows::HasUncPrefix(value.c_str())) {
value = value.substr(4);
}
string wpath, error;
if (!blaze_util::AsWindowsPath(value, &wpath, &error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "Invalid path in envvar \"" << name << "\": " << error;
}
// Callers of GetPathEnv expect a path with forward slashes.
std::replace(wpath.begin(), wpath.end(), '\\', '/');
return wpath;
}
bool ExistsEnv(const string& name) {
return ::GetEnvironmentVariableA(name.c_str(), NULL, 0) != 0;
}
void SetEnv(const string& name, const string& value) {
// _putenv_s both calls ::SetEnvionmentVariableA and updates environ(5).
_putenv_s(name.c_str(), value.c_str());
}
void UnsetEnv(const string& name) { SetEnv(name, ""); }
bool WarnIfStartedFromDesktop() {
// GetConsoleProcessList returns:
// 0, if no console attached (Bazel runs as a subprocess)
// 1, if Bazel was started by clicking on its icon
// 2, if Bazel was started from the command line (even if its output is
// redirected)
DWORD dummy[2] = {0};
if (GetConsoleProcessList(dummy, 2) != 1) {
return false;
}
printf(
"Bazel is a command line tool.\n\n"
"Try opening a console, such as the Windows Command Prompt (cmd.exe) "
"or PowerShell, and running \"bazel help\".\n\n"
"Press Enter to close this window...");
ReadFile(GetStdHandle(STD_INPUT_HANDLE), dummy, 1, dummy, NULL);
return true;
}
#ifndef ENABLE_PROCESSED_OUTPUT
// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
#define ENABLE_PROCESSED_OUTPUT 0x0001
#endif // not ENABLE_PROCESSED_OUTPUT
#ifndef ENABLE_WRAP_AT_EOL_OUTPUT
// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
#define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002
#endif // not ENABLE_WRAP_AT_EOL_OUTPUT
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
// From MSDN about BOOL SetConsoleMode(HANDLE, DWORD).
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif // not ENABLE_VIRTUAL_TERMINAL_PROCESSING
void SetupStdStreams() {
static const DWORD stdhandles[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
STD_ERROR_HANDLE};
for (int i = 0; i <= 2; ++i) {
HANDLE handle = ::GetStdHandle(stdhandles[i]);
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
// Ensure we have open fds to each std* stream. Otherwise we can end up
// with bizarre things like stdout going to the lock file, etc.
_open("NUL", (i == 0) ? _O_RDONLY : _O_WRONLY);
}
DWORD mode = 0;
if (i > 0 && handle != INVALID_HANDLE_VALUE && handle != NULL &&
::GetConsoleMode(handle, &mode)) {
DWORD newmode = mode | ENABLE_PROCESSED_OUTPUT |
ENABLE_WRAP_AT_EOL_OUTPUT |
ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (mode != newmode) {
// We don't care about the success of this. Worst that can happen if
// this method fails is that the console won't understand control
// characters like color change or carriage return.
::SetConsoleMode(handle, newmode);
}
}
}
}
LARGE_INTEGER WindowsClock::GetFrequency() {
LARGE_INTEGER result;
if (!QueryPerformanceFrequency(&result)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "WindowsClock::GetFrequency: QueryPerformanceFrequency failed: "
<< GetLastErrorString();
}
// On ancient Windows versions (pre-XP) and specific hardware the result may
// be 0. Since this is pre-XP, we don't handle that, just error out.
if (result.QuadPart <= 0) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "WindowsClock::GetFrequency: QueryPerformanceFrequency returned "
"invalid result ("
<< result.QuadPart << "): " << GetLastErrorString();
}
return result;
}
LARGE_INTEGER WindowsClock::GetMillisecondsAsLargeInt(
const LARGE_INTEGER& freq) {
LARGE_INTEGER counter;
if (!QueryPerformanceCounter(&counter)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "WindowsClock::GetMillisecondsAsLargeInt: QueryPerformanceCounter "
"failed: "
<< GetLastErrorString();
}
LARGE_INTEGER result;
result.QuadPart =
// seconds
(counter.QuadPart / freq.QuadPart) * 1000LL +
// milliseconds
(((counter.QuadPart % freq.QuadPart) * 1000LL) / freq.QuadPart);
return result;
}
const WindowsClock WindowsClock::INSTANCE;
WindowsClock::WindowsClock()
: kFrequency(GetFrequency()) {}
uint64_t WindowsClock::GetMilliseconds() const {
return GetMillisecondsAsLargeInt(kFrequency).QuadPart;
}
uint64_t AcquireLock(const string& output_base, bool batch_mode, bool block,
BlazeLock* blaze_lock) {
string lockfile = blaze_util::JoinPath(output_base, "lock");
wstring wlockfile;
string error;
if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile, &error)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "AcquireLock(" << output_base << "): AsAbsoluteWindowsPath("
<< lockfile << ") failed: " << error;
}
blaze_lock->handle = INVALID_HANDLE_VALUE;
bool first_lock_attempt = true;
uint64_t st = GetMillisecondsMonotonic();
while (true) {
blaze_lock->handle = ::CreateFileW(
/* lpFileName */ wlockfile.c_str(),
/* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE,
/* dwShareMode */ FILE_SHARE_READ,
/* lpSecurityAttributes */ NULL,
/* dwCreationDisposition */ CREATE_ALWAYS,
/* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
/* hTemplateFile */ NULL);
if (blaze_lock->handle != INVALID_HANDLE_VALUE) {
// We could open the file, so noone else holds a lock on it.
break;
}
if (GetLastError() == ERROR_SHARING_VIOLATION) {
// Someone else has the lock.
BAZEL_LOG(USER) << "Another command holds the client lock";
if (!block) {
BAZEL_DIE(blaze_exit_code::LOCK_HELD_NOBLOCK_FOR_LOCK)
<< "Exiting because the lock is held and --noblock_for_lock was "
"given.";
}
if (first_lock_attempt) {
first_lock_attempt = false;
BAZEL_LOG(USER) << "Waiting for it to complete...";
fflush(stderr);
}
Sleep(/* dwMilliseconds */ 200);
} else {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "AcquireLock(" << lockfile << "): CreateFileW("
<< blaze_util::WstringToString(wlockfile)
<< ") failed: " << GetLastErrorString();
}
}
uint64_t wait_time = GetMillisecondsMonotonic() - st;
// We have the lock.
OVERLAPPED overlapped = {0};
if (!LockFileEx(
/* hFile */ blaze_lock->handle,
/* dwFlags */ LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
/* dwReserved */ 0,
/* nNumberOfBytesToLockLow */ 1,
/* nNumberOfBytesToLockHigh */ 0,
/* lpOverlapped */ &overlapped)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "AcquireLock(" << lockfile << "): LockFileEx("
<< blaze_util::WstringToString(wlockfile)
<< ") failed: " << GetLastErrorString();
}
// On other platforms we write some info about this process into the lock file
// such as the server PID. On Windows we don't do that because the file is
// locked exclusively, meaning other processes may not open the file even for
// reading.
return wait_time;
}
void ReleaseLock(BlazeLock* blaze_lock) {
OVERLAPPED overlapped = {0};
UnlockFileEx(blaze_lock->handle, 0, 1, 0, &overlapped);
CloseHandle(blaze_lock->handle);
}
#ifdef GetUserName
// By including <windows.h>, we have GetUserName defined either as
// GetUserNameA or GetUserNameW.
#undef GetUserName
#endif
string GetUserName() {
WCHAR buffer[UNLEN + 1];
DWORD len = UNLEN + 1;
if (!::GetUserNameW(buffer, &len)) {
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "GetUserNameW failed: " << GetLastErrorString();
}
return string(blaze_util::WstringToCstring(buffer).get());
}
bool IsEmacsTerminal() {
string emacs = GetEnv("EMACS");
// GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't
// do that and instead sets INSIDE_EMACS=<stuff> (where <stuff> can look like
// e.g. "25.1.1,comint"). So we check both variables for maximum
// compatibility.
return emacs == "t" || ExistsEnv("INSIDE_EMACS");
}
// Returns true if stderr is connected to a terminal, and it can support color
// and cursor movement (this is computed heuristically based on the values of
// environment variables). Blaze only outputs control characters to stderr,
// so we only care for the stderr descriptor type.
bool IsStderrStandardTerminal() {
DWORD mode = 0;
HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE);
// handle may be invalid when stderr is redirected
if (handle == INVALID_HANDLE_VALUE || !::GetConsoleMode(handle, &mode) ||
!(mode & ENABLE_PROCESSED_OUTPUT) ||
!(mode & ENABLE_WRAP_AT_EOL_OUTPUT) ||
!(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
return false;
}
return true;
}
// Returns the number of columns of the terminal to which stderr is connected,
// or $COLUMNS (default 80) if there is no such terminal. Blaze only outputs
// formatted messages to stderr, so we only care for width of a terminal
// connected to the stderr descriptor.
int GetStderrTerminalColumns() {
string columns_env = GetEnv("COLUMNS");
if (!columns_env.empty()) {
char* endptr;
int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10);
if (*endptr == '\0') { // $COLUMNS is a valid number
return columns;
}
}
HANDLE stdout_handle = ::GetStdHandle(STD_ERROR_HANDLE);
if (stdout_handle != INVALID_HANDLE_VALUE) {
// stdout_handle may be invalid when stdout is redirected.
CONSOLE_SCREEN_BUFFER_INFO screen_info;
if (GetConsoleScreenBufferInfo(stdout_handle, &screen_info)) {
int width = 1 + screen_info.srWindow.Right - screen_info.srWindow.Left;
if (width > 1) {
return width;
}
}
}
return 80; // default if not a terminal.
}
bool UnlimitResources() {
return true; // Nothing to do so assume success.
}
bool UnlimitCoredumps() {
return true; // Nothing to do so assume success.
}
static const int MAX_KEY_LENGTH = 255;
// We do not care about registry values longer than MAX_PATH
static const int REG_VALUE_BUFFER_SIZE = MAX_PATH;
// Implements heuristics to discover msys2 installation.
static string GetMsysBash() {
HKEY h_uninstall;
// MSYS2 installer writes its registry into HKCU, although documentation
// (https://msdn.microsoft.com/en-us/library/ms954376.aspx)
// clearly states that it should go to HKLM.
static constexpr const char key[] =
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
if (RegOpenKeyExA(HKEY_CURRENT_USER, // _In_ HKEY hKey,
key, // _In_opt_ LPCTSTR lpSubKey,
0, // _In_ DWORD ulOptions,
KEY_ENUMERATE_SUB_KEYS |
KEY_QUERY_VALUE, // _In_ REGSAM samDesired,
&h_uninstall // _Out_ PHKEY phkResult
)) {
BAZEL_LOG(INFO) << "Cannot open HKCU\\" << key;
return string();
}
AutoHandle auto_uninstall(h_uninstall);
// Since MSYS2 decided to generate a new product key for each installation,
// we enumerate all keys under
// HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall and find the first
// with MSYS2 64bit display name.
static constexpr const char msys_display_name[] = "MSYS2 64bit";
DWORD n_subkeys;
if (RegQueryInfoKey(h_uninstall, // _In_ HKEY hKey,
0, // _Out_opt_ LPTSTR lpClass,
0, // _Inout_opt_ LPDWORD lpcClass,
0, // _Reserved_ LPDWORD lpReserved,
&n_subkeys, // _Out_opt_ LPDWORD lpcSubKeys,
0, // _Out_opt_ LPDWORD lpcMaxSubKeyLen,
0, // _Out_opt_ LPDWORD lpcMaxClassLen,
0, // _Out_opt_ LPDWORD lpcValues,
0, // _Out_opt_ LPDWORD lpcMaxValueNameLen,
0, // _Out_opt_ LPDWORD lpcMaxValueLen,
0, // _Out_opt_ LPDWORD lpcbSecurityDescriptor,
0 // _Out_opt_ PFILETIME lpftLastWriteTime
)) {
BAZEL_LOG(INFO) << "Cannot query HKCU\\" << key;
return string();
}
for (DWORD key_index = 0; key_index < n_subkeys; key_index++) {
char subkey_name[MAX_KEY_LENGTH];
if (RegEnumKeyA(h_uninstall, // _In_ HKEY hKey,
key_index, // _In_ DWORD dwIndex,
subkey_name, // _Out_ LPTSTR lpName,
sizeof(subkey_name) // _In_ DWORD cchName
)) {
BAZEL_LOG(INFO) << "Cannot get " << key_index << " subkey of HKCU\\"
<< key;
continue; // try next subkey
}
HKEY h_subkey;
if (RegOpenKeyEx(h_uninstall, // _In_ HKEY hKey,
subkey_name, // _In_opt_ LPCTSTR lpSubKey,
0, // _In_ DWORD ulOptions,
KEY_QUERY_VALUE, // _In_ REGSAM samDesired,
&h_subkey // _Out_ PHKEY phkResult
)) {
BAZEL_LOG(ERROR) << "Failed to open subkey HKCU\\" << key << "\\"
<< subkey_name;
continue; // try next subkey
}
AutoHandle auto_subkey(h_subkey);
BYTE value[REG_VALUE_BUFFER_SIZE];
DWORD value_length = sizeof(value);
DWORD value_type;
if (RegQueryValueEx(h_subkey, // _In_ HKEY hKey,
"DisplayName", // _In_opt_ LPCTSTR lpValueName,
0, // _Reserved_ LPDWORD lpReserved,
&value_type, // _Out_opt_ LPDWORD lpType,
value, // _Out_opt_ LPBYTE lpData,
&value_length // _Inout_opt_ LPDWORD lpcbData
)) {
// This registry key has no DisplayName subkey, so it cannot be MSYS2, or
// it cannot be a version of MSYS2 that we are looking for.
continue;
}
if (value_type == REG_SZ &&
0 == memcmp(msys_display_name, value, sizeof(msys_display_name))) {
BAZEL_LOG(INFO) << "Getting install location of HKCU\\" << key << "\\"
<< subkey_name;
BYTE path[REG_VALUE_BUFFER_SIZE];
DWORD path_length = sizeof(path);
DWORD path_type;
if (RegQueryValueEx(
h_subkey, // _In_ HKEY hKey,
"InstallLocation", // _In_opt_ LPCTSTR lpValueName,
0, // _Reserved_ LPDWORD lpReserved,
&path_type, // _Out_opt_ LPDWORD lpType,
path, // _Out_opt_ LPBYTE lpData,
&path_length // _Inout_opt_ LPDWORD lpcbData
)) {
// This version of MSYS2 does not seem to create a "InstallLocation"
// subkey. Let's ignore this registry key to avoid picking up an MSYS2
// version that may be different from what Bazel expects.
continue; // try next subkey
}
if (path_length == 0 || path_type != REG_SZ) {
// This version of MSYS2 seem to have recorded an empty installation
// location, or the registry key was modified. Either way, let's ignore
// this registry key and keep looking at the next subkey.
continue;
}
BAZEL_LOG(INFO) << "Install location of HKCU\\" << key << "\\"
<< subkey_name << " is " << path;
string path_as_string(path, path + path_length - 1);
string bash_exe = path_as_string + "\\usr\\bin\\bash.exe";
if (!blaze_util::PathExists(bash_exe)) {
// The supposed bash.exe does not exist. Maybe MSYS2 was deleted but not
// uninstalled? We can't tell, but for all we care, this MSYS2 path is
// not what we need, so ignore this registry key.
continue;
}
BAZEL_LOG(INFO) << "Detected MSYS2 Bash at " << bash_exe.c_str();
return bash_exe;
}
}
return string();
}
static string GetBinaryFromPath(const string& binary_name) {
char found[MAX_PATH];
string path_list = blaze::GetPathEnv("PATH");
// We do not fully replicate all the quirks of search in PATH.
// There is no system function to do so, and that way lies madness.
size_t start = 0;
do {
// This ignores possibly quoted semicolons in PATH etc.
size_t end = path_list.find_first_of(";", start);
string path = path_list.substr(
start, end != string::npos ? end - start : string::npos);
// Handle one typical way of quoting (where.exe does not handle this, but
// CreateProcess does).
if (path.size() > 1 && path[0] == '"' && path[path.size() - 1] == '"') {
path = path.substr(1, path.size() - 2);
}
if (SearchPathA(path.c_str(), // _In_opt_ LPCTSTR lpPath,
binary_name.c_str(), // _In_ LPCTSTR lpFileName,
0, // LPCTSTR lpExtension,
sizeof(found), // DWORD nBufferLength,
found, // _Out_ LPTSTR lpBuffer,
0 // _Out_opt_ LPTSTR *lpFilePart
)) {
BAZEL_LOG(INFO) << binary_name.c_str() << " found on PATH: " << found;
return string(found);
}
if (end == string::npos) {
break;
}
start = end + 1;
} while (true);
return string();
}
static string LocateBashMaybe() {
string msys_bash = GetMsysBash();
return msys_bash.empty() ? GetBinaryFromPath("bash.exe") : msys_bash;
}
string DetectBashAndExportBazelSh() {
string bash = blaze::GetPathEnv("BAZEL_SH");
if (!bash.empty()) {
return bash;
}
uint64_t start = blaze::GetMillisecondsMonotonic();
bash = LocateBashMaybe();
uint64_t end = blaze::GetMillisecondsMonotonic();
if (bash.empty()) {
BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start
<< " msec, not found";
} else {
BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start
<< " msec, found " << bash.c_str();
// Set process environment variable.
blaze::SetEnv("BAZEL_SH", bash);
}
return bash;
}
void EnsurePythonPathOption(std::vector<string>* options) {
string python_path = GetBinaryFromPath("python.exe");
if (!python_path.empty()) {
// Provide python path as coming from the least important rc file.
std::replace(python_path.begin(), python_path.end(), '\\', '/');
options->push_back(string("--default_override=0:build=--python_path=") +
python_path);
}
}
} // namespace blaze