Skip to content

[PROPOSAL] make2compdb.c - #392

Open
g-berthiaume wants to merge 13 commits into
skeeto:masterfrom
g-berthiaume:make2compdb
Open

[PROPOSAL] make2compdb.c#392
g-berthiaume wants to merge 13 commits into
skeeto:masterfrom
g-berthiaume:make2compdb

Conversation

@g-berthiaume

Copy link
Copy Markdown

This PR introduces make2compdb.
A new CLI tool to generates Clang's JSON Compilation Database files (compiler_commands.json) from make build systems.

The API can be used in the following way:

$ make -Bwn | make2compdb.exe > compiler_commands.json
$ cat compiler_commands.json
[
  {
    "directory": "C:\\my_project",
    "file": "main.c",
    "output": "main",
    "arguments": [
      "gcc",
      "-o",
      "main",
      "main.c"
    ]
  }
]

Properties

  • Single C23 source file
  • Compiles on both Windows and Linux
  • Unit tests
  • Basic fuzzing (I'm pretty new to this)
  • CRT-free on Windows
  • Acceptably fast
Expand to see peports
$ peports make2compdb.exe
KERNEL32.dll
        0       ExitProcess
        0       GetCommandLineW
        0       GetConsoleMode
        0       GetCurrentDirectoryW
        0       GetStdHandle
        0       ReadFile
        0       VirtualAlloc
        0       WriteConsoleW
        0       WriteFile
SHELL32.dll
        0       CommandLineToArgvW
Expand to see performance benchmarks

While I'm sure, we could do better, the CLI tool seems to be acceptably fast.
It seems to be a bit faster on Linux than on Windows, but I'm not sure if I'm not just measuring piping speeds.

With a small project (linux)

$ hyperfine "./make2compdb < make_output.txt" --warmup 5
Benchmark 1: ./make2compdb < make_output.txt
  Time (mean ± σ):       5.8 ms ±   1.0 ms    [User: 1.1 ms, System: 0.9 ms]
  Range (min … max):     4.1 ms …  11.9 ms    321 runs

With a small project (windows)

$ hyperfine ".\make2compdb.exe < make_output.txt" --warmup 5
Benchmark 1: .\make2compdb.exe < make_output.txt
  Time (mean ± σ):      13.5 ms ±   1.0 ms    [User: 8.6 ms, System: 7.2 ms]
  Range (min … max):    12.3 ms …  18.1 ms    112 runs

With ffmpeg (windows)

$ hyperfine "make2compdb.exe < ffmpeg_output.txt" --warmup 5
Benchmark 1: make2compdb.exe < ffmpeg_output.txt
  Time (mean ± σ):      55.8 ms ±   4.5 ms    [User: 29.0 ms, System: 25.9 ms]
  Range (min … max):    51.4 ms …  79.0 ms    45 runs

With ffmpeg (linux)

$ hyperfine "make2compdb < ffmpeg_output.txt" --warmup 5 
Benchmark 1: make2compdb < ffmpeg_output.txt
  Time (mean ± σ):      28.2 ms ±   0.6 ms    [User: 22.8 ms, System: 3.7 ms]
  Range (min … max):    27.2 ms …  30.3 ms    102 runs

By comparison, the https://github.com/nickdiego/compiledb takes 18 seconds to analyzer ffmpeg.

$ hyperfine "compiledb -p ffmpeg_output.txt"
Benchmark 1: compiledb -p ffmpeg_output.txt
  Time (mean ± σ):     18.469 s ±  0.258 s    [User: 18.128 s, System: 0.205 s]                                        
  Range (min … max):   18.127 s … 19.003 s    10 runs

So it's a 331x speedup.

Features

Two additional features

1. No Make mode

I'm a big fan of the "unity build" compilation technique (like the one used in u-config).
Therefore, I often don't have a use for a makefiles in my projects.
That said, I still need compiler_commands.json for my IDE to work.

How great would it be to just pipe my build command into make2compdb ?
Well you can!

$ echo "gcc main.c -o main" | make2compdb.exe
[
  {
    "directory": "C:\\my_project",
    "file": "main.c",
    "output": "main",
    "arguments": [
      "gcc",
      "-o",
      "main",
      "main.c"
    ]
  }
]

2. --verbose

I believe that there's a lot of value in providing your technical users tools to help them self-diagnose issues.
In my experience, this usually leads to better bug reports and therefore help maintaining the project.

This is why I have added the --verbose CLI flag.
When it's passed to make2compdb, the stdout contains debug information.

$ make -Bwn | make2compdb.exe --verbose
make2compdb
Version: 2026-05-20
Verbose mode: true
Directory: "C:\\my_project"
CLI args: ["./make2compdb", "--verbose"]

------
Step 1: Identifying the parsing mode
    Parsing mode is SHELL

Step 2: Parsing shell command
    Invocation
        Input: "gcc -o main main.c\n"
        Tokens: ["gcc", "-o", "main", "main.c"]
        Compiler: {
            .ok = true
            .source = ["main.c"]
            .output = "main"
            .args = ["gcc", "-o", "main"]
        }
[...]

Limitations

  1. It does not support Microsoft CL.exe. I would be open to adding this feature if there's demand for it.
  2. On Linux, we only support C.UTF-8 and other English UTF8 locales.
    As a non-native English speaker, I can empathize with this being a pain point.
    That said, I didn't find a robust way to parse the Makefile output in different languages.

Note for reviewer

I've learn a lot building this project: Arena, fuzzing, CRT-less windows programming.

When starting this project, I think I underestimated the number of corner cases to handle.
To be honest, the shell parsing humbled me a little bit. :^)

