1
0
mirror of https://github.com/skeeto/w64devkit.git synced 2026-10-23 06:08:56 +03:00

WIP New commands: compdb-cc and compdb-c++

This commit is contained in:
Christopher Wellons
2025-10-14 19:12:00 -04:00
parent f07ad376e2
commit f7dcb988ee
2 changed files with 665 additions and 1 deletions

View File

@@ -58,7 +58,7 @@ RUN sha256sum -c $PREFIX/src/SHA256SUMS \
&& tar xjf vim-$VIM_VERSION.tar.bz2
COPY src/w64devkit.c src/w64devkit.ico src/libmemory.c src/libchkstk.S \
src/alias.c src/debugbreak.c src/pkg-config.c src/vc++filt.c \
src/peports.c src/profile $PREFIX/src/
src/peports.c src/compdb-cc.c src/profile $PREFIX/src/
ARG ARCH=x86_64-w64-mingw32
@@ -514,6 +514,9 @@ RUN printf "id ICON \"$PREFIX/src/w64devkit.ico\"" >w64devkit.rc \
&& $ARCH-gcc -DEXE=pkg-config.exe -DCMD=pkg-config \
-Os -fno-asynchronous-unwind-tables -Wl,--gc-sections -s -nostdlib \
-o $PREFIX/bin/$ARCH-pkg-config.exe $PREFIX/src/alias.c -lkernel32 \
&& $ARCH-gcc -Os -s -nostartfiles -o $PREFIX/bin/compdb-cc.exe \
$PREFIX/src/compdb-cc.c -lmemory \
&& cp $PREFIX/bin/compdb-cc.exe $PREFIX/bin/compdb-c++.exe \
&& sed -i s/'\<ARCH\>'/$ARCH/g $PREFIX/src/profile \
&& mkdir -p $PREFIX/lib/pkgconfig \
&& cp /mingw-w64-v$MINGW_VERSION/COPYING.MinGW-w64-runtime/COPYING.MinGW-w64-runtime.txt \

661
src/compdb-cc.c Normal file
View File

