From c10c90efd41294eca8dad0bd3a20abead33032c2 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 23 Oct 2024 10:50:47 +0200 Subject: Add config.cc.compiledb= shortcut for placing file into source directory --- doc/manual.cli | 48 +++++++++++++++++----- libbuild2/cc/init.cxx | 112 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 18 deletions(-) diff --git a/doc/manual.cli b/doc/manual.cli index 03fa04a..1e489e9 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -8165,12 +8165,12 @@ $ b libhello/ In the above setup it feels natural to call each database after the project and place them into the output directory. However, some consumers, such as -IDEs, may not handle this setup well. Specifically, they may only recognize -the canonical \c{compile_commands.json} file as the compilation database, -opening all other files as generic JSON. They may also assume the directory -where this file resides to be the project source directory root. To accommodate -these assumptions we can instead place each database into the project's -source directory and call it \c{compile_commands.json}: +IDEs and LSP servers, may not handle this setup well. Specifically, they may +only recognize the canonical \c{compile_commands.json} file as the compilation +database, opening all other files as generic JSON. They may also assume the +directory where this file resides to be the project source directory root. To +accommodate these assumptions we can instead place each database into the +project's source directory and call it \c{compile_commands.json}: \ $ bdep init @gcc config.cc.compiledb=libhello@./compile_commands.json @@ -8178,13 +8178,39 @@ $ bdep init @gcc config.cc.compiledb=libhello@./compile_commands.json $ bdep init @gcc config.cc.compiledb=hello@./compile_commands.json \ -Note that in this case it will be your responsibility to remove the database -files if and when necessary. \N{\l{bdep-new(1)} adds \c{compile_commands.json} -to \c{.gitignore} it generates.} +To facilitate this use-case, \c{config.cc.compiledb} supports another +shortcut: if we specify just \ci{name} and it contains a directory component, +then it is interpreted as \ci{path} rather than \ci{name}. In this case +\ci{name} is taken to be the name of the last directory component in \ci{path} +(which would typically be a project or package name). And if \ci{path} is a +directory, then the database file name is taken to be +\c{compile_commands.json}. Or, in other words, the following: + +\ +config.cc.compiledb=...// +\ + +Is equivalent to: + +\ +config.cc.compiledb=@...//compile_commands.json +\ + +This shortcut allows us to simplify the above \c{init} commands to read: + +\ +$ bdep init @gcc config.cc.compiledb=./ + +$ bdep init @gcc config.cc.compiledb=./ +\ + +Note also that in this case it will be your responsibility to remove the +database files if and when necessary. \N{\l{bdep-new(1)} adds +\c{compile_commands.json} to \c{.gitignore} it generates.} If instead of having a separate database for each project we wanted to place -all the entries into a single database, then the relevant commands would -change as follows: +all the entries into a single database (and in the output directory), then the +relevant commands would change as follows: \ $ bdep init @gcc config.cc.compiledb=compiledb diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx index 1ddeca8..d691bc5 100644 --- a/libbuild2/cc/init.cxx +++ b/libbuild2/cc/init.cxx @@ -107,6 +107,36 @@ namespace build2 return r; } + // Detect if just in the [@] form is actually . + // We assume it is and not if it contains a directory + // component or is the special directory name (`.`/`..`) . If that's the + // case, return canonicalized name representing . See the call site + // in core_config_init() below for background. + // + static optional + compiledb_name_to_path (const name& n) + { + if (n.directory ()) + return n; + + if (n.file ()) + { + if (!n.dir.empty () || + path_traits::find_separator (n.value) != string::npos) + { + name r (n); + r.canonicalize (); + return r; + } + else if (n.value == "." || n.value == "..") + { + return name (dir_path (n.value)); + } + } + + return nullopt; + } + // Custom save function that completes relative paths in the // config.cc.compiledb and config.cc.compiledb.name values. // @@ -118,6 +148,26 @@ namespace build2 { const names& ns (v.as ()); // Value is untyped. + // Detect and handle the case where just is actually . + // + if (ns.size () == 1) + { + const name& n (ns.back ()); + + if (optional otn = compiledb_name_to_path (n)) + { + name& tn (*otn); + + if (tn.dir.relative ()) + tn.dir.complete (); + + tn.dir.normalize (); + + storage.push_back (move (tn)); + return make_pair (names_view (storage), "="); + } + } + if (find_if (ns.begin (), ns.end (), [] (const name& n) {return n.pair;}) == ns.end ()) { @@ -188,11 +238,11 @@ namespace build2 // // See the manual for the semantics. // - // config.cc.compiledb -- [@] (untyped) - // config.cc.compiledb.name -- [@]... (untyped) - // config.cc.compiledb.filter -- [@]... - // config.cc.compiledb.filter.input -- [@]... - // config.cc.compiledb.filter.output -- [@]... + // config.cc.compiledb -- [@]| (untyped) + // config.cc.compiledb.name -- [@]... (untyped) + // config.cc.compiledb.filter -- [@]... + // config.cc.compiledb.filter.input -- [@]... + // config.cc.compiledb.filter.output -- [@]... // vp.insert ("config.cc.compiledb"); vp.insert ("config.cc.compiledb.name"); @@ -601,6 +651,12 @@ namespace build2 if (!i->simple () || i->empty ()) fail (loc) << "invalid compilation database name '" << *i << "'"; + // Don't allow names that have (or are) directory components. + // + if (compiledb_name_to_path (*i)) + fail (loc) << "directory component in compilation database name '" + << *i << "'"; + string n (i->value); path p; @@ -760,9 +816,51 @@ namespace build2 // Make sure it's one name/path. // - if (ns.empty () || ns.size () != (ns.front ().pair ? 2 : 1)) + size_t n (ns.size ()); + if (n == 0 || n != (ns.front ().pair ? 2 : 1)) fail (loc) << "invalid compilation database name '" << ns << "'"; + // Detect and translate just which is actually to the + // @ form: + // + // - The part is the name of the directory where the database + // file will reside (typically project/repository or package + // name). + // + // - If is a directory, then the database name is + // compile_commands.json. + // + names tns; + if (n == 1) + { + const name& n (ns.front ()); + + if (optional otn = compiledb_name_to_path (n)) + { + name& tn (*otn); + + // Note: the add_cdbs() call below completes and normalizes the + // path but we need to do it earlier in order to be able to + // derive the name (the last component can be `.`/`..`). + // + if (tn.dir.relative ()) + tn.dir.complete (); + + tn.dir.normalize (); + + if (!exists (tn.dir)) + fail (loc) << "compilation database directory " << tn.dir + << " does not exist"; + + if (tn.value.empty ()) + tn.value = "compile_commands.json"; + + tns.push_back (name (tn.dir.leaf ().string ())); + tns.back ().pair = '@'; + tns.push_back (move (tn)); + } + } + // We inject the database directly into the outer amalgamation's // module, as-if config.cc.compiledb.name was specified in its // scope. Unless there isn't one, in which case it's us. @@ -773,7 +871,7 @@ namespace build2 // enable_filter = add_cdbs ( (p.second != nullptr ? *p.second : m).cdb_names_, - ns, + tns.empty () ? ns : tns, p.first->out_path ()); } -- cgit v1.1