One of the reasons I appreciate w64devkit, is its high standard when it comes to programming.
I think I have a lot to learn from the maintainers of this project, so I welcome any of your suggestions on how to improve make2compdb.

Closes #251

@Peter0x44

Copy link
Copy Markdown
Collaborator

Epic! I'll be test driving this myself.

@skeeto

skeeto commented May 22, 2026

Copy link
Copy Markdown
Owner

Thanks so much, @g-berthiaume! It's interesting to see these concepts through another person's lens.

Make all functions except the entry point static. The program is a single translation unit, which communicates to the compiler that functions do not need external linkage. For example, it should inline any function, regardless of size, called only from a single location as there's no downside to doing so. But external linkage counts as some unknown number of other call sites. IMHO, C gets this inverted: static ought to be the default, and external linkage ought to be opt-in (via a declared interface).

When I compile with GCC 16 in the latest w64dk I get a -Wnonnull-compare (via -Wall) warning on line 769, asserting that a [static] parameter is non-null. This is essentially GCC warning that it's going to eliminate the assertion because UB must have occurred before it could fire. That means this is not a well-placed assertion.

It's "peek" not "peak". I was genuinely confused by this because the latter would normally mean highest value (highest memory use, highest count, etc.). Also "threated" instead of "treated" and "stars" instead of "starts", and "emtpy" instead of "empty".

The program always outputs UTF-16, and there seems to be a misunderstanding how output is supposed to work. I'm surprised the resulting compile_commands.json actually works with anything (did it?):

