diff options
Diffstat (limited to 'src/main.cc')
| -rw-r--r-- | src/main.cc | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..f2360cf --- /dev/null +++ b/src/main.cc @@ -0,0 +1,370 @@ +// Copyright 2015 Google Inc. 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. + +// +build ignore + +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "affinity.h" +#include "dep.h" +#include "eval.h" +#include "exec.h" +#include "file.h" +#include "file_cache.h" +#include "fileutil.h" +#include "find.h" +#include "flags.h" +#include "func.h" +#include "log.h" +#include "ninja.h" +#include "parser.h" +#include "regen.h" +#include "stats.h" +#include "stmt.h" +#include "string_piece.h" +#include "stringprintf.h" +#include "strutil.h" +#include "symtab.h" +#include "timeutil.h" +#include "var.h" + +// We know that there are leaks in Kati. Turn off LeakSanitizer by default. +extern "C" const char* __asan_default_options() { + return "detect_leaks=0:allow_user_segv_handler=1"; +} + +static void Init() { + InitSymtab(); + InitFuncTable(); + InitDepNodePool(); + InitParser(); +} + +static void Quit() { + ReportAllStats(); + + QuitParser(); + QuitDepNodePool(); + QuitFuncTable(); + QuitSymtab(); +} + +static void ReadBootstrapMakefile(const vector<Symbol>& targets, + vector<Stmt*>* stmts) { + string bootstrap = + ("CC?=cc\n" +#if defined(__APPLE__) + "CXX?=c++\n" +#else + "CXX?=g++\n" +#endif + "AR?=ar\n" + // Pretend to be GNU make 3.81, for compatibility. + "MAKE_VERSION?=3.81\n" + "KATI?=ckati\n" + // Overwrite $SHELL environment variable. + "SHELL=/bin/sh\n" + // TODO: Add more builtin vars. + ); + + if (!g_flags.no_builtin_rules) { + bootstrap += ( + // http://www.gnu.org/software/make/manual/make.html#Catalogue-of-Rules + // The document above is actually not correct. See default.c: + // http://git.savannah.gnu.org/cgit/make.git/tree/default.c?id=4.1 + ".c.o:\n" + "\t$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<\n" + ".cc.o:\n" + "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<\n" + // TODO: Add more builtin rules. + ); + } + if (g_flags.generate_ninja) { + bootstrap += StringPrintf("MAKE?=make -j%d\n", + g_flags.num_jobs <= 1 ? 1 : g_flags.num_jobs / 2); + } else { + bootstrap += StringPrintf("MAKE?=%s\n", + JoinStrings(g_flags.subkati_args, " ").c_str()); + } + bootstrap += + StringPrintf("MAKECMDGOALS?=%s\n", JoinSymbols(targets, " ").c_str()); + + char cwd[PATH_MAX]; + if (!getcwd(cwd, PATH_MAX)) { + fprintf(stderr, "getcwd failed\n"); + CHECK(false); + } + bootstrap += StringPrintf("CURDIR:=%s\n", cwd); + Parse(Intern(bootstrap).str(), Loc("*bootstrap*", 0), stmts); +} + +static void SetVar(StringPiece l, VarOrigin origin) { + size_t found = l.find('='); + CHECK(found != string::npos); + Symbol lhs = Intern(l.substr(0, found)); + StringPiece rhs = l.substr(found + 1); + lhs.SetGlobalVar( + new RecursiveVar(Value::NewLiteral(rhs.data()), origin, rhs.data())); +} + +extern "C" char** environ; + +class SegfaultHandler { + public: + explicit SegfaultHandler(Evaluator* ev); + ~SegfaultHandler(); + + void handle(int, siginfo_t*, void*); + + private: + static SegfaultHandler* global_handler; + + void dumpstr(const char* s) const { + (void)write(STDERR_FILENO, s, strlen(s)); + } + void dumpint(int i) const { + char buf[11]; + char* ptr = buf + sizeof(buf) - 1; + + if (i < 0) { + i = -i; + dumpstr("-"); + } else if (i == 0) { + dumpstr("0"); + return; + } + + *ptr = '\0'; + while (ptr > buf && i > 0) { + *--ptr = '0' + (i % 10); + i = i / 10; + } + + dumpstr(ptr); + } + + Evaluator* ev_; + + struct sigaction orig_action_; + struct sigaction new_action_; +}; + +SegfaultHandler* SegfaultHandler::global_handler = nullptr; + +SegfaultHandler::SegfaultHandler(Evaluator* ev) : ev_(ev) { + CHECK(global_handler == nullptr); + global_handler = this; + + // Construct an alternate stack, so that we can handle stack overflows. + stack_t ss; + ss.ss_sp = malloc(SIGSTKSZ * 2); + CHECK(ss.ss_sp != nullptr); + ss.ss_size = SIGSTKSZ * 2; + ss.ss_flags = 0; + if (sigaltstack(&ss, nullptr) == -1) { + PERROR("sigaltstack"); + } + + // Register our segfault handler using the alternate stack, falling + // back to the default handler. + sigemptyset(&new_action_.sa_mask); + new_action_.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESETHAND; + new_action_.sa_sigaction = [](int sig, siginfo_t* info, void* context) { + if (global_handler != nullptr) { + global_handler->handle(sig, info, context); + } + + raise(SIGSEGV); + }; + sigaction(SIGSEGV, &new_action_, &orig_action_); +} + +void SegfaultHandler::handle(int sig, siginfo_t* info, void* context) { + // Avoid fprintf in case it allocates or tries to do anything else that may + // hang. + dumpstr("*kati*: Segmentation fault, last evaluated line was "); + dumpstr(ev_->loc().filename); + dumpstr(":"); + dumpint(ev_->loc().lineno); + dumpstr("\n"); + + // Run the original handler, in case we've been preloaded with libSegFault + // or similar. + if (orig_action_.sa_sigaction != nullptr) { + orig_action_.sa_sigaction(sig, info, context); + } +} + +SegfaultHandler::~SegfaultHandler() { + sigaction(SIGSEGV, &orig_action_, nullptr); + global_handler = nullptr; +} + +static int Run(const vector<Symbol>& targets, + const vector<StringPiece>& cl_vars, + const string& orig_args) { + double start_time = GetTime(); + + if (g_flags.generate_ninja && (g_flags.regen || g_flags.dump_kati_stamp)) { + ScopedTimeReporter tr("regen check time"); + if (!NeedsRegen(start_time, orig_args)) { + fprintf(stderr, "No need to regenerate ninja file\n"); + return 0; + } + if (g_flags.dump_kati_stamp) { + printf("Need to regenerate ninja file\n"); + return 0; + } + ClearGlobCache(); + } + + SetAffinityForSingleThread(); + + MakefileCacheManager* cache_mgr = NewMakefileCacheManager(); + + Intern("MAKEFILE_LIST") + .SetGlobalVar(new SimpleVar(StringPrintf(" %s", g_flags.makefile), + VarOrigin::FILE)); + for (char** p = environ; *p; p++) { + SetVar(*p, VarOrigin::ENVIRONMENT); + } + unique_ptr<Evaluator> ev(new Evaluator()); + SegfaultHandler segfault(ev.get()); + + vector<Stmt*> bootstrap_asts; + ReadBootstrapMakefile(targets, &bootstrap_asts); + ev->set_is_bootstrap(true); + for (Stmt* stmt : bootstrap_asts) { + LOG("%s", stmt->DebugString().c_str()); + stmt->Eval(ev.get()); + } + ev->set_is_bootstrap(false); + + ev->set_is_commandline(true); + for (StringPiece l : cl_vars) { + vector<Stmt*> asts; + Parse(Intern(l).str(), Loc("*bootstrap*", 0), &asts); + CHECK(asts.size() == 1); + asts[0]->Eval(ev.get()); + } + ev->set_is_commandline(false); + + { + ScopedTimeReporter tr("eval time"); + Makefile* mk = cache_mgr->ReadMakefile(g_flags.makefile); + for (Stmt* stmt : mk->stmts()) { + LOG("%s", stmt->DebugString().c_str()); + stmt->Eval(ev.get()); + } + } + + for (ParseErrorStmt* err : GetParseErrors()) { + WARN_LOC(err->loc(), "warning for parse error in an unevaluated line: %s", + err->msg.c_str()); + } + + vector<NamedDepNode> nodes; + { + ScopedTimeReporter tr("make dep time"); + MakeDep(ev.get(), ev->rules(), ev->rule_vars(), targets, &nodes); + } + + if (g_flags.is_syntax_check_only) + return 0; + + if (g_flags.generate_ninja) { + ScopedTimeReporter tr("generate ninja time"); + GenerateNinja(nodes, ev.get(), orig_args, start_time); + ev->DumpStackStats(); + return 0; + } + + for (const auto& p : ev->exports()) { + const Symbol name = p.first; + if (p.second) { + Var* v = ev->LookupVar(name); + const string&& value = v->Eval(ev.get()); + LOG("setenv(%s, %s)", name.c_str(), value.c_str()); + setenv(name.c_str(), value.c_str(), 1); + } else { + LOG("unsetenv(%s)", name.c_str()); + unsetenv(name.c_str()); + } + } + + { + ScopedTimeReporter tr("exec time"); + Exec(nodes, ev.get()); + } + + ev->DumpStackStats(); + + for (Stmt* stmt : bootstrap_asts) + delete stmt; + delete cache_mgr; + + return 0; +} + +static void FindFirstMakefie() { + if (g_flags.makefile != NULL) + return; + if (Exists("GNUmakefile")) { + g_flags.makefile = "GNUmakefile"; +#if !defined(__APPLE__) + } else if (Exists("makefile")) { + g_flags.makefile = "makefile"; +#endif + } else if (Exists("Makefile")) { + g_flags.makefile = "Makefile"; + } +} + +static void HandleRealpath(int argc, char** argv) { + char buf[PATH_MAX]; + for (int i = 0; i < argc; i++) { + if (realpath(argv[i], buf)) + printf("%s\n", buf); + } +} + +int main(int argc, char* argv[]) { + if (argc >= 2 && !strcmp(argv[1], "--realpath")) { + HandleRealpath(argc - 2, argv + 2); + return 0; + } + Init(); + string orig_args; + for (int i = 0; i < argc; i++) { + if (i) + orig_args += ' '; + orig_args += argv[i]; + } + g_flags.Parse(argc, argv); + FindFirstMakefie(); + if (g_flags.makefile == NULL) + ERROR("*** No targets specified and no makefile found."); + // This depends on command line flags. + if (g_flags.use_find_emulator) + InitFindEmulator(); + int r = Run(g_flags.targets, g_flags.cl_vars, orig_args); + Quit(); + return r; +} |
