From c8ecfce34c4db7b94581e365377aea88e862d00f Mon Sep 17 00:00:00 2001 From: Adrien Destugues Date: Tue, 22 Sep 2020 20:29:49 +0200 Subject: [PATCH] jam: add an option to generate compile_commands.json Based on Boost Jam patch: https://github.com/boostorg/build/pull/133 retrofitted to our version. Only the rules whose name contains Cc or C++ are stored there. This can be improved if it's not good enough. The commands are generated only as they are run. Unfortunately I think with Jam there isn't really a way to do otherwise. Change-Id: Ic5d44dc27baa2a2e4157324f6c5a228ab0366afe Reviewed-on: https://review.haiku-os.org/c/buildtools/+/3260 Reviewed-by: Adrien Destugues --- jam/jam.c | 22 ++++++++++++- jam/jam.h | 1 + jam/make1.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/jam/jam.c b/jam/jam.c index 2bbf1ae228..4dfb4c4a9f 100644 --- a/jam/jam.c +++ b/jam/jam.c @@ -141,6 +141,7 @@ struct globs globs = { # else { 0, 1 }, /* display actions */ # endif + 0, /* output compilation db here */ 0 /* output commands, not run them */ } ; @@ -182,7 +183,7 @@ main( int argc, char **argv, char **arg_environ ) argc--, argv++; - if( ( n = getoptions( argc, argv, "d:j:f:gs:t:ano:qv", optv ) ) < 0 ) + if( ( n = getoptions( argc, argv, "d:j:f:gs:t:ano:cqv", optv ) ) < 0 ) { printf( "\nusage: jam [ options ] targets...\n\n" ); @@ -197,6 +198,7 @@ main( int argc, char **argv, char **arg_environ ) printf( "-jx Run up to x shell commands concurrently.\n" ); printf( "-n Don't actually execute the updating actions.\n" ); printf( "-ox Write the updating actions to file x.\n" ); + printf( "-c Output JSON compilation database to compile_commands.json.\n" ); printf( "-q Quit quickly as soon as a target fails.\n" ); printf( "-sx=y Set variable x=y, overriding environment.\n" ); printf( "-tx Rebuild x, even if it is up-to-date.\n" ); @@ -279,6 +281,17 @@ main( int argc, char **argv, char **arg_environ ) } } + /* If we're asked to produce a compilation database, open the file. */ + if ( ( s = getoptval( optv, 'c', 0 ) ) ) + { + if ( !( globs.comp_db = fopen( "compile_commands.json", "w" ) ) ) + { + printf( "Failed to write to 'compile_commands.json'\n"); + exit( EXITBAD ); + } + fprintf(globs.comp_db, "[\n"); + } + /* Set JAMDATE first */ { @@ -439,5 +452,12 @@ main( int argc, char **argv, char **arg_environ ) if( globs.cmdout ) fclose( globs.cmdout ); + /* close compilation database output file */ + if ( globs.comp_db ) + { + fprintf(globs.comp_db, "]\n"); + fclose( globs.comp_db ); + } + return status ? EXITBAD : EXITOK; } diff --git a/jam/jam.h b/jam/jam.h index da3db48e94..6e509d581c 100644 --- a/jam/jam.h +++ b/jam/jam.h @@ -500,6 +500,7 @@ struct globs { int quitquick; int newestfirst; /* build newest sources first */ char debug[DEBUG_MAX]; + FILE *comp_db; /* output compilation db here */ FILE *cmdout; /* print cmds, not run them */ } ; diff --git a/jam/make1.c b/jam/make1.c index ffb7831613..906b41a393 100644 --- a/jam/make1.c +++ b/jam/make1.c @@ -62,6 +62,8 @@ # include "command.h" # include "execcmd.h" +#include + static void make1a( TARGET *t, TARGET *parent ); static void make1b( TARGET *t ); static void make1c( TARGET *t ); @@ -73,6 +75,12 @@ static LIST *make1list( LIST *l, TARGETS *targets, int flags, static SETTINGS *make1settings( LIST *vars ); static void make1bind( TARGET *t, int warn ); +void out_compile_database( + char const * const action, + char const * const source, + char const * const command + ); + /* Ugly static - it's too hard to carry it through the callbacks. */ static struct { @@ -295,6 +303,17 @@ make1c( TARGET *t ) if( globs.cmdout ) fprintf( globs.cmdout, "%s", cmd->buf ); + if ( globs.comp_db != NULL ) + { + const char* rule_name = cmd->rule->name; + const char* target_name = lol_get( (LOL *)&cmd->args, 0 )->string; + const char* source_name = NULL; + LIST* sources = lol_get( (LOL *)&cmd->args, 1); + if (sources != NULL) + source_name = lol_get((LOL *)&cmd->args, 1 )->string; + out_compile_database( rule_name, source_name, cmd->buf ); + } + if( globs.noexec ) { make1d( t, EXEC_CMD_OK ); @@ -671,3 +690,73 @@ make1bind( t->binding = t->time ? T_BIND_EXISTS : T_BIND_MISSING; popsettings( t->settings ); } + + +static void out_json(char const* str, FILE* f) +{ + char const* escape_src = "\"\\\b\n\r\t"; + char const* escape_subst[] = { + "\\\"", "\\\\", "\\b", "\\n", "\\r", "\\t" + }; + char buffer[1024]; + int i = 0; + + /* trim leading whitespace */ + while (*str != 0 && strchr(" \t\n\r\t", *str) != NULL) + ++str; + + for (; *str != 0; ++str) + { + char const* ch; + char const* subst; + if (i >= sizeof(buffer) - 10) + { + buffer[i] = 0; + fputs(buffer, f); + i = 0; + } + + /* skip non-printable characters */ + if ((unsigned)*str < ' ') continue; + + ch = strchr(escape_src, *str); + if (ch == NULL) + { + buffer[i++] = *str; + continue; + } + subst = escape_subst[ch - escape_src]; + strcpy(&buffer[i], subst); + i += strlen(subst); + } + + buffer[i] = 0; + fputs(buffer, f); +} + + +void out_compile_database +( + char const * const action, + char const * const source, + char const * const command +) +{ + /* file format defined here: + * http://clang.llvm.org/docs/JSONCompilationDatabase.html + * we're not interested in link, mkdir, rm or any non-compile action + */ + if (source + && (strstr(action, "Cc") != NULL || strstr(action, "C++") != NULL)) + { + char buffer[PATH_MAX]; + fputs("{ \"directory\": \"", globs.comp_db); + out_json(getcwd(buffer, sizeof(buffer)), globs.comp_db); + fputs("\", \"command\": \"", globs.comp_db); + out_json(command, globs.comp_db); + fputs("\", \"file\": \"", globs.comp_db); + out_json(source, globs.comp_db); + fputs("\" },\n", globs.comp_db); + } + +}