diff --git a/src/engine/jam.c b/src/engine/jam.c index cfb5cecf0c..781fde7dca 100644 --- a/src/engine/jam.c +++ b/src/engine/jam.c @@ -144,7 +144,8 @@ struct globs globs = #else { 0, 1 }, /* debug ... */ #endif - 0, /* output commands, not run them */ + 0, /* mirror output to file */ + 0, /* output compilation db here */ 0, /* action timeout */ 0 /* maximum buffer size zero is all output */ }; @@ -243,9 +244,9 @@ int main( int argc, char * * argv, char * * arg_environ ) ++argv; #ifdef HAVE_PYTHON - #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:qvz" + #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:cqvz" #else - #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:qv" + #define OPTSTRING "-:l:m:d:j:p:f:gs:t:ano:cqv" #endif if ( getoptions( argc, argv, OPTSTRING, optv ) < 0 ) @@ -261,6 +262,7 @@ int main( int argc, char * * argv, char * * arg_environ ) err_printf( "-mx Maximum target output saved (kb), default is to save all output.\n" ); err_printf( "-n Don't actually execute the updating actions.\n" ); err_printf( "-ox Mirror all output to file x.\n" ); + err_printf( "-c Output JSON compilation database to compile_commands.json (only for built targets).\n" ); err_printf( "-px x=0, pipes action stdout and stderr merged into action output.\n" ); err_printf( "-q Quit quickly as soon as a target fails.\n" ); err_printf( "-sx=y Set variable x=y, overriding environment.\n" ); @@ -376,6 +378,17 @@ int main( int argc, char * * argv, char * * arg_environ ) /* ++globs.noexec; */ } + /* 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" ) ) ) + { + err_printf( "Failed to write to 'compile_commands.json'\n"); + exit( EXITBAD ); + } + fprintf(globs.comp_db, "[\n"); + } + constants_init(); cwd_init(); @@ -614,6 +627,13 @@ int main( int argc, char * * argv, char * * arg_environ ) if ( globs.out ) fclose( globs.out ); + /* close compilation database output file */ + if ( globs.comp_db ) + { + fprintf(globs.comp_db, "]\n"); + fclose( globs.comp_db ); + } + #ifdef HAVE_PYTHON Py_Finalize(); #endif diff --git a/src/engine/jam.h b/src/engine/jam.h index ec56fe29b6..a7a53c250b 100644 --- a/src/engine/jam.h +++ b/src/engine/jam.h @@ -469,6 +469,7 @@ struct globs int pipe_action; char debug[ DEBUG_MAX ]; FILE * out; /* mirror output here */ + FILE * comp_db; /* output compilation db here */ long timeout; /* number of seconds to limit actions to, * default 0 for no limit. */ diff --git a/src/engine/make1.c b/src/engine/make1.c index 7dbf7c8da2..b129f0b465 100644 --- a/src/engine/make1.c +++ b/src/engine/make1.c @@ -813,6 +813,8 @@ static void make1c_closure CMD * const cmd = (CMD *)t->cmds; char const * rule_name = 0; char const * target_name = 0; + char const * source_name = 0; + LIST* sources = 0; assert( cmd ); @@ -850,6 +852,18 @@ static void make1c_closure out_action( rule_name, target_name, cmd->buf->value, cmd_stdout, cmd_stderr, cmd_exit_reason ); + if ( globs.comp_db != NULL ) + { + rule_name = object_str( cmd->rule->name ); + target_name = object_str( list_front( lol_get( (LOL *)&cmd->args, 0 ) ) + ); + sources = lol_get( (LOL *)&cmd->args, 1); + if (sources != NULL) + source_name = object_str( list_front( lol_get( + (LOL *)&cmd->args, 1 ) ) ); + out_compile_database( rule_name, source_name, cmd->buf->value ); + } + if ( !globs.noexec ) { call_timing_rule( t, time ); diff --git a/src/engine/output.c b/src/engine/output.c index 2d9f413823..a9539f3e35 100644 --- a/src/engine/output.c +++ b/src/engine/output.c @@ -6,6 +6,8 @@ #include "jam.h" #include "output.h" +#include "cwd.h" +#include "object.h" #include #include @@ -98,6 +100,45 @@ void err_printf(char const * const f, ...) } } +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) + { + if (i >= sizeof(buffer) - 10) + { + buffer[i] = 0; + fputs(buffer, f); + i = 0; + } + + /* skip non-printable characters */ + if (*str < ' ' || *str > 127) continue; + + char const* ch = strchr(escape_src, *str); + if (ch == NULL) + { + buffer[i++] = *str; + continue; + } + char const* subst = escape_subst[ch - escape_src]; + strcpy(&buffer[i], subst); + i += strlen(subst); + } + + buffer[i] = 0; + fputs(buffer, f); +} void out_action ( @@ -145,6 +186,30 @@ void out_action err_flush(); } +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, "compile") != NULL) + { + fputs("{ \"directory\": \"", globs.comp_db); + out_json(object_str(cwd()), 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); + } + +} OBJECT * outf_int( int const value ) { diff --git a/src/engine/output.h b/src/engine/output.h index b6a98ff79b..eedb54b59e 100644 --- a/src/engine/output.h +++ b/src/engine/output.h @@ -23,6 +23,12 @@ void out_action( int const exit_reason ); +void out_compile_database( + char const * const action, + char const * const source, + char const * const command +); + void out_flush(); void err_flush(); void out_puts(char const * const s); diff --git a/test/core_option_c.py b/test/core_option_c.py new file mode 100755 index 0000000000..4d742fe1bc --- /dev/null +++ b/test/core_option_c.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +# Copyright 2016. Arvid Norberg +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Test correct "-c" option handling. + +import BoostBuild +import MockToolset + +t = BoostBuild.Tester(arguments=['toolset=mock', '--ignore-site-config', + '--user-config='], pass_toolset=0, pass_d0=0) + +MockToolset.create(t) +MockToolset.set_expected(t, ''' +action('-c -x c++ hello.cpp -o bin/mock/debug/hello.o') +action('bin/mock/debug/hello.o -o bin/mock/debug/hello') +''') + +t.write("hello.cpp", 'int main() { return 0; }\n') +t.write("Jamroot.jam", "exe test : hello.cpp ;\n") + +t.run_build_system(['-can']) +t.ignore("bin") + +t.expect_addition("compile_commands.json") +with open('compile_commands.json') as f: + l = f.readlines() + assert len(l) == 3 + assert l[0] == '[\n' + assert l[1].startswith('{ "directory": "') + assert l[1].endswith('mock.py -c -x c++ \\"hello.cpp\\" -o \\"bin/mock/debug/hello.o\\"", "file": "hello.cpp" },\n') + assert l[2] == ']\n' + +t.expect_nothing_more() +t.cleanup() + diff --git a/test/test_all.py b/test/test_all.py index 56bcf72fda..ee3f791058 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -194,6 +194,7 @@ def reorder_tests(tests, first_test): "core_option_d2", "core_option_l", "core_option_n", + "core_option_c", "core_parallel_actions", "core_parallel_multifile_actions_1", "core_parallel_multifile_actions_2",