| // Copyright 2011 The Goma Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| #include "gomacc_common.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <time.h> |
| |
| #ifndef _WIN32 |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #endif |
| |
| #include <algorithm> |
| #include <deque> |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| #include <utility> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "compiler_flags.h" |
| #include "compiler_flags_parser.h" |
| #include "compiler_proxy_info.h" |
| #include "compiler_specific.h" |
| #include "env_flags.h" |
| #include "file_helper.h" |
| #include "file_path_util.h" |
| #include "file_stat.h" |
| #include "gcc_flags.h" |
| #include "glog/logging.h" |
| #include "goma_ipc_addr.h" |
| #include "gomacc_argv.h" |
| #include "ioutil.h" |
| #include "mypath.h" |
| #include "path.h" |
| #include "platform_thread.h" |
| #include "scoped_fd.h" |
| #include "simple_timer.h" |
| #include "socket_factory.h" |
| #include "subprocess.h" |
| #include "util.h" |
| |
| #ifdef _WIN32 |
| #include "named_pipe_client_win.h" |
| #endif |
| |
| MSVC_PUSH_DISABLE_WARNING_FOR_PROTO() |
| #include "prototmp/goma_data.pb.h" |
| MSVC_POP_WARNING() |
| |
| #define GOMA_DECLARE_FLAGS_ONLY |
| #include "goma_flags.cc" |
| |
| #ifdef _WIN32 |
| #define isatty(x) false |
| #endif |
| |
| namespace devtools_goma { |
| |
| #ifdef _WIN32 |
| // TODO: move it into goma_ipc, and use it in goma_ipc_unittest.cc? |
| class GomaIPCNamedPipeFactory : public GomaIPC::ChanFactory { |
| public: |
| GomaIPCNamedPipeFactory(const string& name, absl::Duration timeout) |
| : factory_(name, timeout) { |
| } |
| ~GomaIPCNamedPipeFactory() override {} |
| |
| GomaIPCNamedPipeFactory(const GomaIPCNamedPipeFactory&) = delete; |
| GomaIPCNamedPipeFactory& operator=(const GomaIPCNamedPipeFactory&) = delete; |
| |
| std::unique_ptr<IOChannel> New() override { |
| ScopedNamedPipe pipe = factory_.New(); |
| if (!pipe.valid()) { |
| return nullptr; |
| } |
| return std::unique_ptr<IOChannel>(new ScopedNamedPipe(std::move(pipe))); |
| } |
| |
| string DestName() const override { |
| return factory_.DestName(); |
| } |
| |
| private: |
| NamedPipeFactory factory_; |
| }; |
| |
| #else |
| class GomaIPCSocketFactory : public GomaIPC::ChanFactory { |
| public: |
| explicit GomaIPCSocketFactory(string socket_path) |
| : socket_path_(std::move(socket_path)), addr_(nullptr), addr_len_(0) { |
| addr_len_ = InitializeGomaIPCAddress(socket_path_, &un_addr_); |
| addr_ = reinterpret_cast<const sockaddr*>(&un_addr_); |
| } |
| ~GomaIPCSocketFactory() override { |
| } |
| |
| std::unique_ptr<IOChannel> New() override { |
| ScopedSocket socket_fd(socket(AF_GOMA_IPC, SOCK_STREAM, 0)); |
| if (!socket_fd.valid()) { |
| PLOG(ERROR) << "failed to create socket"; |
| return nullptr; |
| } |
| int ret; |
| while ((ret = connect(socket_fd.get(), addr_, addr_len_)) < 0) { |
| if (errno == EINTR) { |
| continue; |
| } |
| break; |
| } |
| if (ret == 0) { |
| if (!socket_fd.SetNonBlocking()) { |
| LOG(ERROR) << "GOMA: failed to set nonblocking: " << socket_fd.get(); |
| return nullptr; |
| } |
| return std::unique_ptr<IOChannel>(new ScopedSocket(std::move(socket_fd))); |
| } |
| LOG_IF(WARNING, ret != -1) << "POSIX spec mentions connect shall return" |
| << " etiher of 0 or -1 but got " << ret; |
| PLOG(ERROR) << "failed to connect to: " << socket_path_; |
| return nullptr; |
| } |
| |
| string DestName() const override { |
| return socket_path_; |
| } |
| |
| private: |
| const string socket_path_; |
| GomaIPCAddr un_addr_; |
| const sockaddr* addr_; |
| socklen_t addr_len_; |
| DISALLOW_COPY_AND_ASSIGN(GomaIPCSocketFactory); |
| }; |
| #endif |
| |
| int GetCompilerProxyPort(GomaIPC::Status* status) { |
| #ifndef _WIN32 |
| GomaIPC goma_ipc(std::unique_ptr<GomaIPC::ChanFactory>( |
| new GomaIPCSocketFactory( |
| file::JoinPathRespectAbsolute(GetGomaTmpDir(), |
| FLAGS_COMPILER_PROXY_SOCKET_NAME)))); |
| #else |
| GomaIPC goma_ipc(std::unique_ptr<GomaIPC::ChanFactory>( |
| new GomaIPCNamedPipeFactory( |
| FLAGS_COMPILER_PROXY_SOCKET_NAME, |
| absl::Milliseconds(FLAGS_NAMEDPIPE_WAIT_TIMEOUT_MS)))); |
| #endif |
| devtools_goma::EmptyMessage req; |
| devtools_goma::HttpPortResponse resp; |
| GomaIPC::Status status_buf; |
| status_buf.health_check_on_timeout = false; |
| if (status == nullptr) { |
| status = &status_buf; |
| } |
| if (goma_ipc.Call("/portz", &req, &resp, status) < 0) { |
| return -1; |
| } |
| return resp.port(); |
| } |
| |
| bool StartCompilerProxy() { |
| if (!FLAGS_START_COMPILER_PROXY) { |
| #if defined(_WIN32) |
| static const char kMsg[] = |
| "compiler_proxy isn't running. Run 'goma_ctl.bat ensure_start'."; |
| #else |
| static const char kMsg[] = |
| "compiler_proxy isn't running. Run 'goma_ctl.py ensure_start'."; |
| #endif |
| fputs(kMsg, stderr); |
| exit(1); |
| } |
| |
| if (FLAGS_COMPILER_PROXY_BINARY.empty()) { |
| return false; |
| } |
| |
| // Try to start up an instance of compiler proxy if it's not |
| // already started. |
| std::cerr << "GOMA: GOMA_START_COMPILER_PROXY=true." |
| << " Starting compiler proxy" << std::endl; |
| #ifndef _WIN32 |
| devtools_goma::ScopedFd lock_fd(open(FLAGS_GOMACC_LOCK_FILENAME.c_str(), |
| O_RDONLY|O_CREAT, |
| 0644)); |
| if (!lock_fd.valid()) { |
| perror("open"); |
| std::cerr << "GOMA: Cannot open " << FLAGS_GOMACC_LOCK_FILENAME |
| << std::endl; |
| return false; |
| } |
| |
| if (flock(lock_fd.fd(), LOCK_EX) == -1) { |
| perror("flock failed"); |
| // Some weird error happened when trying to lock. |
| return false; |
| } |
| #else |
| devtools_goma::ScopedFd lock_fd; |
| lock_fd.reset(CreateEventA(nullptr, TRUE, FALSE, |
| FLAGS_GOMACC_LOCK_GLOBALNAME.c_str())); |
| DWORD last_error = GetLastError(); |
| if (last_error == ERROR_ALREADY_EXISTS) { |
| std::cerr << "GOMA: Someone already starting compiler proxy."; |
| return false; |
| } |
| if (!lock_fd.valid()) { |
| std::cerr << "GOMA: Cannot acquire global named object: " << last_error; |
| } |
| #endif |
| |
| if (GetCompilerProxyPort(nullptr) >= 0) { |
| if (FLAGS_DUMP) { |
| std::cerr << "GOMA: Someone else already ran compiler proxy."; |
| } |
| return true; |
| } |
| |
| #ifndef _WIN32 |
| const string daemon_stderr = file::JoinPathRespectAbsolute( |
| GetGomaTmpDir(), FLAGS_COMPILER_PROXY_DAEMON_STDERR); |
| if (!FLAGS_COMPILER_PROXY_DAEMON_STDERR.empty() && |
| FLAGS_GOMACC_COMPILER_PROXY_RESTART_DELAY > 0) { |
| struct stat st; |
| if (stat(daemon_stderr.c_str(), &st) != -1) { |
| struct timeval tv; |
| PCHECK(gettimeofday(&tv, nullptr) == 0); |
| if (st.st_size > 0 && |
| tv.tv_sec - st.st_mtime < |
| FLAGS_GOMACC_COMPILER_PROXY_RESTART_DELAY) { |
| // Don't retry starting proxy too soon if the last attempt seems |
| // to have failed. |
| return false; |
| } |
| } |
| } |
| #endif |
| |
| const string& compiler_proxy_binary = file::JoinPath( |
| GetMyDirectory(), FLAGS_COMPILER_PROXY_BINARY); |
| |
| if (FLAGS_DUMP) { |
| std::cerr << "GOMA: " << "Invoke " << compiler_proxy_binary << std::endl; |
| } |
| |
| #ifndef _WIN32 |
| int pipe_fd[2]; |
| PCHECK(pipe(pipe_fd) == 0); |
| |
| pid_t pid; |
| if (!(pid = fork())) { |
| // child process, run compiler_proxy with default arguments. |
| |
| lock_fd.Close(); |
| close(pipe_fd[0]); |
| |
| std::set<int> preserve_fds; |
| Daemonize(daemon_stderr, pipe_fd[1], preserve_fds); |
| |
| unsetenv("GOMA_COMPILER_PROXY_DAEMON_MODE"); |
| if (execlp(compiler_proxy_binary.c_str(), |
| compiler_proxy_binary.c_str(), |
| nullptr) == -1) { |
| perror(("execlp compiler_proxy (" + |
| compiler_proxy_binary + ")").c_str()); |
| } |
| exit(1); |
| } else if (pid < 0) { |
| // did not succeed in fork() |
| perror("fork"); |
| std::cerr << "GOMA: fork failed." << std::endl; |
| return false; |
| } |
| |
| // Read out the proxy's actual pid. |
| close(pipe_fd[1]); |
| if (read(pipe_fd[0], &pid, sizeof(pid)) != sizeof(pid)) { |
| char buf[1024]; |
| // Meaning of returned value of strerror_r is different between |
| // XSI and GNU. Need to ignore. |
| (void)strerror_r(errno, buf, sizeof buf); |
| std::cerr << "GOMA: Could not get the proxy's pid. Something went wrong:" |
| << buf << std::endl; |
| close(pipe_fd[0]); |
| return false; |
| } |
| close(pipe_fd[0]); |
| #else |
| PROCESS_INFORMATION pi; |
| STARTUPINFOA si; |
| |
| ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); |
| ZeroMemory(&si, sizeof(STARTUPINFO)); |
| si.cb = sizeof(STARTUPINFO); |
| |
| string path_env = GetEnv("PATH"); |
| CHECK(!path_env.empty()) << "No PATH env. found."; |
| string command_path; |
| // Note: "" to use the Windows default pathext. |
| if (!GetRealExecutablePath( |
| nullptr, "cmd.exe", "", path_env, "", &command_path, nullptr, nullptr)) { |
| std::cerr << "GOMA: failed to find cmd.exe: " |
| << " path_env=" << path_env |
| << std::endl; |
| } |
| string command_line = command_path + " /k \""; |
| command_line = command_line + compiler_proxy_binary; |
| command_line = command_line + "\""; |
| if (CreateProcessA(command_path.c_str(), &command_line[0], nullptr, nullptr, |
| FALSE, DETACHED_PROCESS, nullptr, nullptr, &si, &pi)) { |
| CloseHandle(pi.hThread); |
| } else { |
| DWORD error = GetLastError(); |
| std::cerr << "GOMA: failed to start compiler_proxy: " << error |
| << std::endl; |
| } |
| #endif |
| |
| int num_retries = 0; |
| // Wait until compiler proxy becomes ready. |
| while (GetCompilerProxyPort(nullptr) < 0) { |
| // Make sure the proxy is running. |
| #ifndef _WIN32 |
| if (kill(pid, 0) == -1) { |
| std::cerr << "GOMA: Failed to start compiler proxy." << std::endl; |
| return false; |
| } |
| #else |
| DWORD exit_code = 0; |
| GetExitCodeProcess(pi.hProcess, &exit_code); |
| if (exit_code != STILL_ACTIVE) { |
| std::cerr << "GOMA: compiler proxy died with exit code " |
| << exit_code << std::endl; |
| return false; |
| } |
| #endif |
| |
| // If this loop takes more than 3 secs, |
| num_retries++; |
| if (num_retries++ >= 30 && num_retries % 10 == 0) { |
| std::cerr << "GOMA: Compiler proxy is taking too much time to start. " |
| << "Something might go wrong." << std::endl; |
| } |
| // Wait 100ms. |
| absl::SleepFor(absl::Milliseconds(100)); |
| } |
| |
| return true; |
| } |
| |
| GomaClient::GomaClient(int id, |
| std::unique_ptr<CompilerFlags> flags, |
| const char** envp, |
| string local_compiler_path) |
| #ifndef _WIN32 |
| : goma_ipc_(std::unique_ptr<GomaIPC::ChanFactory>(new GomaIPCSocketFactory( |
| file::JoinPathRespectAbsolute(GetGomaTmpDir(), |
| FLAGS_COMPILER_PROXY_SOCKET_NAME)))), |
| #else |
| : goma_ipc_(std::unique_ptr<GomaIPC::ChanFactory>( |
| new GomaIPCNamedPipeFactory( |
| FLAGS_COMPILER_PROXY_SOCKET_NAME, |
| absl::Milliseconds(FLAGS_NAMEDPIPE_WAIT_TIMEOUT_MS)))), |
| #endif |
| id_(id), |
| flags_(std::move(flags)), |
| local_compiler_path_(std::move(local_compiler_path)) { |
| flags_->GetClientImportantEnvs(envp, &envs_); |
| |
| #ifdef _WIN32 |
| if (flags_->type() == CompilerFlagType::Clexe) { |
| for (const auto& file : flags_->optional_input_filenames()) { |
| // Open the file while gomacc running to prevent from removal. |
| optional_files_.push_back(new ScopedFd(ScopedFd::OpenForRead(file))); |
| } |
| } |
| #endif |
| // used for logging. |
| string buf; |
| std::vector<string> info; |
| info.push_back(flags_->compiler_name()); |
| if (flags_->type() == CompilerFlagType::Gcc) { |
| const GCCFlags& gcc_flags = static_cast<const GCCFlags&>(*flags_); |
| switch (gcc_flags.mode()) { |
| case GCCFlags::PREPROCESS: |
| info.push_back("preprocessing"); |
| if (flags_->input_filenames().size() > 0) |
| info.push_back(flags_->input_filenames()[0]); |
| break; |
| case GCCFlags::COMPILE: |
| info.push_back("compiling"); |
| if (flags_->input_filenames().size() > 0) |
| info.push_back(flags_->input_filenames()[0]); |
| break; |
| case GCCFlags::LINK: |
| info.push_back("linking"); |
| if (flags_->output_files().size() > 0) |
| info.push_back(flags_->output_files()[0]); |
| break; |
| } |
| } else { |
| if (flags_->input_filenames().size() > 0) |
| info.push_back(flags_->input_filenames()[0]); |
| } |
| |
| name_ = absl::StrJoin(info, " "); |
| } |
| |
| GomaClient::~GomaClient() { |
| if (stdin_file_.valid()) { |
| remove(stdin_filename_.c_str()); |
| } |
| #ifdef _WIN32 |
| for (const auto& it : rsp_files_) { |
| ScopedFd* fd = it.second; |
| delete fd; |
| DeleteFileA(it.first.c_str()); |
| } |
| for (const auto* fd : optional_files_) { |
| delete fd; |
| } |
| #endif |
| } |
| |
| void GomaClient::OutputResp() { |
| #ifdef _WIN32 |
| if (multi_exec_resp_.get()) { |
| OutputMultiExecResp(multi_exec_resp_.get()); |
| return; |
| } |
| #endif |
| CHECK(exec_resp_.get() != nullptr); |
| OutputExecResp(exec_resp_.get()); |
| } |
| |
| int GomaClient::retval() const { |
| #ifdef _WIN32 |
| if (multi_exec_resp_.get()) { |
| for (const auto& it : multi_exec_resp_->response()) { |
| if (it.resp().result().exit_status() != 0) |
| return it.resp().result().exit_status(); |
| } |
| return 0; |
| } |
| #endif |
| CHECK(exec_resp_.get()); |
| return exec_resp_->result().exit_status(); |
| } |
| |
| // Call IPC Request. Return IPC_OK if successful. |
| GomaClient::Result GomaClient::CallIPCAsync() { |
| string request_path; |
| std::unique_ptr<google::protobuf::Message> req; |
| |
| #ifdef _WIN32 |
| if (FLAGS_FAN_OUT_EXEC_REQ && flags_->input_filenames().size() > 1) { |
| std::unique_ptr<MultiExecReq> multi_exec_req(new MultiExecReq); |
| request_path = "/me"; |
| PrepareMultiExecRequest(multi_exec_req.get()); |
| req = std::move(multi_exec_req); |
| multi_exec_resp_.reset(new MultiExecResp); |
| } else { |
| #endif |
| std::unique_ptr<ExecReq> exec_req(new ExecReq); |
| request_path = "/e"; |
| PrepareExecRequest(*flags_, exec_req.get()); |
| req = std::move(exec_req); |
| exec_resp_ = absl::make_unique<ExecResp>(); |
| #ifdef _WIN32 |
| } |
| #endif |
| if (FLAGS_DUMP_REQUEST) { |
| std::cerr << "GOMA:" << name_ << ": " << req->DebugString(); |
| } |
| status_ = GomaIPC::Status(); |
| ipc_chan_ = goma_ipc_.CallAsync(request_path, req.get(), &status_); |
| if (ipc_chan_ == nullptr) { |
| if (status_.connect_success == true) { |
| if (status_.http_return_code == 401) { |
| std::cerr << "GOMA: Authentication failed (401)" << std::endl; |
| } else if (status_.http_return_code == 400) { |
| std::cerr << "GOMA: Bad request (400)" << std::endl; |
| } else if (FLAGS_DUMP) { |
| std::cerr << "GOMA: IPC Connection was successful but RPC failed" |
| << std::endl; |
| } |
| |
| return IPC_REJECTED; |
| } |
| LOG(WARNING) << "failed to connect to compiler_proxy: " |
| << status_.DebugString(); |
| // If the failure reason was failure to connect, try starting |
| // compiler proxy and retry the request. |
| if (StartCompilerProxy()) { |
| status_ = GomaIPC::Status(); |
| ipc_chan_ = goma_ipc_.CallAsync(request_path, req.get(), &status_); |
| if (ipc_chan_ != nullptr) { |
| // retry after starting compiler_proxy was successful |
| if (FLAGS_DUMP) { |
| std::cerr << "GOMA: Retry after starting compiler_proxy success" |
| << std::endl; |
| } |
| } else { |
| // Even if we retried, we weren't successful, give up. |
| if (FLAGS_DUMP) { |
| std::cerr << "GOMA: Retry after starting compiler_proxy was " |
| "unsuccessful" |
| << std::endl; |
| } |
| return IPC_FAIL; |
| } |
| } else { |
| // Starting compiler proxy was unsuccessful |
| if (FLAGS_DUMP) { |
| std::cerr << "GOMA: Could not connect to compiler_proxy and " |
| "starting it failed." |
| << std::endl; |
| } |
| return IPC_FAIL; |
| } |
| } |
| return IPC_OK; |
| } |
| |
| GomaClient::Result GomaClient::WaitIPC() { |
| DCHECK(ipc_chan_ != nullptr); |
| google::protobuf::Message* resp = nullptr; |
| #ifdef _WIN32 |
| if (multi_exec_resp_.get()) |
| resp = multi_exec_resp_.get(); |
| #endif |
| if (exec_resp_.get()) |
| resp = exec_resp_.get(); |
| |
| if (goma_ipc_.Wait(std::move(ipc_chan_), resp, &status_) != OK) |
| return IPC_FAIL; |
| |
| absl::Duration req_send_time = status_.req_send_time; |
| absl::Duration resp_recv_time = status_.resp_recv_time; |
| absl::Duration resp_write_time; |
| |
| SimpleTimer timer; |
| if (FLAGS_DUMP_RESPONSE) { |
| std::cerr << "GOMA:" << name_ << ": " << resp->DebugString(); |
| } |
| if (FLAGS_OUTPUT_EXEC_RESP) { |
| OutputResp(); |
| } |
| |
| // TODO: check output files are written? |
| if (FLAGS_DUMP_TIME) { |
| resp_write_time = timer.GetDuration(); |
| std::cerr << "GOMA:" << name_ |
| << " send/recv/write=" |
| << req_send_time << "/" |
| << resp_recv_time << "/" |
| << resp_write_time << std::endl; |
| // TODO: show more time metrics. |
| } |
| return IPC_OK; |
| } |
| |
| string GomaClient::CreateStdinFile() { |
| #ifndef _WIN32 |
| stdin_filename_ = file::JoinPath(GetGomaTmpDir(), "gomacc.stdin.XXXXXX"); |
| stdin_file_.reset(mkstemp(&stdin_filename_[0])); |
| for (;;) { |
| char buf[4096]; |
| int r = read(STDIN_FILENO, buf, sizeof buf); |
| if (r < 0) { |
| if (errno == EINTR) continue; |
| PLOG(ERROR) << "read"; |
| break; |
| } |
| if (r == 0) { |
| break; |
| } |
| PCHECK(write(stdin_file_.fd(), buf, r) == r); |
| } |
| #else |
| char temp_file[MAX_PATH] = {0}; |
| GetTempFileNameA(GetGomaTmpDir().c_str(), "gomacc.stdin", 0, temp_file); |
| stdin_filename_ = temp_file; |
| stdin_file_.reset(ScopedFd::Create(stdin_filename_, 0600)); |
| char buf[4096]; |
| size_t actual_read = 0; |
| while ((actual_read = fread(buf, 1, 4096, stdin)) > 0) { |
| stdin_file_.Write(buf, actual_read); |
| } |
| #endif |
| return stdin_filename_; |
| } |
| |
| GomaClient::Result GomaClient::CallIPC() { |
| Result r = CallIPCAsync(); |
| if (r != IPC_OK) |
| return r; |
| return WaitIPC(); |
| } |
| |
| #ifdef _WIN32 |
| bool GomaClient::PrepareMultiExecRequest(MultiExecReq* req) { |
| const string tmpdir = devtools_goma::GetGomaTmpDir(); |
| pid_t pid = Getpid(); |
| |
| std::set<string> input_filenames(flags_->input_filenames().begin(), |
| flags_->input_filenames().end()); |
| std::vector<string> args_no_input; // args other than input filenames. |
| // Input filenames may be in @rsp file, so scan expanded_args here. |
| const std::vector<string>& expanded_args = |
| (flags_->expanded_args().empty() |
| ? flags_->args() : flags_->expanded_args()); |
| FanOutArgsByInput(expanded_args, input_filenames, &args_no_input); |
| |
| int nth = 0; |
| for (std::set<string>::const_iterator iter = input_filenames.begin(); |
| iter != input_filenames.end(); |
| ++iter, ++nth) { |
| const string& input_filename = *iter; |
| const string cmdline = BuildArgsForInput(args_no_input, input_filename); |
| std::stringstream fname; |
| fname << file::Basename(input_filename) |
| << "." << pid << "." << nth << ".rsp"; |
| const string rsp_filename = file::JoinPath(tmpdir, fname.str()); |
| if (!WriteStringToFile(cmdline, rsp_filename)) { |
| LOG(ERROR) << "GOMA: Failed to create " << rsp_filename; |
| return false; |
| } |
| // Keeps handle open, so that the rsp_file are not removed by tmp cleaner |
| // while gomacc is running. |
| rsp_files_.emplace_back(rsp_filename, |
| new ScopedFd(ScopedFd::OpenForRead(rsp_filename))); |
| std::vector<string> args_of_input; |
| args_of_input.push_back(flags_->args()[0]); |
| args_of_input.push_back("@" + rsp_filename); |
| std::unique_ptr<CompilerFlags> flags_of_input( |
| CompilerFlagsParser::MustNew(args_of_input, ".")); |
| if (!PrepareExecRequest(*flags_of_input, req->add_req())) { |
| LOG(ERROR) << "GOMA: failed to create ExecReq for " << input_filename; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void GomaClient::OutputMultiExecResp(MultiExecResp* resp) { |
| for (auto& exec_resp : *resp->mutable_response()) { |
| OutputExecResp(exec_resp.mutable_resp()); |
| } |
| } |
| #endif |
| |
| bool GomaClient::PrepareExecRequest(const CompilerFlags& flags, ExecReq* req) { |
| req->mutable_command_spec()->set_name( |
| flags.compiler_name()); |
| |
| bool use_color_diagnostics = false; |
| #ifndef _WIN32 |
| if (GCCFlags::IsClangCommand(flags.compiler_name()) && |
| isatty(STDERR_FILENO)) { |
| const char* term = getenv("TERM"); |
| if (term != nullptr && strcmp(term, "dump") != 0) |
| use_color_diagnostics = true; |
| } |
| #endif |
| |
| if (flags.type() == CompilerFlagType::Gcc) { |
| const GCCFlags& gcc_flags = static_cast<const GCCFlags&>(flags); |
| if (gcc_flags.is_stdin_input()) { |
| CHECK(!isatty(STDIN_FILENO)) << "goma doesn't support tty input." |
| << flags.DebugString(); |
| ExecReq_Input* input = req->add_input(); |
| string tempfilename = CreateStdinFile(); |
| input->set_filename(tempfilename); |
| input->set_hash_key(""); |
| DCHECK_EQ(req->input_size(), 1); |
| FLAGS_RETRY = false; |
| } |
| if (FLAGS_FALLBACK_CONFTEST) { |
| devtools_goma::RequesterEnv* requester_env = req->mutable_requester_env(); |
| for (const auto& input : gcc_flags.input_filenames()) { |
| if (file::Stem(input) == "conftest") { |
| FileStat file_stat(input); |
| if (!file_stat.IsValid() || |
| *file_stat.mtime + absl::Seconds(10) > absl::Now()) { |
| // probably conftest.c, force fallback. |
| requester_env->add_fallback_input_file(input); |
| } |
| } |
| } |
| } |
| } |
| |
| // If local_compiler_path_ is empty, compiler proxy will find out |
| // local compiler from requester_env's PATH and gomacc_path. |
| if (gomacc_path_.empty()) { |
| req->mutable_requester_env()->set_gomacc_path(GetMyPathname()); |
| } else { |
| req->mutable_requester_env()->set_gomacc_path(gomacc_path_); |
| } |
| for (size_t i = 0; i < flags.args().size(); ++i) { |
| req->add_arg(flags.args()[i]); |
| if (i == 0 && use_color_diagnostics) |
| req->add_arg("-fcolor-diagnostics"); |
| } |
| if (cwd_.empty()) { |
| cwd_ = GetCurrentDirNameOrDie(); |
| } |
| req->set_cwd(cwd_); |
| |
| if (!local_compiler_path_.empty()) { |
| req->mutable_command_spec()->set_local_compiler_path(local_compiler_path_); |
| } |
| |
| req->mutable_requester_info()->set_api_version( |
| RequesterInfo::CURRENT_VERSION); |
| req->mutable_requester_info()->set_pid(Getpid()); |
| req->mutable_requester_info()->set_goma_revision(kBuiltRevisionString); |
| string autoninja_build_id = GetEnv("AUTONINJA_BUILD_ID"); |
| if (!autoninja_build_id.empty()) { |
| req->mutable_requester_info()->set_build_id( |
| std::move(autoninja_build_id)); |
| } |
| |
| if (FLAGS_STORE_ONLY) { |
| if (FLAGS_USE_SUCCESS) { |
| fprintf(stderr, |
| "You cannot use both GOMA_STORE_ONLY and GOMA_USE_SUCCESS\n"); |
| exit(1); |
| } |
| req->set_cache_policy(ExecReq::STORE_ONLY); |
| } else if (FLAGS_USE_SUCCESS) { |
| req->set_cache_policy(ExecReq::LOOKUP_AND_STORE_SUCCESS); |
| } |
| |
| for (size_t i = 0; i < envs_.size(); i++) { |
| req->add_env(envs_[i]); |
| } |
| |
| devtools_goma::RequesterEnv* requester_env = req->mutable_requester_env(); |
| const string path_env = GetEnv("PATH"); |
| if (!path_env.empty()) |
| requester_env->set_local_path(path_env); |
| if (!FLAGS_VERIFY_COMMAND.empty()) { |
| requester_env->set_verify_command(FLAGS_VERIFY_COMMAND); |
| requester_env->set_use_local(false); |
| requester_env->set_fallback(false); |
| } else if (FLAGS_VERIFY_OUTPUT) { |
| requester_env->set_verify_output(true); |
| requester_env->set_use_local(true); |
| requester_env->set_fallback(true); |
| } else { |
| if (FLAGS_USE_LOCAL) |
| requester_env->set_use_local(true); |
| if (FLAGS_FALLBACK) |
| requester_env->set_fallback(true); |
| } |
| if (!FLAGS_FALLBACK_INPUT_FILES.empty()) { |
| for (auto&& f : absl::StrSplit(FLAGS_FALLBACK_INPUT_FILES, |
| ',', |
| absl::SkipEmpty())) { |
| requester_env->add_fallback_input_file(string(f)); |
| } |
| } |
| |
| if (!FLAGS_IMPLICIT_INPUT_FILES.empty()) { |
| // Set these file in ExecReq. |
| // We don't need hash_key for these files here. |
| // Compiler proxy picks them as required_files and computes hash_key. |
| for (auto&& f : absl::StrSplit(FLAGS_IMPLICIT_INPUT_FILES, |
| ',', |
| absl::SkipEmpty())) { |
| ExecReq_Input* input = req->add_input(); |
| input->set_filename(file::JoinPathRespectAbsolute(cwd_, f)); |
| input->set_hash_key(""); |
| } |
| } |
| #ifndef _WIN32 |
| mode_t mask = umask(0000); |
| umask(mask); |
| requester_env->set_umask(mask); |
| #endif |
| return true; |
| } |
| |
| void GomaClient::OutputExecResp(ExecResp* resp) { |
| WriteStdout(resp->result().stdout_buffer()); |
| WriteStderr(resp->result().stderr_buffer()); |
| for (int i = 0; i < resp->error_message_size(); i++) { |
| std::cerr << "GOMA:" << name_ |
| << ":*ERROR*: " << resp->error_message(i) << std::endl; |
| } |
| resp->mutable_result()->clear_stdout_buffer(); |
| resp->mutable_result()->clear_stderr_buffer(); |
| resp->clear_error_message(); |
| } |
| |
| } // namespace devtools_goma |