blob: e9df6c7e07fc7f6d64fc84250937276aeeff3ffd [file] [log] [blame]
// Copyright 2010 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 <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>
#endif
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#ifdef _WIN32
#include "config_win.h"
#endif
#include "basictypes.h"
#include "breakpad.h"
#include "compiler_flags.h"
#include "compiler_flags_parser.h"
#include "compiler_specific.h"
#include "cros_util.h"
#include "env_flags.h"
#include "file_stat.h"
#include "glog/logging.h"
#include "goma_ipc.h"
#include "gomacc_argv.h"
#include "gomacc_common.h"
#include "ioutil.h"
#include "mypath.h"
#include "path.h" // file::JoinPath
MSVC_PUSH_DISABLE_WARNING_FOR_PROTO()
#include "prototmp/goma_data.pb.h"
MSVC_POP_WARNING()
#include "scoped_fd.h"
#include "simple_timer.h"
#include "subprocess.h"
#include "util.h"
GOMA_DECLARE_bool(DUMP);
GOMA_DECLARE_bool(DUMP_ARGV);
GOMA_DECLARE_bool(FALLBACK);
GOMA_DECLARE_bool(GOMACC_ENABLE_CRASH_DUMP);
GOMA_DECLARE_bool(GOMACC_WRITE_LOG_FOR_TESTING);
GOMA_DECLARE_bool(RETRY);
GOMA_DECLARE_bool(STORE_ONLY);
GOMA_DECLARE_string(TMP_DIR);
GOMA_DECLARE_bool(USE_LOCAL);
GOMA_DECLARE_bool(DISABLED);
GOMA_DECLARE_bool(VERIFY_ASSEMBLER_CODE);
GOMA_DECLARE_bool(VERIFY_PREPROCESS_CODE);
GOMA_DECLARE_string(VERIFY_COMMAND);
#ifdef __linux__
GOMA_DECLARE_string(LOAD_AVERAGE_LIMIT);
GOMA_DECLARE_int32(MAX_SLEEP_TIME);
#endif
#ifdef _WIN32
GOMA_DECLARE_bool(FAN_OUT_EXEC_REQ);
#endif
using devtools_goma::CompilerFlags;
using devtools_goma::CompilerFlagsParser;
using devtools_goma::FileStat;
using devtools_goma::GomaIPC;
using devtools_goma::GetMyDirectory;
using devtools_goma::GetMyPathname;
using devtools_goma::Getpid;
using devtools_goma::GomaClient;
using std::string;
#ifndef _WIN32
using devtools_goma::ReadCommandOutputByPopen;
using devtools_goma::Execvpe;
using devtools_goma::ExecvpeNonGomacc;
#else
using devtools_goma::SpawnAndWait;
using devtools_goma::SpawnAndWaitNonGomacc;
#endif
namespace {
#ifndef _WIN32
// Dump for debugging
string DumpArgvString(
size_t argc, const char *argv[], const char *message) {
std::stringstream ss;
ss << "DEBUG: " << message << ": ";
for (size_t i = 0; i < argc; ++i) {
ss << " " << (argv[i] ? argv[i] : "(null)");
}
ss << std::endl;
return ss.str();
}
static void DumpArgv(size_t argc, const char *argv[], const char *message) {
std::cerr << DumpArgvString(argc, argv, message);
}
#endif
static bool AmIGomacc(absl::string_view argv0) {
absl::string_view basename = file::Basename(argv0);
if (basename != "gomacc"
#ifdef _WIN32
&& basename != "gomacc.exe"
#endif
) {
return false;
}
return true;
}
bool HandleHttpPortRequest(int argc, char* argv[]) {
if (argc < 2 || strcmp(argv[1], "port") != 0) {
return false;
}
if (!AmIGomacc(argv[0])) {
return false;
}
GomaIPC::Status status;
status.health_check_on_timeout = false;
int port = GetCompilerProxyPort(&status);
if (port < 0) {
std::cerr << "GOMA: port request failed. "
<< "connect_success: " << status.connect_success
<< ", err: " << status.err
<< " " << status.error_message
<< ", http_return_code: " << status.http_return_code
<< std::endl;
} else {
std::cout << port << std::endl;
}
return true;
}
bool HandleGomaTmpDir(int argc, char* argv[]) {
if (argc < 2 || strcmp(argv[1], "tmp_dir") != 0) {
return false;
}
if (!AmIGomacc(argv[0])) {
return false;
}
std::cout << devtools_goma::GetGomaTmpDir() << std::endl;
return true;
}
// Runs gomacc again with modification to get preprocessed code (-E) or
// assembler code (-S) instead of object code (-c).
void VerifyIntermediateStageOutput(bool args0_is_argv0,
const std::vector<string>& args,
const char* new_option,
const char* new_ext) {
#ifndef _WIN32
// Unset GOMA_VERIFY_*_CODE not to run the same thing again.
unsetenv("GOMA_VERIFY_PREPROCESS_CODE");
unsetenv("GOMA_VERIFY_ASSEMBLER_CODE");
string mypath = GetMyPathname();
std::vector<const char*> new_argv;
std::vector<string> outputs;
new_argv.push_back(mypath.c_str());
bool run_verify_output = false;
// TODO: refactor CompilerFlags and reuse here.
// args[0] represents real gcc/g++/javac command.
// mypath is realpath of argv[0].
// So, if args[0] is argv[0], we already set it as new_argv[0] (mypath), so
// we need to skip args[0].
// Otherwise, if args[0] is not argv[0], it would be invoked via gomacc
// (e.g. "gomacc gcc .."), so we need to set args[0] in new_argv.
for (size_t i = (args0_is_argv0 ? 1 : 0); i < args.size(); i++) {
if (args[i] == "-S" || args[i] == "-E") {
return;
}
if (args[i] == "-c") {
new_argv.push_back(new_option);
run_verify_output = true;
continue;
}
if (strncmp(args[i].c_str(), "-M", 2) == 0) {
if (args[i] == "-MF")
++i; // skip next args. -MF file.
continue;
}
if (args[i] == "-o") {
if (i + 1 == args.size()) {
// argument to '-o' is missing.
return;
}
new_argv.push_back("-o");
++i;
} else if (strncmp(args[i].c_str(), "-o", 2) != 0) {
new_argv.push_back(args[i].c_str());
continue;
}
// args[i] is filename or -ofilename.
string output = args[i];
size_t ext = output.find_last_of('.');
CHECK_NE(ext, string::npos);
output = output.substr(0, ext) + new_ext;
outputs.push_back(output);
new_argv.push_back(outputs.back().c_str());
}
if (!run_verify_output)
return;
int argc = new_argv.size();
new_argv.push_back(nullptr);
if (FLAGS_DUMP_ARGV)
DumpArgv(argc, &new_argv[0], "verify intermediate");
pid_t pid = fork();
if (!pid) {
// Child process.
setenv("GOMA_VERIFY_OUTPUT", "true", 1);
execvp(GetMyPathname().c_str(), const_cast<char**>(&new_argv[0]));
perror("execvp");
return;
}
if (pid < 0) {
perror("fork");
return;
}
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status)) {
std::cerr << "VerifyAssemblerCode: exit_status=" << status << std::endl;
}
#else
UNREFERENCED_PARAMETER(args0_is_argv0);
UNREFERENCED_PARAMETER(args);
UNREFERENCED_PARAMETER(new_option);
UNREFERENCED_PARAMETER(new_ext);
#endif
}
} // anonymous namespace
int main(int argc, char* argv[], const char* envp[]) {
CheckFlagNames(envp);
GOOGLE_PROTOBUF_VERIFY_VERSION;
// TODO: Check their overhead if they are acceptable.
// We might want to eliminate them in release version?
google::InitGoogleLogging(argv[0]);
#ifndef _WIN32
google::InstallFailureSignalHandler();
#else
WinsockHelper wsa;
#endif
FLAGS_TMP_DIR = devtools_goma::GetGomaTmpDir();
devtools_goma::CheckTempDirectory(FLAGS_TMP_DIR);
if (FLAGS_GOMACC_ENABLE_CRASH_DUMP) {
devtools_goma::InitCrashReporter(devtools_goma::GetCrashDumpDirectory());
}
if (FLAGS_GOMACC_WRITE_LOG_FOR_TESTING) {
LOG(INFO) << "This is a log used by a test that need gomacc.INFO.";
fprintf(stderr, "log has been written. exiting...\n");
return 0;
}
if (HandleHttpPortRequest(argc, argv)) {
return 0;
}
if (HandleGomaTmpDir(argc, argv)) {
return 0;
}
std::vector<string> args;
bool masquerade_mode = false;
string verify_command;
string local_command_path;
if (!devtools_goma::BuildGomaccArgv(
argc, (const char**)argv,
&args, &masquerade_mode,
&verify_command, &local_command_path)) {
// no gcc or g++ in argv.
fprintf(stderr, "usage: %s [gcc|g++|cl] [options]\n", argv[0]);
#ifndef _WIN32
const string& goma_ctl = file::JoinPath(GetMyDirectory(), "goma_ctl.py");
if (system(goma_ctl.c_str())) {
fprintf(stderr, "Failed to check compiler_proxy's status\n");
}
#endif
exit(1);
}
if (!verify_command.empty()) {
FLAGS_VERIFY_COMMAND = verify_command;
FLAGS_USE_LOCAL = false;
FLAGS_FALLBACK = false;
FLAGS_STORE_ONLY = true;
FLAGS_RETRY = false;
}
#ifdef __linux__
// For ChromiumOS.
if (!devtools_goma::CanGomaccHandleCwd()) {
FLAGS_DISABLED = true;
}
#endif
if (FLAGS_DISABLED) {
if (masquerade_mode) {
local_command_path = argv[0];
}
// Non absolute path gcc won't be set to local_command_path but it should
// be set for this time.
if (local_command_path.empty()) {
local_command_path = argv[1];
}
std::vector<string> envs;
envs.push_back("GOMA_WILL_FAIL_WITH_UKNOWN_FLAG=true");
for (int i = 0; envp[i]; ++i)
envs.push_back(envp[i]);
FileStat gomacc_filestat(GetMyPathname());
CHECK(gomacc_filestat.IsValid());
#ifdef __linux__
// For ChromiumOS.
// TODO: support other platforms?
float load = strtof(FLAGS_LOAD_AVERAGE_LIMIT.c_str(), nullptr);
const absl::Duration max_sleep_time = absl::Seconds(FLAGS_MAX_SLEEP_TIME);
if (load >= 1.0 && FLAGS_MAX_SLEEP_TIME > 0) {
devtools_goma::WaitUntilLoadAvgLowerThan(load, max_sleep_time);
} else {
LOG(WARNING) << "Will not wait for the low load average because of "
<< "wrong value."
<< " FLAGS_LOAD_AVERAGE_LIMIT=" << FLAGS_LOAD_AVERAGE_LIMIT
<< " FLAGS_MAX_SLEEP_TIME=" << FLAGS_MAX_SLEEP_TIME;
}
#endif
#ifdef _WIN32
// Not sure why, but using execve causes accessing of
// invalid memory address.
// b/69231578
// NOTE: SpawnAndWaitNonGomacc is not execve equivalent for windows.
exit(SpawnAndWaitNonGomacc(&gomacc_filestat, local_command_path, args,
envs));
#else
exit(ExecvpeNonGomacc(&gomacc_filestat, local_command_path, args, envs));
#endif
}
std::unique_ptr<CompilerFlags> flags(CompilerFlagsParser::New(args, "."));
if (flags.get() == nullptr) {
// TODO: handle all commands in compiler_proxy
if (local_command_path.empty()) {
// masquerade mode with unsupported command name
// or prepend mode with command basename.
fprintf(stderr, "usage: %s [gcc|g++|cl] [options]\n", argv[0]);
exit(1);
}
std::vector<string> envs;
for (int i = 0; envp[i]; ++i)
envs.push_back(envp[i]);
// prepend mode with command path.
#ifdef _WIN32
// Not sure why, but using execve causes accessing of
// invalid memory address.
// b/69231578
// NOTE: SpawnAndWait is not execve equivalent for windows.
exit(SpawnAndWait(local_command_path, args, envs));
#else
exit(Execvpe(local_command_path, args, envs));
#endif
}
GomaClient client(Getpid(), std::move(flags), envp, local_command_path);
if (FLAGS_VERIFY_PREPROCESS_CODE) {
VerifyIntermediateStageOutput(masquerade_mode, args, "-E", ".i");
}
if (FLAGS_VERIFY_ASSEMBLER_CODE) {
VerifyIntermediateStageOutput(masquerade_mode, args, "-S", ".s");
}
GomaClient::Result r = client.CallIPC();
if (r != GomaClient::IPC_OK)
LOG(ERROR) << "GOMA: compiler proxy not working?";
int retval = (r != GomaClient::IPC_OK) ? EXIT_FAILURE : client.retval();
client.OutputResp();
// normalize exit status code to what could be handled by caller.
if (retval < 0 || retval > 0xff) {
return EXIT_FAILURE;
}
return retval;
}