// 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. #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include // _open #include // FOLDERID_Profile #include // UNLEN #include // CoTaskMemFree #include // SHGetKnownFolderPath #include // va_start, va_end, va_list #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/blaze_util_platform.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/process.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_(nullptr), cleanup_group_(nullptr) {} PTP_POOL threadpool_; PTP_CLEANUP_GROUP cleanup_group_; TP_CALLBACK_ENVIRON threadpool_env_; std::mutex dir_cache_lock_; std::set dir_cache_; std::mutex error_lock_; 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::mutex* error_lock_, 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::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 result(new WindowsDumper()); result->threadpool_ = CreateThreadpool(nullptr); if (result->threadpool_ == nullptr) { if (error) { string msg = GetLastErrorString(); *error = "CreateThreadpool failed: " + msg; } return nullptr; } result->cleanup_group_ = CreateThreadpoolCleanupGroup(); if (result->cleanup_group_ == nullptr) { 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_, nullptr); return result.release(); // release pointer ownership } void WindowsDumper::Dump(const void* data, const size_t size, const string& path) { { std::lock_guard g(error_lock_); if (!error_msg_.empty()) { 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_, &error_lock_, &error_msg_)); PTP_WORK w = CreateThreadpoolWork(WorkCallback, ctx.get(), &threadpool_env_); if (w == nullptr) { string err = GetLastErrorString(); err = string("WindowsDumper::Dump() couldn't submit work: ") + err; std::lock_guard g(error_lock_); error_msg_ = err; } else { ctx.release(); // release pointer ownership SubmitThreadpoolWork(w); } } bool WindowsDumper::Finish(string* error) { if (threadpool_ == nullptr) { return true; } CloseThreadpoolCleanupGroupMembers(cleanup_group_, FALSE, nullptr); CloseThreadpoolCleanupGroup(cleanup_group_); CloseThreadpool(threadpool_); threadpool_ = nullptr; cleanup_group_ = nullptr; std::lock_guard g(error_lock_); if (!error_msg_.empty() && error) { *error = error_msg_; } return error_msg_.empty(); } namespace { DumpContext::DumpContext(unique_ptr data, const size_t size, const string path, std::mutex* dir_cache_lock, std::set* 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 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 g(*error_lock_); *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; 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().GetProductName().c_str()); if (SignalHandler::Get().GetServerProcessInfo()->server_pid_ != -1) { KillServerProcess( SignalHandler::Get().GetServerProcessInfo()->server_pid_, SignalHandler::Get().GetOutputBase()); } _exit(1); } SigPrintf( "\n%s Ctrl+C handler; shutting down.\n\n", SignalHandler::Get().GetProductName().c_str()); SignalHandler::Get().CancelServer(); return TRUE; case CTRL_CLOSE_EVENT: SignalHandler::Get().CancelServer(); return TRUE; } return false; } void SignalHandler::Install(const string& product_name, const blaze_util::Path& output_base, const ServerProcessInfo* server_process_info_, SignalHandler::Callback cancel_server) { product_name_ = product_name; output_base_ = output_base; server_process_info_ = server_process_info_; 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 blaze_util::Path& output_base) {} string GetProcessIdAsString() { return blaze_util::ToString(GetCurrentProcessId()); } string GetSelfPath(const char* argv0) { WCHAR buffer[kWindowsPathBufferSize] = {0}; if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "GetSelfPath: GetModuleFileNameW: " << GetLastErrorString(); } return blaze_util::WstringToCstring(buffer); } 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\\\n" "or:\n" " set HOME=c:\\_bazel\\"; } return home; } string GetHomeDir() { // Check HOME, for sake of consistency with Linux / macOS. This is only set // under MSYS2, or potentially in tests. string home = GetPathEnv("HOME"); if (IsRunningWithinTest() || !home.empty()) { // Bazel is running inside of a test. Respect $HOME that the test setup has // set, even if it's empty. return home; } // Check USERPROFILE before calling SHGetKnownFolderPath. Doing so allows the // user to customize (or override) the home directory. // See https://github.com/bazelbuild/bazel/issues/7819#issuecomment-533050947 string userprofile = GetPathEnv("USERPROFILE"); if (!userprofile.empty()) { return userprofile; } 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. // On Windows 2016 Server, Nano server: FOLDERID_Profile is unknown but // %USERPROFILE% is set. See https://github.com/bazelbuild/bazel/issues/6701 if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, nullptr, &wpath))) { string result = blaze_util::WstringToCstring(wpath); ::CoTaskMemFree(wpath); return result; } return ""; } 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. } std::unique_ptr GetProcessCWD(int pid) { // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and // implement or delete. return nullptr; } 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) static const int MAX_CMDLINE_LENGTH = 32768; struct CmdLine { WCHAR cmdline[MAX_CMDLINE_LENGTH]; }; static void CreateCommandLine(CmdLine* result, const blaze_util::Path& exe, const std::vector& wargs_vector) { std::wstringstream cmdline; string short_exe; if (!exe.IsEmpty()) { string error; wstring wshort_exe; if (!blaze_util::AsShortWindowsPath(exe.AsNativePath(), &wshort_exe, &error)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "CreateCommandLine: AsShortWindowsPath(" << exe.AsPrintablePath() << "): " << error; } cmdline << L'\"' << wshort_exe << L'\"'; } bool first = true; for (const std::wstring& wa : wargs_vector) { if (first) { // Skip first argument, it is equal to 'exe'. first = false; continue; } else { cmdline << L' '; } cmdline << wa; } wstring 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 << "): " << blaze_util::WstringToCstring(cmdline_str); } // Copy command line into a mutable buffer. // CreateProcess is allowed to mutate its command line argument. wcsncpy(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 blaze_util::Path& 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.AsPrintablePath() << "): GetProcessStartupTime failed: " << GetLastErrorString(); } blaze_util::Path start_time_file = server_dir.GetRelative("server.starttime"); if (!blaze_util::WriteFile(blaze_util::ToString(start_time), start_time_file)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "WriteProcessStartupTime(" << server_dir.AsPrintablePath() << "): WriteFile(" << start_time_file.AsPrintablePath() << ") failed: " << GetLastErrorString(); } } static HANDLE CreateJvmOutputFile(const blaze_util::Path& 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.AsNativePath().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 */ nullptr); if (handle != INVALID_HANDLE_VALUE) { if (daemon_out_append && !SetFilePointerEx(handle, {0}, nullptr, FILE_END)) { fprintf(stderr, "Could not seek to end of file (%s)\n", path.AsPrintablePath().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 blaze_util::Path& exe, const std::vector& args_vector, const std::map& env, const blaze_util::Path& daemon_output, const bool daemon_out_append, const string& binaries_dir, const blaze_util::Path& server_dir, const StartupOptions& options, BlazeServerStartup** server_startup) { SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; AutoHandle devnull(::CreateFileW( L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &inheritable_handle_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); if (!devnull.IsValid()) { std::string error = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe.AsPrintablePath() << "): CreateFileA(NUL) failed: " << error; } AutoHandle stdout_file(CreateJvmOutputFile( daemon_output, &inheritable_handle_sa, daemon_out_append)); if (!stdout_file.IsValid()) { std::string error = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe.AsPrintablePath() << "): CreateJvmOutputFile(" << daemon_output.AsPrintablePath() << ") 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)) { std::string error = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe.AsPrintablePath() << "): DuplicateHandle(" << daemon_output.AsPrintablePath() << ") failed: " << error; } AutoHandle stderr_file(stderr_handle); // Create an attribute list. wstring werror; std::unique_ptr lpAttributeList; if (!AutoAttributeList::Create(devnull, stdout_file, stderr_handle, &lpAttributeList, &werror)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe.AsPrintablePath() << "): attribute list creation failed: " << blaze_util::WstringToCstring(werror); } PROCESS_INFORMATION processInfo = {0}; STARTUPINFOEXW startupInfoEx = {0}; lpAttributeList->InitStartupInfoExW(&startupInfoEx); std::vector wesc_args_vector; wesc_args_vector.reserve(args_vector.size()); for (const string& a : args_vector) { std::wstring wa = blaze_util::CstringToWstring(a); std::wstring wesc = bazel::windows::WindowsEscapeArg(wa); wesc_args_vector.push_back(wesc); } CmdLine cmdline; CreateCommandLine(&cmdline, exe, wesc_args_vector); BOOL ok; { WithEnvVars env_obj(env); ok = CreateProcessW( /* lpApplicationName */ nullptr, /* lpCommandLine */ cmdline.cmdline, /* lpProcessAttributes */ nullptr, /* lpThreadAttributes */ nullptr, /* bInheritHandles */ TRUE, /* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | EXTENDED_STARTUPINFO_PRESENT, /* lpEnvironment */ nullptr, /* lpCurrentDirectory */ nullptr, /* lpStartupInfo */ &startupInfoEx.StartupInfo, /* lpProcessInformation */ &processInfo); } if (!ok) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteDaemon(" << exe.AsPrintablePath() << "): CreateProcess(" << cmdline.cmdline << ") failed: " << err; } WriteProcessStartupTime(server_dir, processInfo.hProcess); // Pass ownership of processInfo.hProcess *server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess); string pid_string = blaze_util::ToString(processInfo.dwProcessId); blaze_util::Path pid_file = server_dir.GetRelative(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.AsPrintablePath().c_str()); } // Don't close processInfo.hProcess here, it's now owned by the // ProcessHandleBlazeServerStartup instance. CloseHandle(processInfo.hThread); return processInfo.dwProcessId; } // 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. ATTRIBUTE_NORETURN static void ExecuteProgram( const blaze_util::Path& exe, const std::vector& wargs_vector) { CmdLine cmdline; CreateCommandLine(&cmdline, blaze_util::Path(), wargs_vector); bazel::windows::WaitableProcess proc; std::wstring werror; // TODO(laszlocsomor): Fix proc.Create to accept paths with UNC prefix. if (!proc.Create(blaze_util::RemoveUncPrefixMaybe(exe.AsNativePath().c_str()), cmdline.cmdline, nullptr, L"", &werror) || proc.WaitFor(-1, nullptr, &werror) != bazel::windows::WaitableProcess::kWaitSuccess) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteProgram(" << exe.AsPrintablePath() << ") failed: " << blaze_util::WstringToCstring(werror); } werror.clear(); int x = proc.GetExitCode(&werror); if (!werror.empty()) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "ExecuteProgram(" << exe.AsPrintablePath() << ") failed: " << blaze_util::WstringToCstring(werror); } exit(x); } void ExecuteServerJvm(const blaze_util::Path& exe, const std::vector& server_jvm_args) { std::vector wargs; wargs.reserve(server_jvm_args.size()); for (const string& a : server_jvm_args) { std::wstring wa = blaze_util::CstringToWstring(a); std::wstring wesc = bazel::windows::WindowsEscapeArg(wa); wargs.push_back(wesc); } ExecuteProgram(exe, wargs); } void ExecuteRunRequest(const blaze_util::Path& exe, const std::vector& run_request_args) { std::vector wargs; wargs.reserve(run_request_args.size()); for (const string& a : run_request_args) { // The arguments are already escaped, don't escape further. wargs.push_back(blaze_util::CstringToWstring(a)); } ExecuteProgram(exe, wargs); } const char kListSeparator = ';'; bool SymlinkDirectories(const string& posix_target, const blaze_util::Path& name) { wstring target; string error; if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target, &error)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "SymlinkDirectories(" << posix_target << ", " << name.AsPrintablePath() << "): AsAbsoluteWindowsPath(" << posix_target << ") failed: " << error; return false; } wstring werror; if (CreateJunction(name.AsNativePath(), target, &werror) != CreateJunctionResult::kSuccess) { string error(blaze_util::WstringToCstring(werror)); BAZEL_LOG(ERROR) << "SymlinkDirectories(" << posix_target << ", " << name.AsPrintablePath() << "): 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 blaze_util::Path& 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( output_base.GetRelative("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 == blaze_util::ToString(start_time); } bool KillServerProcess(int pid, const blaze_util::Path& 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)) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "Cannot terminate server process with PID " << pid << ", output_base=(" << output_base.AsPrintablePath() << "): " << err; } return result; } void TrySleep(unsigned int milliseconds) { Sleep(milliseconds); } // Not supported. void ExcludePathFromBackup(const blaze_util::Path& 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 blaze_util::Path& path) { // TODO(bazel-team): implement this properly, by mimicing whatever the POSIX // implementation does. if (!blaze_util::MakeDirectories(path, 0755)) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "MakeDirectories(" << path.AsPrintablePath() << ") failed: " << err; } if (!blaze_util::IsDirectory(path)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "'" << path.AsPrintablePath() << "' is not a directory"; } ExcludePathFromBackup(path); } string GetEnv(const string& name) { DWORD size = ::GetEnvironmentVariableA(name.c_str(), nullptr, 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()); } 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(), nullptr, 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, nullptr); 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 == nullptr) { // 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 != nullptr && ::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 blaze_util::Path& output_base, bool batch_mode, bool block, BlazeLock* blaze_lock) { blaze_util::Path lockfile = output_base.GetRelative("lock"); blaze_lock->handle = INVALID_HANDLE_VALUE; bool first_lock_attempt = true; uint64_t st = GetMillisecondsMonotonic(); while (true) { blaze_lock->handle = ::CreateFileW( /* lpFileName */ lockfile.AsNativePath().c_str(), /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, /* dwShareMode */ FILE_SHARE_READ, /* lpSecurityAttributes */ nullptr, /* dwCreationDisposition */ CREATE_ALWAYS, /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, /* hTemplateFile */ nullptr); 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 { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "AcquireLock(" << lockfile.AsPrintablePath() << "): CreateFile failed: " << err; } } 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)) { string err = GetLastErrorString(); BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "AcquireLock(" << lockfile.AsPrintablePath() << "): LockFileEx failed: " << err; } // 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() { // Check USER, for sake of consistency with Linux / macOS. This is only set // under MSYS2, or potentially in tests. string user = GetEnv("USER"); if (!user.empty()) { return user; } // Check USERNAME before calling GetUserNameW. Doing so allows the user to // customize (or override) the user name. // See https://github.com/bazelbuild/bazel/issues/7819#issuecomment-533050947 user = GetEnv("USERNAME"); if (!user.empty()) { return user; } WCHAR buffer[UNLEN + 1]; DWORD len = UNLEN + 1; if (!::GetUserNameW(buffer, &len)) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "GetUserNameW failed: " << GetLastErrorString(); } return blaze_util::WstringToCstring(buffer); } 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= (where can look like // e.g. "25.1.1,comint"). So we check both variables for maximum // compatibility. return emacs == "t" || ExistsEnv("INSIDE_EMACS"); } bool IsStandardTerminal() { for (DWORD i : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}) { DWORD mode = 0; HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE); // 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; } 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. } 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* 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