$ printf 'cc x.c' | make2compdb | xxd | head -n1
00000000: 5b00 0a00 2000 2000 2000 2000 7b00 0a00  [... . . . .{...

If the output device is a console, use WriteConsoleW with UTF-16. Otherwise use WriteFile with UTF-8. Casting c16 * to u8 * does not make it UTF-8. The program is already UTF-8 most of the way through, and on Windows the output buffer is UTF-16. The common case is writing to a JSON file, not to a console, and so this means the common case would convert UTF-8-to-UTF-16 then UTF-16-to-UTF-8, which is silly. Instead use a UTF-8 buffer and, if a console is detected, convert UTF-8-to-UTF-16 at the last moment (caveat: tricky edge case around straddlers which even Microsoft's CRTs get wrong). It's more important that it outputs UTF-8 than that it prints UTF-16 to the console correctly, which is mostly a bonus to make visual inspection/debugging more reliable.

As a see it for yourself test of the above, these must both produce the same result:

$ printf 'cc π.c' | make2compdb | grep file | xxd
00000000: 2020 2020 2020 2020 2266 696c 6522 3a20          "file": 
00000010: 22cf 802e 6322 2c0a                      "...c",.

$ printf 'cc π.c' | make2compdb >json && grep file json | xxd
00000000: 2020 2020 2020 2020 2266 696c 6522 3a20          "file": 
00000010: 22cf 802e 6322 2c0a                      "...c",.

The parser should be more oriented around tokens than substring search. Don't substring search for "clang", match the "clang" token. You can further tokenize tokens as part of this process, first tokenizing to "x86_64-w64-mingw32-gcc", then to "x86_64" "w64" "mingw32" "gcc" to match "gcc".

Because it's substring matching, this doesn't work as intended:

$ printf 'ccache cc -c x.c' | make2compdb

Produces an arguments of ["ccache", "cc", "-c", "x.c"] because ccache matches cc, whereas, say, mycache works fine. Add a test for this when you fix it.

print_str_escaped_string produces invalid JSON for some inputs:

$ printf "cc -I'a b' x.c" | make2compdb

Produces output with escaped single quotes, "-I\'a b\'". JSON output could use some unit tests.

The PARSING_MODE_SHELL loop doesn't seem to accumulate commands, just keep the last, so:

$ printf 'cc a.c; cc b.c' | make2compdb

Only captures the second command. Add a test for this when you fix it. Unless this is intentional?

The &>> branch doesn't peek correctly, though it works out by chance anyway.

Compiler --output flag isn't handled, though there's an aspirational comment about it.

is_source_file should probably also cover at least .s and .S (assembly).

enum u32 is a strange name. Did you mean enum : u32?

Trivial to adjust, but even 16MiB arena may be a little tight for larger builds. Maybe 64M? Build commands can be quite bloated.


Nothing below is a problem, nor must change. Just pointing them out as commentary.

If you're committed to GNU-style toolchains (e.g. GCC, Clang), as MSVC is excluded, then because you're linking -lmemory, __builtin_memset (and __builtin_memcmp, etc.) is a first-class feature. In release builds, compilers likely figure out memory_set on their own, which is what makes -lmemory necessary, but it's nice to have a fast memory clear primitive even in debug builds.

Might be surprising (or might not given those const *const), but this is a semantically valid function:

void example(Str const s)
{
    s.ptr[0] = 0;
}

But s is const, right? Sure, but it doesn't propagate to pointed at objects. So the const is doing practically nothing. That const is so pathetic in C and C++ is why I don't bother with it in code I write. Though perhaps you feel it's still useful as documentation. On the other hand, it's not used consistently.

@Peter0x44

Copy link
Copy Markdown
Collaborator

On Linux, we only support C.UTF-8 and other English UTF8 locales.
As a non-native English speaker, I can empathize with this being a pain point.
That said, I didn't find a robust way to parse the Makefile output in different languages.

Would calling setlocale before starting the process not help? Then it wouldn't matter what the user's locale is.

@g-berthiaume

g-berthiaume commented May 25, 2026

Copy link
Copy Markdown
Author

@skeeto Thank you for your review.
I agree with every point you mentioned. I will correct those problems later this week.

@Peter0x44
Would calling setlocale before starting the process not help?

I'm not a Linux expert, but even if we assume that the shell starts make and make2compdb at exactly the same time, I'm not sure we could reliably set the locale without creating a locale "race condition". I'm not even sure when this global variable is read... is it at every write, or maybe in the crt0 before the main?

That said, even if we could, I would prefer not to touch the locale config. In cases of a crash, we could break users environment because make2compdb has set the locales before exiting.
What do you think?


EDIT (2026-05-29):
I have decided to rewrite my shell parser to make it cleaner and more robust.
My update will be delayed to next week.

@g-berthiaume

g-berthiaume commented Jun 5, 2026

Copy link
Copy Markdown
Author

Round 2!

The focus for this new release has been to make make2compdb more token-oriented.

Changes

  • All functions except the entry point are static.
  • Fix: All targets now compile without warnings -Wall -Wextra -Wpedantic -Wconversion.
  • Fix: Remove English typos like peak-> peek (sorry for the confusion).
  • Fix: Windows now uses the correct encoding when outputting to pipes/files.

As you can see, the output is now identical, regardless of the destination.

λ printf 'cc π.c' | ./make2compdb | grep file | xxd
00000000: 2020 2020 2020 2020 2266 696c 6522 3a20          "file":
00000010: 22cf 802e 6322 2c0a                      "...c",.

λ printf 'cc π.c' | ./make2compdb >json && grep file json | xxd
00000000: 2020 2020 2020 2020 2266 696c 6522 3a20          "file":
00000010: 22cf 802e 6322 2c0a                      "...c",.
  • Improve the compiler detection. The parser is now more token-oriented (more on that later).

Now ccache won't be detected as cc.

λ printf 'ccache clang -c x.c' | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "x.c",
        "arguments": [
            "clang",
            "-c",
            "x.c"
        ]
    }
]
  • Rework string shell unescaping and json escaping to be compliant with the JSON Compilation Database Format Specification.
λ printf "cc -I'a b' x.c" | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "x.c",
        "arguments": [
            "cc",
            "-Ia b",
            "x.c"
        ]
    }
]
  • Fix: Detect comma-separated commands
