cosmopolitan/tool/build/mkdeps.c

318 lines
10 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 2020 Justine Alexandra Roberts Tunney │
│ │
│ This program is free software; you can redistribute it and/or modify │
│ it under the terms of the GNU General Public License as published by │
│ the Free Software Foundation; version 2 of the License. │
│ │
│ This program is distributed in the hope that it will be useful, but │
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
│ General Public License for more details. │
│ │
│ You should have received a copy of the GNU General Public License │
│ along with this program; if not, write to the Free Software │
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/alg/arraylist.h"
#include "libc/alg/arraylist2.h"
#include "libc/alg/bisectcarleft.h"
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/runtime/ezmap.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/knuthmultiplicativehash.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/madv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.h"
/**
* @fileoverview Make dependency generator.
*
* This program generates Makefile code saying which sources include
* which headers, thus improving build invalidation.
*
* The same thing can be accomplished using GCC's -M flag. This tool is
* much faster. It's designed to process over 9,000 sources to generate
* 50k+ lines of make code in ~80ms using one core and a meg of ram.
*/
static const char kSourceExts[][5] = {".s", ".S", ".c", ".cc", ".cpp"};
static alignas(16) const char kIncludePrefix[] = "include \"";
static const char *const kModelessPackages[] = {
"libc/nt/",
"libc/stubs/",
"libc/sysv/",
};
struct Strings {
size_t i, n;
char *p;
};
struct Source {
uint32_t hash; /* 0 means empty w/ triangle probe */
uint32_t name; /* strings.p[name] w/ interning */
uint32_t id; /* rehashing changes indexes */
};
struct Edge {
int32_t from; /* sources.p[from.id] */
int32_t to; /* sources.p[to.id] */
};
struct Sources {
size_t i, n; /* phase 1: hashmap: popcnt(n)==1 if n */
struct Source *p; /* phase 2: arraylist sorted by id */
};
struct Edges {
size_t i, n;
struct Edge *p;
};
int g_sourceid;
struct Strings strings;
struct Sources sources;
struct Edges edges;
const char *buildroot;
int *visited;
char *out;
FILE *fout;
static int CompareSourcesById(struct Source *a, struct Source *b) {
return a->id > b->id ? 1 : a->id < b->id ? -1 : 0;
}
static int CompareEdgesByFrom(struct Edge *a, struct Edge *b) {
return a->from > b->from ? 1 : a->from < b->from ? -1 : 0;
}
static uint32_t Hash(const void *s, size_t l) {
return max(1, crc32c(0, s, l));
}
void Crunch(void) {
size_t i, j;
for (i = 0, j = 0; j < sources.n; ++j) {
if (!sources.p[j].hash) continue;
if (i != j) memcpy(&sources.p[i], &sources.p[j], sizeof(sources.p[j]));
i++;
}
sources.i = i;
qsort(sources.p, sources.i, sizeof(*sources.p), (void *)CompareSourcesById);
qsort(edges.p, edges.i, sizeof(*edges.p), (void *)CompareEdgesByFrom);
}
void Rehash(void) {
size_t i, j, step;
struct Sources old;
memcpy(&old, &sources, sizeof(sources));
sources.n = sources.n ? sources.n << 1 : 16;
sources.p = calloc(sources.n, sizeof(struct Source));
for (i = 0; i < old.n; ++i) {
if (!old.p[i].hash) continue;
step = 0;
do {
j = (old.p[i].hash + step * (step + 1) / 2) & (sources.n - 1);
step++;
} while (sources.p[j].hash);
memcpy(&sources.p[j], &old.p[i], sizeof(old.p[i]));
}
free(old.p);
}
uint32_t GetSourceId(const char *name, size_t len) {
size_t i, step;
uint32_t hash;
i = 0;
hash = Hash(name, len);
if (sources.n) {
step = 0;
do {
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
if (sources.p[i].hash == hash &&
memcmp(name, &strings.p[sources.p[i].name], len) == 0) {
return sources.p[i].id;
}
step++;
} while (sources.p[i].hash);
}
if (++sources.i >= (sources.n >> 1)) {
Rehash();
step = 0;
do {
i = (hash + step * (step + 1) / 2) & (sources.n - 1);
step++;
} while (sources.p[i].hash);
}
sources.p[i].hash = hash;
sources.p[i].name = CONCAT(&strings.p, &strings.i, &strings.n, name, len);
strings.p[strings.i++] = '\0';
return (sources.p[i].id = g_sourceid++);
}
void LoadRelationships(int argc, char *argv[]) {
struct MappedFile mf;
const char *p, *pe;
size_t i, linecap = 0;
char *line = NULL;
FILE *finpaths;
for (i = optind; i < argc; ++i) {
CHECK_NOTNULL((finpaths = fopen(argv[i], "r")));
while (getline(&line, &linecap, finpaths) != -1) {
if (mapfileread(chomp(line), &mf) == -1) {
CHECK_EQ(ENOENT, errno, "%s", line);
/*
* This code helps GNU Make automatically fix itself when we
* delete a source file. It removes o/.../srcs.txt or
* o/.../hdrs.txt and exits nonzero. Since we use hyphen
* notation on mkdeps related rules, the build will
* automatically restart itself.
*/
fprintf(stderr, "%s %s...\n", "Refreshing", argv[i]);
unlink(argv[i]);
exit(1);
}
if (mf.size) {
if (mf.size > PAGESIZE) {
madvise(mf.addr, mf.size, MADV_WILLNEED | MADV_SEQUENTIAL);
}
uint32_t sauce = GetSourceId(line, strlen(line));
size_t inclen = strlen(kIncludePrefix);
p = mf.addr;
pe = p + mf.size;
while ((p = strstr(p, kIncludePrefix))) {
const char *path = p + inclen;
const char *pathend = memchr(path, '"', pe - path);
if (pathend && (intptr_t)p > (intptr_t)mf.addr &&
(p[-1] == '#' || p[-1] == '.') &&
(p - 1 == mf.addr || p[-2] == '\n')) {
uint32_t dependency = GetSourceId(path, pathend - path);
struct Edge edge;
edge.from = sauce;
edge.to = dependency;
append(&edges, &edge);
}
p = path;
}
}
CHECK_NE(-1, unmapfile(&mf));
}
CHECK_NE(-1, fclose(finpaths));
}
free_s(&line);
}
void GetOpts(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "ho:r:")) != -1) {
switch (opt) {
case 'o':
out = optarg;
break;
case 'r':
buildroot = optarg;
break;
default:
fprintf(stderr, "%s: %s [-r %s] [-o %s] [%s...]\n", "Usage", argv[0],
"BUILDROOT", "OUTPUT", "PATHSFILE");
exit(1);
}
}
if (isempty(out)) fprintf(stderr, "need -o FILE"), exit(1);
if (isempty(buildroot)) fprintf(stderr, "need -r o/$(MODE)"), exit(1);
}
const char *StripExt(const char *s) {
static bool once;
static size_t i, n;
static char *p, *dot;
if (!once) {
once = true;
__cxa_atexit(free_s, &p, NULL);
}
i = 0;
CONCAT(&p, &i, &n, s, strlen(s) + 1);
dot = strrchr(p, '.');
if (dot) *dot = '\0';
return p;
}
bool IsObjectSource(const char *name) {
for (size_t i = 0; i < ARRAYLEN(kSourceExts); ++i) {
if (endswith(name, kSourceExts[i])) return true;
}
return false;
}
void Dive(uint32_t sauce) {
for (uint32_t i = bisectcarleft((const int32_t(*)[2])edges.p, edges.i, sauce);
edges.p[i].from == sauce; ++i) {
int32_t dep = edges.p[i].to;
if (bts(visited, dep)) continue;
fputs(" \\\n\t", fout);
fputs(&strings.p[sources.p[dep].name], fout);
Dive(dep);
}
}
bool IsModeless(const char *path) {
size_t i;
for (i = 0; i < ARRAYLEN(kModelessPackages); ++i) {
if (startswith(path, kModelessPackages[i])) return true;
}
return false;
}
int main(int argc, char *argv[]) {
out = "/dev/stdout";
GetOpts(argc, argv);
char *tmp =
!fileexists(out) || isregularfile(out) ? xasprintf("%s.tmp", out) : NULL;
CHECK_NOTNULL((fout = fopen(tmp ? tmp : out, "w")));
LoadRelationships(argc, argv);
Crunch();
size_t bitmaplen = roundup((sources.i + 8) / 8, 4);
visited = malloc(bitmaplen);
for (size_t i = 0; i < sources.i; ++i) {
const char *path = &strings.p[sources.p[i].name];
if (!IsObjectSource(path)) continue;
bool needprefix = !startswith(path, "o/");
const char *prefix = !needprefix ? "" : IsModeless(path) ? "o/" : buildroot;
fprintf(fout, "\n%s%s.o: \\\n\t%s", prefix, StripExt(path), path);
memset(visited, 0, bitmaplen);
bts(visited, i);
Dive(i);
fprintf(fout, "\n");
}
if (fclose(fout) == -1) perror(out), exit(1);
if (tmp) {
if (rename(tmp, out) == -1) perror(out), exit(1);
}
free_s(&strings.p);
free_s(&sources.p);
free_s(&edges.p);
free_s(&visited);
free_s(&tmp);
return 0;
}