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 <pulkomandy@gmail.com>
This commit is contained in:
Adrien Destugues 2020-09-22 20:29:49 +02:00 committed by Adrien Destugues
parent 51dc302367
commit c8ecfce34c
3 changed files with 111 additions and 1 deletions

View File

@ -141,6 +141,7 @@ struct globs globs = {
# else # else
{ 0, 1 }, /* display actions */ { 0, 1 }, /* display actions */
# endif # endif
0, /* output compilation db here */
0 /* output commands, not run them */ 0 /* output commands, not run them */
} ; } ;
@ -182,7 +183,7 @@ main( int argc, char **argv, char **arg_environ )
argc--, argv++; 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" ); 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( "-jx Run up to x shell commands concurrently.\n" );
printf( "-n Don't actually execute the updating actions.\n" ); printf( "-n Don't actually execute the updating actions.\n" );
printf( "-ox Write the updating actions to file x.\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( "-q Quit quickly as soon as a target fails.\n" );
printf( "-sx=y Set variable x=y, overriding environment.\n" ); printf( "-sx=y Set variable x=y, overriding environment.\n" );
printf( "-tx Rebuild x, even if it is up-to-date.\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 */ /* Set JAMDATE first */
{ {
@ -439,5 +452,12 @@ main( int argc, char **argv, char **arg_environ )
if( globs.cmdout ) if( globs.cmdout )
fclose( 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; return status ? EXITBAD : EXITOK;
} }

View File

@ -500,6 +500,7 @@ struct globs {
int quitquick; int quitquick;
int newestfirst; /* build newest sources first */ int newestfirst; /* build newest sources first */
char debug[DEBUG_MAX]; char debug[DEBUG_MAX];
FILE *comp_db; /* output compilation db here */
FILE *cmdout; /* print cmds, not run them */ FILE *cmdout; /* print cmds, not run them */
} ; } ;

View File

@ -62,6 +62,8 @@
# include "command.h" # include "command.h"
# include "execcmd.h" # include "execcmd.h"
#include <unistd.h>
static void make1a( TARGET *t, TARGET *parent ); static void make1a( TARGET *t, TARGET *parent );
static void make1b( TARGET *t ); static void make1b( TARGET *t );
static void make1c( 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 SETTINGS *make1settings( LIST *vars );
static void make1bind( TARGET *t, int warn ); 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. */ /* Ugly static - it's too hard to carry it through the callbacks. */
static struct { static struct {
@ -295,6 +303,17 @@ make1c( TARGET *t )
if( globs.cmdout ) if( globs.cmdout )
fprintf( globs.cmdout, "%s", cmd->buf ); 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 ) if( globs.noexec )
{ {
make1d( t, EXEC_CMD_OK ); make1d( t, EXEC_CMD_OK );
@ -671,3 +690,73 @@ make1bind(
t->binding = t->time ? T_BIND_EXISTS : T_BIND_MISSING; t->binding = t->time ? T_BIND_EXISTS : T_BIND_MISSING;
popsettings( t->settings ); 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);
}
}