λ printf 'cc a.c; cc b.c' | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "a.c",
        "arguments": [
            "cc",
            "a.c"
        ]
    },
    {
        "directory": "C:\\dev",
        "file": "b.c",
        "arguments": [
            "cc",
            "b.c"
        ]
    }
]
  • Fix: &>> redirect operator is now working properly
λ printf 'gcc main.c -o main &>>test.txt' | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "main.c",
        "output": "main",
        "arguments": [
            "gcc",
            "-o",
            "main",
            "main.c"
        ]
    }
]
  • Fix: Now support the longer -o flag versions: --output and --output=.
λ printf 'gcc main.c --output main' | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "main.c",
        "output": "main",
        "arguments": [
            "gcc",
            "--output",
            "main",
            "main.c"
        ]
    }
]
  • Fix: str_is_source_file now includes .s and .S (assembly).
  • Fix: enum enum u32 -> enum : u32.
  • Increase memory arena size from 16MiB to 64MiB.
  • Now use __builtin_memset and __builtin_memcpy (provided by -lmemory).
  • Standardize const usage.
  • Fix: You can now use more than 1 compiler wrapper
λ printf "distcc ccache clang main.c" | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "main.c",
        "arguments": [
            "clang",
            "main.c"
        ]
    }
]
  • Fix: The -x none flag now correctly resets the language.

Notice that some_include.h is not treated as a source file.

λ printf "gcc -x c lib.h main.c -x none some_include.h" | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "lib.h",
        "arguments": [
            "gcc",
            "-x",
            "c",
            "lib.h",
            "main.c",
            "-x",
            "none",
            "some_include.h"
        ]
    },
    {
        "directory": "C:\\dev",
        "file": "main.c",
        "arguments": [
            "gcc",
            "-x",
            "c",
            "lib.h",
            "main.c",
            "-x",
            "none",
            "some_include.h"
        ]
    }
]
  • Fix: Shell redirections can now be used inside the compiler invocation (not just the edges).

Here's a (nasty) example:

λ printf 'gcc main.c -o 2&> lib.c main' | ./make2compdb
[
    {
        "directory": "C:\\dev",
        "file": "main.c",
        "output": "main",
        "arguments": [
            "gcc",
            "-o",
            "main",
            "main.c"
        ]
    }
]
  • Rework shell tokenizer.

Details

At its core, this release focuses on the shell tokenizer re-implementation.
With a better tokenizer, it's easier for the rest of the code to be more token-oriented.
The shell should now be more robust, more tested and more readable.
Also, the tokenizer is now allocation-free, which is a nice bonus.

After thinking about it and re-reading Arenas and the almighty concatenation operator, I realized that I could simplify my memory allocation strategy by composing str_concat and str_duplicate. I'm pretty happy with the results.

Performance-wise, the new parsing strategy is a bit slower, which is to be expected, as we now look at every token.
On my machine, ffmpeg is parsed in under 80ms on both Linux and Windows, which is still acceptable.

Note for reviewer

Thanks again for your last feedback.
Please let me know if you see any areas where this tool could be improved.

@g-berthiaume

Copy link
Copy Markdown
Author