@@ -0,0 +1,661 @@
// compdb-cc: JSON Compilation Database build wrapper
//
// Generates a compile_commands.json for arbitrary build systems by wrapping
// the compiler program and logging commands to a database. The database is
// UTF-8-encoded (assuming UTF-8 inputs on POSIX) with LF line endings on all
// platforms. Supports parallel builds.
//
// w64dk: $ cc -nostartfiles -o compdb-cc.exe compdb-cc.c
// unix: $ cc -o compdb-cc compdb-cc.c
// msvc: $ cl compdb-cc.c
//
// First, delete any existing database. This tool cannot update arbitrary
// databases, only those it has created and exclusively modified. Then run
// the build with $COMPDB_PATH set to the absolute path of the database:
//
// $ rm -f compile_commands.json
// $ COMPDB_PATH="$PWD"/compile_commands.json make -B CC=compdb-cc
//
// The wrapper may be called with different working directories by the build
// system, in which case an absolute path will be necessary in order to know
// the proper location. If $COMPDB_PATH is unset or empty, the wrapper runs
// the command without and database updates, and so it can be harmlessly left
// as the compiler during development.
//
// The underlying compiler is derived from the suffix, so compdb-cc runs cc,
// compdb-c++ runs c++, etc. Rename or symlink appropriately.
//
// Ref: https://clang.llvm.org/docs/JSONCompilationDatabase.html
// This is free and unencumbered software released into the public domain.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#if _MSC_VER
# define unreachable() __assume(0)
# pragma comment(linker, "/subsystem:console")
# pragma comment(lib, "kernel32")
# pragma comment(lib, "shell32")
# pragma comment(lib, "libvcruntime")
#endif
#define lenof(a) (iz)(sizeof(a) / sizeof(*(a)))
#define affirm(c) while (!(c)) unreachable()
#define S(s) (Str){(u8 *)s, lenof(s)-1}
#define new(a, n, t) (t *)alloc(a, n, sizeof(t), _Alignof(t))
typedef uint8_t u8;
typedef int16_t i16;
typedef int32_t b32;
typedef int32_t i32;
typedef int64_t i64;
typedef ptrdiff_t iz;
typedef size_t uz;
// Platform API
//
// Platform layer opens $COMPDB_PATH, locks it, seeks SEEK_SIZE bytes
// from the end (if possible), and calls compdb(). The application layer
// calls plt_write() zero or more times. If returns with STATUS_OK, the
// platform layer unlocks the file and execs the remaining command line.
enum {
STATUS_OK = 0,
STATUS_USAGE = 200,
STATUS_EXEC = 201,
STATUS_OPEN = 202,
STATUS_WRITE = 203,
STATUS_OOM = 204,
SEEK_SIZE = -3,
};
typedef struct Plt Plt;
static b32 plt_write(Plt *, i32 fd, u8 *, i32);
static void plt_oom();
// Application layer
static uz touz(iz n)
{
affirm(n >= 0);
return (uz)n;
}
typedef struct {
u8 *beg;
u8 *end;
} Arena;
static void *alloc(Arena *a, iz count, iz size, iz align)
{
iz pad = (iz)-(uz)a->beg & (align - 1);
if (count >= (a->end - a->beg - pad)/size) {
plt_oom();
}
u8 *r = a->beg + pad;
a->beg += pad + count*size;
return memset(r, 0, touz(size*count));
}
typedef struct {
u8 *data;
iz len;
} Str;
static Str import(u8 *s)
{
Str r = {0};
r.data = s;
for (; r.data[r.len]; r.len++) {}
return r;
}
static b32 equals(Str a, Str b)
{
return a.len==b.len && !memcmp(a.data, b.data, touz(a.len));
}
static Str substring(Str s, iz i)
{
affirm(i>=0 && i<=s.len);
s.data += i;
s.len -= i;
return s;
}
static b32 endswith(Str s, Str suf)
{
return s.len>=suf.len && equals(substring(s, s.len-suf.len), suf);
}
typedef struct {
Plt *plt;
i32 len;
i32 fd;
b32 err;
u8 buf[1<<12];
} Output;
static Output *newoutput(Arena *a, Plt *plt, i32 fd)
{
Output *b = new(a, 1, Output);
b->plt = plt;
b->fd = fd;
return b;
}
static void flush(Output *b)
{
if (!b->err && b->len) {
b->err = !plt_write(b->plt, b->fd, b->buf, b->len);
}
b->len = 0;
}
static void print(Output *b, Str s)
{
for (iz off = 0; !b->err && off<s.len;) {
i32 avail = (i32)lenof(b->buf) - b->len;
i32 count = avail<s.len-off ? avail : (i32)(s.len-off);
memcpy(b->buf+b->len, s.data+off, touz(count));
off += count;
b->len += count;
if (b->len == lenof(b->buf)) {
flush(b);
}
}
}
static void printu8(Output *b, u8 c)
{
print(b, (Str){&c, 1});
}
// Print a JSON-quoted representation of the string.
static void printq(Output *b, Str s)
{
printu8(b, '"');
for (iz i = 0; i < s.len; i++) {
u8 c = s.data[i];
if (c == '\n') {
print(b, S("\\n"));
} else if (c == '\t') {
print(b, S("\\t"));
} else if (c == '\r') {
print(b, S("\\r"));
} else if (c=='\\' || c=='"') {
printu8(b, '\\');
printu8(b, c);
} else if (c < ' ') {
print(b, S("\\u00"));
static u8 hex[] = "0123456789abcdef";
printu8(b, hex[c>>4]);
printu8(b, hex[c&15]);
} else {
printu8(b, c);
}
}
printu8(b, '"');
}
typedef struct {
i32 argc;
Str *argv;
Str cwd;
} Config;
static void perfile(Output *out, Config conf, Str file, b32 first)
{
printu8(out, first ? '[' : ',');
print(out, S("\n {"));
print(out, S("\"file\": "));
printq(out, file);
print(out, S(", \"directory\": "));
printq(out, conf.cwd);
print(out, S(", \"arguments\": ["));
for (i32 i = 0; i < conf.argc; i++) {
if (i) {
print(out, S(", "));
}
printq(out, conf.argv[i]);
}
print(out, S("]}"));
}
static b32 match(Str arg)
{
return endswith(arg, S(".c"))
|| endswith(arg, S(".C"))
|| endswith(arg, S(".cc"))
|| endswith(arg, S(".cpp"))
|| endswith(arg, S(".s"))
|| endswith(arg, S(".S"));
}
static Str cmd_suffix(Str s)
{
iz cut = s.len;
for (; cut && s.data[cut-1]!='-'; cut--) {}
return (Str){s.data+cut, s.len-cut};
}
static u8 usage[] =
"usage: compdb-cc [CMD ...]\n"
"example: COMPDB_PATH=\"$PWD\"/compile_commands.json make -B CC=compdb-cc\n"
" When $COMPDB_PATH is set, updates the named file with build commands.\n";
static i32 compdb(Plt *plt, Arena *a, i32 argc, u8 **argv, Str cwd, b32 first)
{
Output *out = newoutput(a, plt, 1);
Output *err = newoutput(a, plt, 2);
Config conf = {};
conf.argc = argc;
conf.argv = new(a, argc, Str);
conf.cwd = cwd;
for (i32 i = 0; i < argc; i++) {
conf.argv[i] = import(argv[i]);
if (i == 0) {
conf.argv[i] = cmd_suffix(conf.argv[i]);
}
}
if (argc < 2) {
print(err, S(usage));
if (conf.argc) {
print(err, S(" This program wraps the compiler named \""));
print(err, conf.argv[0]);
print(err, S("\".\n"));
}
flush(err);
return STATUS_USAGE;
}
i32 count = 0;
for (i32 i = 1; i < argc; i++) {
Str arg = conf.argv[i];
if (match(arg)) {
count++;
perfile(out, conf, arg, first);
first = 0;
}
}
if (count) {
print(out, S("\n]\n"));
}
flush(out);
return out->err ? STATUS_WRITE : STATUS_OK;
}
// Platform layer
#if _WIN32
typedef uint16_t char16_t;
typedef char16_t c16;
enum {
CP_UTF8 = 65001,
OPEN_ALWAYS = 4,
FILE_ATTRIBUTE_NORMAL = 0x80,
FILE_SHARE_ALL = 7,
GENERIC_WRITE = 0x40000000,
FILE_END = 2,
LOCKFILE_EXCLUSIVE_LOCK = 2,
};
typedef struct {
i32 cb;
uz a, b, c;
i32 d, e, f, g, h, i, j, k;
i16 l, m;
uz n, o, p, q;
} si;
typedef struct {
uz process;
uz thread;
i32 pid;
i32 tid;
} pi;
#define W32(r) __declspec(dllimport) r __stdcall
W32(b32) CloseHandle(uz);
W32(c16 **) CommandLineToArgvW(c16 *, i32 *);
W32(uz) CreateFileW(c16 *, i32, i32, uz, i32, i32, uz);
W32(b32) CreateProcessW(c16*,c16*,void*,void*,b32,i32,c16*,c16*,si*,pi*);
W32(void) ExitProcess(i32);
W32(c16 *) GetCommandLineW();
W32(i32) GetCurrentDirectoryW(i32, c16 *);
W32(i32) GetEnvironmentVariableW(c16 *, c16 *, i32);
W32(i32) GetExitCodeProcess(uz, i32 *);
W32(uz) GetStdHandle(i32);
W32(b32) LockFileEx(uz, i32, i32, i32, i32, uz[4]);
W32(b32) SetFilePointerEx(uz, i64, i64 *, i32);
W32(i32) WaitForSingleObject(uz, i32);
W32(i32) WideCharToMultiByte(i32, i32, c16 *, i32, u8 *, i32, uz, uz);
W32(b32) WriteFile(uz, u8 *, i32, i32 *, uz);
struct Plt {
uz handle[3];
uz err;
};
static b32 plt_write(Plt *plt, i32 fd, u8 *buf, i32 len)
{
return WriteFile(plt->handle[fd], buf, len, &(i32){}, 0);
}
static void plt_oom()
{
ExitProcess(STATUS_OOM);
unreachable();
}
#define U(s) (S16){s, lenof(s)-1}
typedef struct {
c16 *data;
iz len;
} S16;
static S16 wimport(c16 *s)
{
S16 r = {};
r.data = s;
for (; r.data[r.len]; r.len++) {}
return r;
}
static S16 wclone(Arena *a, S16 s)
{
S16 r = s;
r.data = new(a, s.len, c16);
for (iz i = 0; i < r.len; i++) {
r.data[i] = s.data[i];
}
return r;
}
static S16 wconcat(Arena *a, S16 head, S16 tail)
{
if (a->beg != (u8 *)(head.data+head.len)) {
head = wclone(a, head);
}
head.len += wclone(a, tail).len;
return head;
}
static S16 wslice(S16 s, iz beg, iz end)
{
affirm(beg >= 0 && beg <= end && end <= s.len);
s.data += beg;
s.len = end - beg;
return s;
}
typedef struct {
S16 cmd;
S16 args;
} Args;
static Args split(S16 s)
{
Args r = {};
iz beg = 0;
iz cut = 0;
if (s.len && s.data[0] == '"') {
cut = beg = 1;
for (; cut < s.len; cut++) {
if (s.data[cut] == '"') {
break;
}
}
r.cmd = wslice(s, beg, cut);
cut += cut < s.len;
r.args = wslice(s, cut, s.len);
} else {
for (; cut < s.len; cut++) {
if (s.data[cut]==' ' || s.data[cut]=='\t') {
break;
}
}
r.cmd = wslice(s, beg, cut);
r.args = wslice(s, cut, s.len);
}
return r;
}
static S16 wcmd_suffix(S16 s)
{
iz cut = s.len;
for (; cut && s.data[cut-1]!='-'; cut--) {}
return wslice(s, cut, s.len);
}
static Str encode(Arena *a, c16 *s)
{
Str r = {};
i32 len = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, 0, 0);
if (len) {
r.data = new(a, len, u8);
r.len = len - 1;
WideCharToMultiByte(CP_UTF8, 0, s, -1, r.data, len, 0, 0);
}
return r;
}
static Str workingdir(Arena *a)
{
i32 len = GetCurrentDirectoryW(0, 0);
c16 *buf = new(a, len, c16);
GetCurrentDirectoryW(len, buf);
return encode(a, buf);
}
static c16 *envvar(Arena *a, c16 *key)
{
i32 len = GetEnvironmentVariableW(key, 0, 0);
if (!len) {
return 0;
}
c16 *buf = new(a, len, c16);
GetEnvironmentVariableW(key, buf, len);
return buf;
}
static i32 exec(uz err, c16 *cmd)
{
if (!*cmd) {
return STATUS_OK;
}
si si = {sizeof(si)};
pi pi = {};
if (!CreateProcessW(0, cmd, 0, 0, 1, 0, 0, 0, &si, &pi)) {
Str msg = S("compdb-cc: CreateProcessW() failed to exec command\n");
WriteFile(err, msg.data, (i32)msg.len, &(i32){}, 0);
return STATUS_EXEC;
}
WaitForSingleObject(pi.process, -1);
i32 r;
GetExitCodeProcess(pi.process, &r);
return r;
}
static i32 run(Arena *a)
{
uz err = GetStdHandle(-12);
c16 *cmd = GetCommandLineW();
i32 argc = 0;
c16 **wargv = CommandLineToArgvW(cmd, &argc);
u8 **argv = new(a, argc+1, u8 *);
for (i32 i = 0; i < argc; i++) {
argv[i] = encode(a, wargv[i]).data;
}
Plt *plt = new(a, 1, Plt);
plt->handle[1] = (uz)-1;
plt->handle[2] = err;
if (argc < 2) {
return compdb(plt, a, argc, argv, (Str){}, 0);
}
Args args = split(wimport(cmd));
S16 cc = wcmd_suffix(args.cmd);
if (cc.len == args.cmd.len) {
Str msg = S(
"compdb-cc: could not determine actual compiler from name\n"
);
WriteFile(err, msg.data, (i32)msg.len, &(i32){}, 0);
return STATUS_EXEC;
}
S16 realcmd = wconcat(a, cc, args.args);
realcmd = wconcat(a, realcmd, U(u"\0"));
c16 *path = envvar(a, u"COMPDB_PATH");
if (!path || !*path) {
return exec(err, realcmd.data);
}
Str cwd = workingdir(a);
i32 access = GENERIC_WRITE;
i32 share = FILE_SHARE_ALL;
i32 create = OPEN_ALWAYS;
i32 attr = FILE_ATTRIBUTE_NORMAL;
plt->handle[1] = CreateFileW(path, access, share, 0, create, attr, 0);
if (plt->handle[1] == (uz)-1) {
Str msg = S("compdb-cc: CreateFileW() failed to open database\n");
WriteFile(err, msg.data, (i32)msg.len, &(i32){}, 0);
return STATUS_OPEN;
}
LockFileEx(plt->handle[1], LOCKFILE_EXCLUSIVE_LOCK, 0, -1, -1, (uz[4]){});
b32 first = !SetFilePointerEx(plt->handle[1], SEEK_SIZE, 0, FILE_END);
i32 status = compdb(plt, a, argc, argv, cwd, first);
if (status) {
return status;
}
CloseHandle(plt->handle[1]); // unlock
return exec(err, realcmd.data);
}
i32 __stdcall mainCRTStartup()
{
static u8 mem[1<<21];
Arena a = {mem, mem+lenof(mem)};
i32 r = run(&a);
ExitProcess(r);
unreachable();
}
#else // POSIX
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <unistd.h>
struct Plt {
int fds[3];
};
static b32 plt_write(Plt *plt, i32 fd, u8 *buf, i32 len)
{
return write(plt->fds[fd], buf, touz(len)) == len;
}
static void plt_oom()
{
exit(STATUS_OOM);
}
static int exec(int argc, char **argv)
{
if (argc < 1) {
return STATUS_OK;
}
execvp(argv[0], argv);
fprintf(stderr, "compdb-cc: execvp(%s): %s\n", argv[1], strerror(errno));
return STATUS_EXEC;
}
static Str workingdir(Arena *a)
{
Str r = {};
if (getcwd((char *)a->beg, touz(a->end-a->beg))) {
r = import(a->beg);
a->beg += r.len + 1;
}
return r;
}
int main(int argc, char **argv)
{
static u8 mem[1<<21];
Arena a = {mem, mem+lenof(mem)};
Plt plt = {{0, 1, 2}};
if (argc < 2) {
return compdb(&plt, &a, argc, (u8 **)argv, (Str){}, 0);
}
Str argv0 = import((u8 *)argv[0]);
Str cc = cmd_suffix(argv0);
if (cc.len == argv0.len) {
fprintf(
stderr,
"compdb-cc: could not determine actual compiler from name, %s\n",
argv[0]
);
return STATUS_EXEC;
}
char **new_argv = new(&a, argc+1, char *);
new_argv[0] = (char *)cc.data;
for (int i = 1; i < argc; i++) {
new_argv[i] = argv[i];
}
char *path = getenv("COMPDB_PATH");
if (!path || !*path) {
return exec(argc, new_argv);
}
Str cwd = workingdir(&a);
plt.fds[1] = open(path, O_CREAT|O_RDWR, 0666);
if (plt.fds[1] == -1) {
fprintf(stderr, "compdb-cc: open(%s): %s\n", path, strerror(errno));
return STATUS_OPEN;
}
flock(plt.fds[1], LOCK_EX);
b32 first = lseek(plt.fds[1], SEEK_SIZE, SEEK_END) == -1;
i32 status = compdb(&plt, &a, argc, (u8 **)argv, cwd, first);
if (status) {
return status;
}
close(plt.fds[1]); // unlock
return exec(argc, new_argv);
}
#endif