// 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 #include // va_start, va_end, va_list #include #include // UNLEN #include // IsWindows8OrGreater #include // _open #include // FOLDERID_Profile #include // CoTaskMemFree #include // SHGetKnownFolderPath #include #include #include #include #include // NOLINT #include #include #include // NOLINT (to silence Google-internal linter) #include // static_assert #include #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), was_error_(false) {} PTP_POOL threadpool_; PTP_CLEANUP_GROUP cleanup_group_; TP_CALLBACK_ENVIRON threadpool_env_; std::mutex dir_cache_lock_; std::set dir_cache_; std::atomic_bool was_error_; string error_msg_; }; namespace { class DumpContext { public: DumpContext(unique_ptr data, const size_t size, const string path, std::mutex* dir_cache_lock, std::set* dir_cache, std::atomic_bool* was_error, string* error_msg); void Run(); private: void MaybeSignalError(const string& msg); unique_ptr data_; const size_t size_; const string path_; std::mutex* dir_cache_lock_; std::set* dir_cache_; std::atomic_bool* was_error_; 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 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) { if (was_error_) { return; } unique_ptr data_copy(new uint8_t[size]); memcpy(data_copy.get(), data, size); unique_ptr ctx(new DumpContext(std::move(data_copy), size, path, &dir_cache_lock_, &dir_cache_, &was_error_, &error_msg_)); PTP_WORK w = CreateThreadpoolWork(WorkCallback, ctx.get(), &threadpool_env_); if (w == NULL) { string err = GetLastErrorString(); if (!was_error_.exchange(true)) { // Benign race condition: though we use no locks to access `error_msg_`, // only one thread may ever flip `was_error_` from false to true and enter // the body of this if-clause. Since `was_error_` is the same object as // used by all other threads trying to write to `error_msg_` (see // DumpContext::MaybeSignalError), using it provides adequate mutual // exclusion to write `error_msg_`. error_msg_ = string("WindowsDumper::Dump() couldn't submit work: ") + 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; if (was_error_ && error) { // No race condition reading `error_msg_`: all worker threads terminated // by now. *error = error_msg_; } return !was_error_; } namespace { DumpContext::DumpContext(unique_ptr data, const size_t size, const string path, std::mutex* dir_cache_lock, std::set* dir_cache, std::atomic_bool* was_error, string* error_msg) : data_(std::move(data)), size_(size), path_(path), dir_cache_lock_(dir_cache_lock), dir_cache_(dir_cache), was_error_(was_error), 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 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) { if (!was_error_->exchange(true)) { // Benign race condition: though we use no locks to access `error_msg_`, // only one thread may ever flip `was_error_` from false to true and enter // the body of this if-clause. Since `was_error_` is the same object as used // by all other threads and by WindowsDumper::Dump(), using it provides // adequate mutual exclusion to write `error_msg_`. *error_msg_ = msg; } } VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work) { unique_ptr ctx(reinterpret_cast(Context)); ctx->Run(); } } // namespace } // namespace embedded_binaries SignalHandler SignalHandler::INSTANCE; class WindowsClock { public: uint64_t GetMilliseconds() const; uint64_t GetProcessMilliseconds() 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; // Time (in milliseconds) at process start. const LARGE_INTEGER kStart; 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. Use the --output_user_root flag."; } return home; } string GetHomeDir() { PWSTR wpath; if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, &wpath))) { string result = string(blaze_util::WstringToCstring(wpath).get()); ::CoTaskMemFree(wpath); return result; } return GetEnv("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(); } uint64_t GetMillisecondsSinceProcessStart() { return WindowsClock::INSTANCE.GetProcessMilliseconds(); } 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(GetEnv("JAVA_HOME")); if (javahome.empty()) { return ""; } return javahome; } 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& 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(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, SECURITY_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& args_vector, const std::map& env, const string& daemon_output, const bool daemon_out_append, const string& server_dir, 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 sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); // We redirect stdin to the NUL device, and redirect stdout and stderr to // `stdout_file` and `stderr_file` (opened below) by telling CreateProcess to // use these file handles, so they must be inheritable. sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; AutoHandle devnull(::CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); if (!devnull.IsValid()) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe << "): CreateFileA(NUL) failed: " << GetLastErrorString(); } AutoHandle stdout_file(CreateJvmOutputFile(wdaemon_output.c_str(), &sa, daemon_out_append)); if (!stdout_file.IsValid()) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe << "): CreateJvmOutputFile(" << blaze_util::WstringToString(wdaemon_output) << ") failed: " << GetLastErrorString(); } 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 with length of 1 AutoAttributeList lpAttributeList(1); HANDLE handlesToInherit[2] = {stdout_file, stderr_handle}; if (!UpdateProcThreadAttribute( lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handlesToInherit, 2 * sizeof(HANDLE), NULL, NULL)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe << "): UpdateProcThreadAttribute failed: " << GetLastErrorString(); } PROCESS_INFORMATION processInfo = {0}; STARTUPINFOEXA startupInfoEx = {0}; startupInfoEx.StartupInfo.cb = sizeof(startupInfoEx); startupInfoEx.StartupInfo.hStdInput = devnull; startupInfoEx.StartupInfo.hStdOutput = stdout_file; startupInfoEx.StartupInfo.hStdError = stderr_handle; startupInfoEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; startupInfoEx.lpAttributeList = lpAttributeList; 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& 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 value(new char[size]); ::GetEnvironmentVariableA(name.c_str(), value.get(), size); return string(value.get()); } 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()), kStart(GetMillisecondsAsLargeInt(kFrequency)) {} uint64_t WindowsClock::GetMilliseconds() const { return GetMillisecondsAsLargeInt(kFrequency).QuadPart; } uint64_t WindowsClock::GetProcessMilliseconds() const { return GetMilliseconds() - kStart.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 , 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"); string inside_emacs = GetEnv("INSIDE_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= (where can look like // e.g. "25.1.1,comint"). So we check both variables for maximum // compatibility. return emacs == "t" || !inside_emacs.empty(); } // Returns true iff both stdout and stderr are connected to a // terminal, and it can support color and cursor movement // (this is computed heuristically based on the values of // environment variables). bool IsStandardTerminal() { for (DWORD i : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}) { DWORD mode = 0; HANDLE handle = ::GetStdHandle(i); // handle may be invalid when std{out,err} 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 stdout is // connected, or $COLUMNS (default 80) if there is no such terminal. int GetTerminalColumns() { 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_OUTPUT_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. } 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 )) { BAZEL_LOG(ERROR) << "Failed to query DisplayName of HKCU\\" << key << "\\" << subkey_name; continue; // try next subkey } 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 )) { BAZEL_LOG(ERROR) << "Failed to query InstallLocation of HKCU\\" << key << "\\" << subkey_name; continue; // try next subkey } if (path_length == 0 || path_type != REG_SZ) { BAZEL_LOG(ERROR) << "Zero-length (" << path_length << ") install location or wrong type (" << path_type << ")"; continue; // try next subkey } 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)) { BAZEL_LOG(INFO) << bash_exe.c_str() << " does not exist"; continue; // try next subkey } BAZEL_LOG(INFO) << "Detected msys 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::GetEnv("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); BAZEL_LOG(ERROR) << binary_name.c_str() << " not found on PATH"; return string(); } static string LocateBash() { string msys_bash = GetMsysBash(); if (!msys_bash.empty()) { return msys_bash; } return GetBinaryFromPath("bash.exe"); } void DetectBashOrDie() { if (!blaze::GetEnv("BAZEL_SH").empty()) return; uint64_t start = blaze::GetMillisecondsMonotonic(); string bash = LocateBash(); uint64_t end = blaze::GetMillisecondsMonotonic(); BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start << " msec, found " << bash.c_str(); if (!bash.empty()) { // Set process environment variable. blaze::SetEnv("BAZEL_SH", bash); } else { // TODO(bazel-team) should this be printed to stderr? If so, it should use // BAZEL_LOG(ERROR) printf( "Bazel on Windows requires MSYS2 Bash, but we could not find it.\n" "If you do not have it installed, you can install MSYS2 from\n" " http://repo.msys2.org/distrib/msys2-x86_64-latest.exe\n" "\n" "If you already have it installed but Bazel cannot find it,\n" "set BAZEL_SH environment variable to its location:\n" " set BAZEL_SH=c:\\path\\to\\msys2\\usr\\bin\\bash.exe\n"); exit(1); } } void EnsurePythonPathOption(std::vector* 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