@skeeto @Peter0x44
Just a small heads up:
I'll be on vacation next week, and therefore away from my keyboard.
I'll be back on the 21.

Comment thread src/make2compdb.c
return tokens;
}

// :: Compiler

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this CompilerKind? It doesn't seem to be doing anything needed?

@g-berthiaume g-berthiaume Jun 23, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, currently CompilerKind is only used in the CompilerInvocation struct, which is not really needed.

The idea is that I could extend make2compdb to support other compilers like Microsoft's CL.exe.

You can see the scaffolding for executing this idea in compiler_command_from_invocation():
image

@sleeptightAnsiC sleeptightAnsiC Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this distinction between compilers will be ever needed.

The chance for some flag to clash when, e.g. one compiler expects it to consume the argument while the other does not, it's probably not something worth worrying about.

GCC, Clang and all it's forks (emcc, zig cc, icx, etc...) they all share flags which should be compatible. For CL the flags are usually prefixed with slash / rather than dash -, so you can cheat a bit here :)

As I mentioned in #251 (comment), hardcoding compilers/flags will never be reliable, so you may as well ignore small incompatibilities and keep the code simple, that is, just detect if the compiler is known, but don't try to do anything smart about it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this discussion a lot.
I'm hesitant to fully commit to this idea because the core of my parsing strategy relies on finding the "start" of the compilation command, and therefore identifying the compiler.

Without it, I'm not sure how I would be able to navigate the noise that is a makefile stdout.

Comment thread src/make2compdb.c Outdated
Comment on lines +2155 to +2158
Arena scratch = *perm;

Str cwd = dirstack_pop(&dir_stack, &scratch);
(void)cwd;

@sleeptightAnsiC sleeptightAnsiC Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "gymnastic" around returning cwd is a bit odd. dirstack_pop is only used in this one place. Wouldn't it make more sense to just edit it like so [diff below] ?

tbh, the code related to dirstack_* and strlist_* seems like (potentially) unnecessary boiler plate, but I haven't looked through all of it though.

diff --git a/src/make2compdb.c b/src/make2compdb.c
index 39f9d27..46ce8a0 100644
--- a/src/make2compdb.c
+++ b/src/make2compdb.c
@@ -838,15 +838,12 @@ static Str dirstack_peek(DirectoryStack ds, Arena *perm)
     return directory_copy;
 }
 
-static Str dirstack_pop(DirectoryStack *ds, Arena *perm)
+static void dirstack_pop(DirectoryStack *ds)
 {
     Str directory = strlist_pop_back(&ds->stack);
     if (directory.len > 0) {
-        Str directory_copy = str_duplicate(perm, directory);
         arena_reset_to(&ds->arena, (byte *)directory.ptr);
-        return directory_copy;
     }
-    return directory;
 }
 
 // :: OS
@@ -2152,10 +2149,7 @@ static int make2compdb(Arena *perm, OsWriterInterface *w, StrList cli_args, Str
 
             Str dir = directory_from_make_dir_line(&input);
             if (dir.len > 0) {
-                Arena scratch = *perm;
-
-                Str cwd = dirstack_pop(&dir_stack, &scratch);
-                (void)cwd;
+                dirstack_pop(&dir_stack);
             }
             break;
         }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "gymnastic" around returning cwd is a bit odd. dirstack_pop is only used in this one place.

I agree with you. I used to use this value to validate that we pop the expected directory, but for fuzzing, this made no sense.

Should be fixed in my next commit.

Comment thread src/make2compdb.c Outdated
"Options:\n"
" --version: Display the version number.\n"
" -h, --help: Display this help page.\n"
" -v, --verbose: Display debug information to help with problem diagnosis.\n"

@sleeptightAnsiC sleeptightAnsiC Jun 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance for diagnostic to appear at stderr when using --verbose and similar, so it can be easily piped and split from json output?

I see there is some platform specific separation of stdin/out/err already, but the diagnostic still goes to stdout, so it's not really practical to deal with. (basically, everything that isn't json should go to stderr)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion.
I just tried it. To be honest, it feels to me like it would be a bit confusing for new users.
That said, I'm not opposed to this change.

I will commit it so you guys can try it, and I'll revert it if need be.

