mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
605 lines
16 KiB
C
605 lines
16 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/bits/safemacros.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/copyfile.h"
|
|
#include "libc/calls/sigbits.h"
|
|
#include "libc/calls/struct/sigset.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/conv.h"
|
|
#include "libc/log/color.internal.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/getopt/getopt.h"
|
|
|
|
#define MANUAL \
|
|
"\
|
|
SYNOPSIS\n\
|
|
\n\
|
|
compile.com [FLAGS] COMMAND [ARGS...]\n\
|
|
\n\
|
|
OVERVIEW\n\
|
|
\n\
|
|
Compiler Collection Frontend Frontend\n\
|
|
\n\
|
|
DESCRIPTION\n\
|
|
\n\
|
|
This is a generic command wrapper, e.g.\n\
|
|
\n\
|
|
compile.com gcc -o program program.c\n\
|
|
\n\
|
|
This wrapper provides the following services:\n\
|
|
\n\
|
|
- Ensures the output directory exists\n\
|
|
- Echo the launched subcommand (silent mode supported if V=0)\n\
|
|
- Magic filtering of GCC vs. Clang flag incompatibilities\n\
|
|
- Unzips the vendored GCC toolchain if it hasn't happened yet\n\
|
|
- Making temporary copies of APE executables w/o side-effects\n\
|
|
- Truncating long lines in \"TERM=dumb\" terminals like emacs\n\
|
|
\n\
|
|
This wrapper is extremely fast.\n\
|
|
\n\
|
|
FLAGS\n\
|
|
\n\
|
|
-A ACTION specifies short command name for V=0 logging\n\
|
|
-T TARGET specifies target name for V=0 logging\n\
|
|
-V NUMBER specifies compiler version\n\
|
|
-t touch target on success\n\
|
|
-n do nothing (used to prime the executable)\n\
|
|
-? print help\n\
|
|
\n"
|
|
|
|
struct Args {
|
|
size_t n;
|
|
char **p;
|
|
};
|
|
|
|
struct Command {
|
|
size_t n;
|
|
char *p;
|
|
};
|
|
|
|
bool iscc;
|
|
bool isclang;
|
|
bool isgcc;
|
|
bool wantasan;
|
|
bool wantfentry;
|
|
bool wantframe;
|
|
bool wantnop;
|
|
bool wantnopg;
|
|
bool wantpg;
|
|
bool wantrecord;
|
|
bool wantubsan;
|
|
bool touchtarget;
|
|
|
|
char *cmd;
|
|
char *comdbg;
|
|
char *cachedcmd;
|
|
char *originalcmd;
|
|
char *colorflag;
|
|
char *outdir;
|
|
char *outpath;
|
|
char *action;
|
|
char *target;
|
|
char ccpath[PATH_MAX];
|
|
int ccversion;
|
|
int columns;
|
|
|
|
sigset_t mask;
|
|
sigset_t savemask;
|
|
|
|
struct Args env;
|
|
struct Args args;
|
|
struct Command command;
|
|
|
|
const char *const kSafeEnv[] = {
|
|
"ADDR2LINE", // needed by GetAddr2linePath
|
|
"MAKEFLAGS", // needed by IsRunningUnderMake
|
|
"PATH", // needed by clang
|
|
"PWD", // just seems plain needed
|
|
"TERM", // needed by IsTerminalInarticulate
|
|
"TMPDIR", // needed by compiler
|
|
};
|
|
|
|
const char *const kGccOnlyFlags[] = {
|
|
"--nocompress-debug-sections",
|
|
"--noexecstack",
|
|
"-Wa,--nocompress-debug-sections",
|
|
"-Wa,--noexecstack",
|
|
"-Wno-unused-but-set-variable",
|
|
"-Wunsafe-loop-optimizations",
|
|
"-fbranch-target-load-optimize",
|
|
"-fcx-limited-range",
|
|
"-fdelete-dead-exceptions",
|
|
"-femit-struct-debug-baseonly",
|
|
"-fipa-pta",
|
|
"-fivopts",
|
|
"-flimit-function-alignment",
|
|
"-fmerge-constants",
|
|
"-fmodulo-sched",
|
|
"-fmodulo-sched-allow-regmoves",
|
|
"-fno-align-jumps",
|
|
"-fno-align-labels",
|
|
"-fno-align-loops",
|
|
"-fno-fp-int-builtin-inexact",
|
|
"-fno-gnu-unique",
|
|
"-fno-gnu-unique",
|
|
"-fno-instrument-functions",
|
|
"-fno-whole-program",
|
|
"-fopt-info-vec",
|
|
"-fopt-info-vec-missed",
|
|
"-freg-struct-return",
|
|
"-freschedule-modulo-scheduled-loops",
|
|
"-frounding-math",
|
|
"-fsched2-use-superblocks",
|
|
"-fschedule-insns",
|
|
"-fschedule-insns2",
|
|
"-fshrink-wrap",
|
|
"-fshrink-wrap-separate",
|
|
"-fsignaling-nans",
|
|
"-fstack-clash-protection",
|
|
"-ftracer",
|
|
"-ftrapv",
|
|
"-ftree-loop-im",
|
|
"-ftree-loop-vectorize",
|
|
"-funsafe-loop-optimizations",
|
|
"-fversion-loops-for-strides",
|
|
"-fwhole-program",
|
|
"-gdescribe-dies",
|
|
"-gstabs",
|
|
"-mcall-ms2sysv-xlogues",
|
|
"-mdispatch-scheduler",
|
|
"-mfpmath=sse+387",
|
|
"-mmitigate-rop",
|
|
"-mno-fentry",
|
|
};
|
|
|
|
char *DescribeCommand(void) {
|
|
if (iscc) {
|
|
if (isgcc) {
|
|
return xasprintf("gcc %d", ccversion);
|
|
} else if (isclang) {
|
|
return xasprintf("clang %d", ccversion);
|
|
}
|
|
}
|
|
return basename(cmd);
|
|
}
|
|
|
|
bool IsSafeEnv(const char *s) {
|
|
const char *p;
|
|
int n, m, l, r, x;
|
|
p = strchr(s, '=');
|
|
n = p ? p - s : -1;
|
|
l = 0;
|
|
r = ARRAYLEN(kSafeEnv) - 1;
|
|
while (l <= r) {
|
|
m = (l + r) >> 1;
|
|
x = strncmp(s, kSafeEnv[m], n);
|
|
if (x < 0) {
|
|
r = m - 1;
|
|
} else if (x > 0) {
|
|
l = m + 1;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsGccOnlyFlag(const char *s) {
|
|
int m, l, r, x;
|
|
l = 0;
|
|
r = ARRAYLEN(kGccOnlyFlags) - 1;
|
|
while (l <= r) {
|
|
m = (l + r) >> 1;
|
|
x = strcmp(s, kGccOnlyFlags[m]);
|
|
if (x < 0) {
|
|
r = m - 1;
|
|
} else if (x > 0) {
|
|
l = m + 1;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
if (startswith(s, "-ffixed-")) return true;
|
|
if (startswith(s, "-fcall-saved")) return true;
|
|
if (startswith(s, "-fcall-used")) return true;
|
|
if (startswith(s, "-fgcse-")) return true;
|
|
if (startswith(s, "-fvect-cost-model=")) return true;
|
|
if (startswith(s, "-fsimd-cost-model=")) return true;
|
|
if (startswith(s, "-fopt-info")) return true;
|
|
if (startswith(s, "-mstringop-strategy=")) return true;
|
|
if (startswith(s, "-mpreferred-stack-boundary=")) return true;
|
|
if (startswith(s, "-Wframe-larger-than=")) return true;
|
|
return false;
|
|
}
|
|
|
|
bool FileExistsAndIsNewerThan(const char *filepath, const char *thanpath) {
|
|
struct stat st1, st2;
|
|
if (stat(filepath, &st1) == -1) return false;
|
|
if (stat(thanpath, &st2) == -1) return false;
|
|
if (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) return false;
|
|
if (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) return true;
|
|
return st1.st_mtim.tv_nsec >= st2.st_mtim.tv_nsec;
|
|
}
|
|
|
|
void AddEnv(char *s) {
|
|
env.p = realloc(env.p, ++env.n * sizeof(*env.p));
|
|
env.p[env.n - 1] = s;
|
|
}
|
|
|
|
void AddArg(char *s) {
|
|
size_t n;
|
|
args.p = realloc(args.p, ++args.n * sizeof(*args.p));
|
|
args.p[args.n - 1] = s;
|
|
if (s) {
|
|
n = strlen(s);
|
|
if (command.n) {
|
|
command.p = realloc(command.p, command.n + 1 + n);
|
|
command.p[command.n] = ' ';
|
|
memcpy(command.p + command.n + 1, s, n);
|
|
command.n += 1 + n;
|
|
} else {
|
|
command.p = realloc(command.p, command.n + n);
|
|
memcpy(command.p + command.n, s, n);
|
|
command.n += n;
|
|
}
|
|
} else {
|
|
command.p = realloc(command.p, command.n + 2);
|
|
command.p[command.n++] = '\r';
|
|
command.p[command.n++] = '\n';
|
|
}
|
|
}
|
|
|
|
int Launch(void) {
|
|
int ws, pid;
|
|
if ((pid = vfork()) == -1) exit(errno);
|
|
if (!pid) {
|
|
sigprocmask(SIG_SETMASK, &savemask, NULL);
|
|
execve(cmd, args.p, env.p);
|
|
_exit(127);
|
|
}
|
|
while (waitpid(pid, &ws, 0) == -1) {
|
|
if (errno != EINTR) exit(errno);
|
|
}
|
|
return ws;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
size_t n;
|
|
char *p, **envp;
|
|
int i, ws, rc, opt;
|
|
|
|
/*
|
|
* parse prefix arguments
|
|
*/
|
|
while ((opt = getopt(argc, argv, "?hntA:T:V:")) != -1) {
|
|
switch (opt) {
|
|
case 'n':
|
|
exit(0);
|
|
case 't':
|
|
touchtarget = true;
|
|
break;
|
|
case 'A':
|
|
action = optarg;
|
|
break;
|
|
case 'T':
|
|
target = optarg;
|
|
break;
|
|
case 'V':
|
|
ccversion = atoi(optarg);
|
|
break;
|
|
case '?':
|
|
case 'h':
|
|
write(1, MANUAL, sizeof(MANUAL) - 1);
|
|
exit(0);
|
|
default:
|
|
write(2, MANUAL, sizeof(MANUAL) - 1);
|
|
exit(1);
|
|
}
|
|
}
|
|
if (optind == argc) {
|
|
write(2, MANUAL, sizeof(MANUAL) - 1);
|
|
exit(1);
|
|
}
|
|
|
|
cmd = argv[optind];
|
|
if (!strchr(cmd, '/')) {
|
|
if (!(cmd = commandv(cmd, ccpath))) exit(127);
|
|
}
|
|
|
|
isgcc = !!strstr(basename(cmd), "gcc");
|
|
isclang = !!strstr(basename(cmd), "clang");
|
|
iscc = isgcc | isclang;
|
|
|
|
/*
|
|
* ingest arguments
|
|
*/
|
|
for (i = optind; i < argc; ++i) {
|
|
if (argv[i][0] != '-') {
|
|
AddArg(argv[i]);
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i], "-o")) {
|
|
AddArg(argv[i]);
|
|
AddArg((outpath = argv[++i]));
|
|
continue;
|
|
}
|
|
if (!iscc) {
|
|
AddArg(argv[i]);
|
|
continue;
|
|
}
|
|
if (isclang && IsGccOnlyFlag(argv[i])) {
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i], "-w")) {
|
|
AddArg(argv[i]);
|
|
AddArg("-D__W__");
|
|
} else if (!strcmp(argv[i], "-Oz")) {
|
|
if (isclang) {
|
|
AddArg(argv[i]);
|
|
} else {
|
|
AddArg("-Os");
|
|
}
|
|
} else if (!strcmp(argv[i], "-pg")) {
|
|
wantpg = true;
|
|
} else if (!strcmp(argv[i], "-x-no-pg")) {
|
|
wantnopg = true;
|
|
} else if (!strcmp(argv[i], "-mfentry")) {
|
|
wantfentry = true;
|
|
} else if (!strcmp(argv[i], "-mnop-mcount")) {
|
|
wantnop = true;
|
|
} else if (!strcmp(argv[i], "-mrecord-mcount")) {
|
|
wantrecord = true;
|
|
} else if (!strcmp(argv[i], "-fno-omit-frame-pointer")) {
|
|
wantframe = true;
|
|
} else if (!strcmp(argv[i], "-fomit-frame-pointer")) {
|
|
wantframe = false;
|
|
} else if (!strcmp(argv[i], "-mno-vzeroupper")) {
|
|
if (isgcc) {
|
|
AddArg("-Wa,-msse2avx");
|
|
AddArg("-D__MNO_VZEROUPPER__");
|
|
} else if (isclang) {
|
|
AddArg("-mllvm");
|
|
AddArg("-x86-use-vzeroupper=0");
|
|
}
|
|
} else if (!strcmp(argv[i], "-msse2avx")) {
|
|
if (isgcc) {
|
|
AddArg(argv[i]);
|
|
} else if (isclang) {
|
|
AddArg("-Wa,-msse2avx");
|
|
}
|
|
} else if (!strcmp(argv[i], "-fsanitize=address")) {
|
|
if (isgcc && ccversion >= 6) wantasan = true;
|
|
} else if (!strcmp(argv[i], "-fsanitize=undefined")) {
|
|
if (isgcc && ccversion >= 6) wantubsan = true;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=address")) {
|
|
wantasan = false;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=undefined")) {
|
|
wantubsan = false;
|
|
} else if (!strcmp(argv[i], "-fno-sanitize=all")) {
|
|
wantasan = false;
|
|
wantubsan = false;
|
|
} else if (startswith(argv[i], "-fsanitize=implicit") &&
|
|
strstr(argv[i], "integer")) {
|
|
if (isgcc) AddArg(argv[i]);
|
|
} else if (startswith(argv[i], "-fvect-cost") ||
|
|
startswith(argv[i], "-mstringop") ||
|
|
startswith(argv[i], "-gz") ||
|
|
strstr(argv[i], "stack-protector") ||
|
|
strstr(argv[i], "sanitize") ||
|
|
startswith(argv[i], "-fvect-cost") ||
|
|
startswith(argv[i], "-fvect-cost")) {
|
|
if (isgcc && ccversion >= 6) {
|
|
AddArg(argv[i]);
|
|
}
|
|
} else if (startswith(argv[i], "-fdiagnostic-color=")) {
|
|
colorflag = argv[i];
|
|
} else if (startswith(argv[i], "-R") ||
|
|
!strcmp(argv[i], "-fsave-optimization-record")) {
|
|
if (isclang) AddArg(argv[i]);
|
|
} else if (isclang && startswith(argv[i], "--debug-prefix-map")) {
|
|
/* llvm doesn't provide a gas interface so simulate w/ clang */
|
|
AddArg(xasprintf("-f%s", argv[i] + 2));
|
|
} else {
|
|
AddArg(argv[i]);
|
|
}
|
|
}
|
|
if (!outpath) {
|
|
outpath = target;
|
|
}
|
|
|
|
/*
|
|
* append special args
|
|
*/
|
|
if (iscc) {
|
|
if (isclang) {
|
|
AddArg("-Wno-unused-command-line-argument");
|
|
AddArg("-Wno-incompatible-pointer-types-discards-qualifiers");
|
|
}
|
|
AddArg("-no-canonical-prefixes");
|
|
if (!IsTerminalInarticulate()) {
|
|
AddArg(firstnonnull(colorflag, "-fdiagnostics-color=always"));
|
|
}
|
|
if (wantpg && !wantnopg) {
|
|
AddArg("-pg");
|
|
AddArg("-D__PG__");
|
|
if (wantnop && !isclang) {
|
|
AddArg("-mnop-mcount");
|
|
AddArg("-D__MNOP_MCOUNT__");
|
|
}
|
|
if (wantrecord) {
|
|
AddArg("-mrecord-mcount");
|
|
AddArg("-D__MRECORD_MCOUNT__");
|
|
}
|
|
if (wantfentry) {
|
|
AddArg("-mfentry");
|
|
AddArg("-D__MFENTRY__");
|
|
}
|
|
}
|
|
if (wantasan) {
|
|
AddArg("-fsanitize=address");
|
|
AddArg("-D__FSANITIZE_ADDRESS__");
|
|
}
|
|
if (wantubsan) {
|
|
AddArg("-fsanitize=undefined");
|
|
AddArg("-fno-data-sections");
|
|
}
|
|
if (wantframe) {
|
|
AddArg("-fno-omit-frame-pointer");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* terminate args
|
|
*/
|
|
AddArg(NULL);
|
|
|
|
/*
|
|
* scrub environment for determinism and great justice
|
|
*/
|
|
for (envp = environ; *envp; ++envp) {
|
|
if (IsSafeEnv(*envp)) {
|
|
AddEnv(*envp);
|
|
}
|
|
}
|
|
AddEnv("LC_ALL=C");
|
|
AddEnv("SOURCE_DATE_EPOCH=0");
|
|
|
|
/*
|
|
* ensure output directory exists
|
|
*/
|
|
if (outpath) {
|
|
outdir = xdirname(outpath);
|
|
if (!isdirectory(outdir)) {
|
|
makedirs(outdir, 0755);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* log command being run
|
|
*/
|
|
if (!strcmp(nulltoempty(getenv("V")), "0") && !IsTerminalInarticulate()) {
|
|
p = xasprintf("\e[F\e[K%-15s%s\r\n", firstnonnull(action, "BUILD"),
|
|
firstnonnull(target, nulltoempty(outpath)));
|
|
n = strlen(p);
|
|
} else {
|
|
if (IsTerminalInarticulate() &&
|
|
(columns = atoi(nulltoempty(getenv("COLUMNS")))) > 25 &&
|
|
command.n > columns + 2) {
|
|
/* emacs command window is very slow so truncate lines */
|
|
command.n = columns + 2;
|
|
command.p[command.n - 5] = '.';
|
|
command.p[command.n - 4] = '.';
|
|
command.p[command.n - 3] = '.';
|
|
command.p[command.n - 2] = '\r';
|
|
command.p[command.n - 1] = '\n';
|
|
}
|
|
p = command.p;
|
|
n = command.n;
|
|
}
|
|
write(2, p, n);
|
|
|
|
/*
|
|
* create temporary copy when launching APE binaries
|
|
* and we help FindDebugBinary to find debug symbols
|
|
*/
|
|
if (!IsWindows() && endswith(cmd, ".com")) {
|
|
comdbg = xasprintf("%s.dbg", cmd);
|
|
cachedcmd = xasprintf("o/%s", cmd);
|
|
if (fileexists(comdbg)) {
|
|
AddEnv(xasprintf("COMDBG=%s", comdbg));
|
|
}
|
|
if (FileExistsAndIsNewerThan(cachedcmd, cmd)) {
|
|
cmd = cachedcmd;
|
|
} else {
|
|
if (startswith(cmd, "o/")) {
|
|
cachedcmd = NULL;
|
|
}
|
|
originalcmd = cmd;
|
|
cmd = xasprintf("%s.tmp.%d", originalcmd, getpid());
|
|
copyfile(originalcmd, cmd, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* terminate environment
|
|
*/
|
|
AddEnv(NULL);
|
|
|
|
/*
|
|
* launch command
|
|
*/
|
|
sigfillset(&mask);
|
|
sigprocmask(SIG_BLOCK, &mask, &savemask);
|
|
ws = Launch();
|
|
|
|
/*
|
|
* if execve() failed unzip gcc and try again
|
|
*/
|
|
if (WIFEXITED(ws) && WEXITSTATUS(ws) == 127 &&
|
|
startswith(cmd, "o/third_party/gcc") &&
|
|
fileexists("third_party/gcc/unbundle.sh")) {
|
|
system("third_party/gcc/unbundle.sh");
|
|
ws = Launch();
|
|
}
|
|
|
|
/*
|
|
* cleanup temporary copy of ape executable
|
|
*/
|
|
if (originalcmd) {
|
|
if (cachedcmd && WIFEXITED(ws) && !WEXITSTATUS(ws)) {
|
|
makedirs(xdirname(cachedcmd), 0755);
|
|
rename(cmd, cachedcmd);
|
|
} else {
|
|
unlink(cmd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* propagate exit
|
|
*/
|
|
if (WIFEXITED(ws)) {
|
|
if (!WEXITSTATUS(ws)) {
|
|
if (touchtarget && target) {
|
|
makedirs(xdirname(target), 0755);
|
|
touch(target, 0644);
|
|
}
|
|
return 0;
|
|
} else {
|
|
p = xasprintf("%s%s EXITED WITH %d%s: %.*s\r\n", RED2, DescribeCommand(),
|
|
WEXITSTATUS(ws), RESET, command.n, command.p);
|
|
rc = WEXITSTATUS(ws);
|
|
}
|
|
} else {
|
|
p = xasprintf("%s%s TERMINATED BY %s%s: %.*s\r\n", RED2, DescribeCommand(),
|
|
strsignal(WTERMSIG(ws)), RESET, command.n, command.p);
|
|
rc = 128 + WTERMSIG(ws);
|
|
}
|
|
|
|
/*
|
|
* print full command in the event of error
|
|
*/
|
|
write(2, p, strlen(p));
|
|
return rc;
|
|
}
|