Comment thread src/make2compdb.c Outdated
SL("-bundle_loader"),
SL("--param"),
SL("-arch"),
SL("-wrapper"), SL("-wrapper"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-wrapper twice?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed in my next commit.
Thanks for your attention to detail.

@g-berthiaume

Copy link
Copy Markdown
Author

Thanks @sleeptightAnsiC for your review.
Sorry for the delay in the reply; it's a bit too hot in my house to program these days.
I will dedicate time to this project as soon as the temperature is more manageable here.
Also, thanks again for your #251 (comment), it has been helpful.

@g-berthiaume

g-berthiaume commented Jul 29, 2026

Copy link
Copy Markdown
Author

Round 3!

The focus for this new release is addressing the issues identified by @Peter0x44 and @sleeptightAnsiC.
Thanks for your help.

Changelog

  • The --verbose mode is now piped to stderr.
  • Simplify CompilerKind and add some new "gcc-like" compilers.
  • Various fixes

Note for reviewer

As always, thanks again for your last feedback.
Please let me know if you see any areas where this tool could be improved.

Previous rounds

@g-berthiaume

Copy link
Copy Markdown
Author

Hi @skeeto
Just following up on this proposal. Are you still interested in this contribution?
If so, let me know if there’s anything missing or anything you’d like me to address.

@skeeto

skeeto commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Sorry, I did not prioritize a followup review enough and so I kept putting it off. It takes awhile to get my head back into the problem after being away from it more than a couple of weeks.

str_unescape applies C backslash semantics to shell lines. A backslash in POSIX shell means to take the next character literally, and so in a shell command \t isn't a tab but a literal t, with a superfluous backslash. For example:

$ echo "gcc -c -Ds=\"It's fine\n\" x.c" | ./make2compdb.exe

Here the apostrophe is lost, and the \n becomes a newline where it should go into gcc as two bytes, backslash and n.

clang is missing a test, this clang++ test fails, and the nvc++ test uses the wrong string:

@@ -2558,2 +2558,5 @@ static void test_compiler_parse(Arena a)
 
+    run_test_compiler_parser(SL("clang"), SL("clang"), COMPILER_IS_GCC_COMPATIBLE);
+    run_test_compiler_parser(SL("clang++"), SL("clang++"), COMPILER_IS_GCC_COMPATIBLE);
+
     // zig cc
@@ -2573,3 +2576,3 @@ static void test_compiler_parse(Arena a)
     // NVIDIA HPC C++ compiler (nvc++ -std=c++17 -O2 -c main.cpp -o main.o)
-    run_test_compiler_parser(SL("nvc"), SL("nvc"), COMPILER_IS_GCC_COMPATIBLE);
+    run_test_compiler_parser(SL("nvc++"), SL("nvc++"), COMPILER_IS_GCC_COMPATIBLE);
 

print_str_escaped_string: The writer shouldn't produce \v nor \a (not valid JSON escapes), and all control characters should be escaped with at least \u00xx.

Should str_drop_tail clamp to len - 1? That doesn't make sense to me. Though it doesn't seem to cause any bugs.

Some typos in the header doc:

@@ -1,3 +1,3 @@
 // make2compdb
-// Generates a Clang's JSON Compilation Database files (`compiler_commands.json`) from make build systems.
+// Generates a Clang's JSON Compilation Database files (`compile_commands.json`) from make build systems.
 // This json file can be usefull for multiple tools including the [clangd](https://clangd.llvm.org/) LSP.
@@ -6,4 +6,4 @@
 //
-//      $ make -Bwn | make2compdb.exe > compiler_commands.json
-//      $ cat compiler_commands.json
+//      $ make -Bwn | make2compdb.exe > compile_commands.json
+//      $ cat compile_commands.json
 //      [

I think these are meant to go to standard error?

--- a/src/make2compdb.c
+++ b/src/make2compdb.c
@@ -2101,5 +2101,5 @@ static int make2compdb(Arena *perm, OsWriterInterface *os_stdout, OsWriterInterf
         else {
-            print_str(os_stdout, SL("Unknown CLI arg: '"));
-            print_str(os_stdout, arg->str);
-            println_str(os_stdout, SL("'\n"));
+            print_str(os_stderr, SL("Unknown CLI arg: '"));
+            print_str(os_stderr, arg->str);
+            println_str(os_stderr, SL("'\n"));
             return -1;

Also please exit with 1, not -1. That's a bad habit from the unix world where conventionally it's clamped to 255, but Windows uses the whole 32-bit exit status, and so it clamps to 0xffffffff:

$ echo 'int main() { return -1; }' | cc -xc -O - && ./a.exe
sh: ./a.exe: Error 0xffffffff

The busybox-w32 shell reports such high-valued errors loudly because they're often significant (special Windows errors).

make2compdb() has a static counter variable, s_prev_command_count.

print_compiler_invocation appears to be missing a newline in its ouput.

Bad text replacement here?

        goto reject; // unU8_paired low surrogate

I think we're pretty close to merging. str_unescape is an especially important blocker and needs to be fixed. It's also the most complicated. Everything that follows is in roughly decreasing priority.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces make2compdb, converting GNU Make output into Clang JSON compilation databases.

Changes:

  • Adds shell parsing, compiler detection, JSON generation, tests, and fuzzing.
  • Supports CRT-free Windows and POSIX execution.
  • Integrates the executable into w64devkit packaging.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 10 comments.

File Description
src/make2compdb.c Implements the utility and platform support.
Dockerfile Builds and installs make2compdb.exe.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/make2compdb.c
switch (c) {
case '\'': break; //< Unquote
case '\"': break; //< Unquote
case '\\': {
Comment thread src/make2compdb.c
Comment on lines +980 to +986
case '\v': print_str(w, SL("\\v")); break;
case '\b': print_str(w, SL("\\b")); break;
case '\f': print_str(w, SL("\\f")); break;
case '\a': print_str(w, SL("\\a")); break;
case '\\': print_str(w, SL("\\\\")); break;
default: print_u8(w, c); break;
}
Comment thread src/make2compdb.c

(void)first; //< We cannot rely on "make" as this program name changes.

if (str_equal(third, SL("directory"))) {
Comment thread src/make2compdb.c

switch (make_u16(mode, c)) {
case make_u16(IN_QUOTE, '\''): mode = NORMAL; break;
case make_u16(IN_DOUBLE_QUOTE, '\"'): mode = NORMAL; break;
Comment thread src/make2compdb.c
Comment on lines +1526 to +1529
if (str_equal(segment.head, SL("clang"))) {
compiler.kind = COMPILER_IS_GCC_COMPATIBLE;
compiler.string = input;
return compiler;
Comment thread src/make2compdb.c

switch (state) {
case SEARCH_COMPILER: {
compiler = compiler_parse(token);
Comment thread src/make2compdb.c
Comment on lines +2102 to +2104
print_str(os_stdout, SL("Unknown CLI arg: '"));
print_str(os_stdout, arg->str);
println_str(os_stdout, SL("'\n"));
Comment thread src/make2compdb.c
Comment on lines +2972 to +2975
u8 *src = NULL;
src = realloc(src, len);
assert(src);
memcpy(src, buf, len);
Comment thread src/make2compdb.c
Comment on lines +3274 to +3275
bs->stream.err = !WriteConsoleW(bs->stream.handle, buf, buf_len, NULL, 0);
bs->buf.len = 0;
Comment thread Dockerfile
-o $PREFIX/bin/makensis.exe $PREFIX/src/alias.c -lkernel32 \
&& $ARCH-gcc \
-Oz -fno-asynchronous-unwind-tables -fno-builtin -Wl,--gc-sections \
-s -nostdlib -o $PREFIX/bin/make2compdb.exe $PREFIX/src/make2compdb.c \
@g-berthiaume

g-berthiaume commented Aug 18, 2026

Copy link
Copy Markdown
Author

@skeeto Sorry, I did not prioritize a followup review enough and so I kept putting it off. It takes awhile to get my head back into the problem after being away from it more than a couple of weeks.

No worries.
Since the last time we spoke was in May, I wanted to make sure my contributions were still aligned with your vision of the tool.

Thank you for your review; I'll address those problems shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Speculative application: make2compdb

5 participants