From a84ff43b183181e0a12c6d5e31c1f366d39ce2fe Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 31 Jul 2017 18:42:47 +0200 Subject: Experimental (and probably broken) pkg-config generation support --- build2/algorithm.cxx | 23 +-- build2/algorithm.hxx | 27 +++- build2/algorithm.ixx | 8 ++ build2/bin/rule.cxx | 12 +- build2/cc/common.cxx | 7 +- build2/cc/common.hxx | 15 +- build2/cc/init.cxx | 7 +- build2/cc/install.cxx | 33 ++++- build2/cc/install.hxx | 9 ++ build2/cc/link.cxx | 120 +++++++++------- build2/cc/link.hxx | 2 +- build2/cc/pkgconfig.cxx | 341 ++++++++++++++++++++++++++++++++++---------- build2/install/init.cxx | 10 +- build2/install/rule.cxx | 60 ++++---- build2/install/rule.hxx | 8 +- build2/install/utility.hxx | 6 + build2/pkgconfig/init.cxx | 24 +++- build2/pkgconfig/target.cxx | 54 +++++++ build2/pkgconfig/target.hxx | 48 +++++++ build2/target.cxx | 20 --- build2/target.hxx | 27 +++- build2/variable.cxx | 2 +- build2/variable.hxx | 9 +- build2/variable.txx | 22 +++ 24 files changed, 674 insertions(+), 220 deletions(-) create mode 100644 build2/pkgconfig/target.cxx create mode 100644 build2/pkgconfig/target.hxx diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index dc1c418..29bae6d 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -262,19 +262,20 @@ namespace build2 n += s; } - const target& m (t.member != nullptr // Might already be there. - ? *t.member + const_ptr* mp (&t.member); + for (auto p (*mp); p != nullptr && !p->is_a (tt); mp = &p->member) ; + + const target& m (*mp != nullptr // Might already be there. + ? **mp : search (t, tt, t.dir, t.out, n)); target_lock l (lock (a, m)); assert (l.target != nullptr); // Someone messing with ad hoc members? - if (t.member == nullptr) - t.member = l.target; + if (*mp == nullptr) + *mp = l.target; else - // Basic sanity check. - // - assert (t.member->type () == tt && t.member->name == n); + assert ((*mp)->name == n); // Basic sanity check. return l; }; @@ -513,7 +514,7 @@ namespace build2 catch (const failed&) { // As a sanity measure clear the target data since it can be incomplete - // or invalid (mark()/unmark() should give you some for ideas). + // or invalid (mark()/unmark() should give you some ideas). // t.clear_data (); t.prerequisite_targets.clear (); @@ -619,6 +620,10 @@ namespace build2 // Fall through to apply. } + // @@ Doing match without execute messes up our target_count. Does + // not seem like it will be easy to fix (we don't know whether + // someone else will execute this target). + // case target::offset_matched: { // Apply (locked). @@ -1234,7 +1239,7 @@ namespace build2 noop_action (action a, const target& t) { text << "noop action triggered for " << diag_doing (a, t); - assert (false); // We shouldn't be called, see target::recipe(). + assert (false); // We shouldn't be called (see target::recipe()). return target_state::unchanged; } diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx index 2006e2a..931b396 100644 --- a/build2/algorithm.hxx +++ b/build2/algorithm.hxx @@ -136,14 +136,37 @@ namespace build2 target_lock lock (action, const target&); - // Add an ad hoc member. If the suffix is specified, it is added (as an - // extension) to the member's target name. Return the locked member target. + // Add an ad hoc member to the end of the chain assuming that an already + // existing member of this target type is the same. + // + // If the suffix is specified, it is added (as an extension) to the member's + // target name. Return the locked member target. // target_lock add_adhoc_member (action, target&, const target_type&, const char* suffix = nullptr); + template + inline target_lock + add_adhoc_member (action a, target& t, const char* s = nullptr) + { + return add_adhoc_member (a, t, T::static_type, s); + } + + // Find an ad hoc member of the specified target type returning NULL if not + // found. + // + const target* + find_adhoc_member (const target&, const target_type&); + + template + inline const T* + find_adhoc_member (const target& t) + { + return static_cast (find_adhoc_member (t, T::static_type)); + } + // Match and apply a rule to the action/target with ambiguity detection. // Increment the target's dependents count, which means that you should call // this function with the intent to also call execute(). Return the target diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx index 892c832..5680288 100644 --- a/build2/algorithm.ixx +++ b/build2/algorithm.ixx @@ -7,6 +7,14 @@ namespace build2 { + inline const target* + find_adhoc_member (const target& t, const target_type& tt) + { + const target* m (t.member); + for (; m != nullptr && !m->is_a (tt); m = m->member) ; + return m; + } + inline const target& search (const target& t, const prerequisite& p) { diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index bb9036b..7d32e26 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -38,7 +38,7 @@ namespace build2 // our prerequisites. // match_result lib_rule:: - match (action act, target& xt, const string&) const + match (action, target& xt, const string&) const { lib& t (xt.as ()); @@ -57,15 +57,7 @@ namespace build2 t.a = a ? &search (t, t.dir, t.out, t.name) : nullptr; t.s = s ? &search (t, t.dir, t.out, t.name) : nullptr; - match_result mr (true); - - // If there is an outer operation, indicate that we match - // unconditionally so that we don't override ourselves. - // - if (act.outer_operation () != 0) - mr.recipe_action = action (act.meta_operation (), act.operation ()); - - return mr; + return true; } recipe lib_rule:: diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index ce8415c..6f51662 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -869,8 +869,11 @@ namespace build2 // information. The idea is that when we auto-generate .pc files, we // will copy those macros (or custom ones) from *.export.poptions. // - if (pkgconfig == nullptr || !pkgconfig_extract ( - act, *p.scope, *lt, a, s, p.proj, name, *pd, sysd)) + if (pkgconfig == nullptr || + !pkgconfig_load (act, *p.scope, + *lt, a, s, + p.proj, name, + *pd, sysd)) { if (a != nullptr) add_macro (*a, "STATIC"); if (s != nullptr) add_macro (*s, "SHARED"); diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 951863a..66c226c 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -266,15 +266,12 @@ namespace build2 extract_library_dirs (const scope&) const; bool - pkgconfig_extract (action, - const scope&, - bin::lib&, - bin::liba*, - bin::libs*, - const optional&, - const string&, - const dir_path&, - const dir_paths&) const; // pkgconfig.cxx + pkgconfig_load (action, const scope&, + bin::lib&, bin::liba*, bin::libs*, + const optional&, + const string&, + const dir_path&, + const dir_paths&) const; // pkgconfig.cxx // Alternative search logic for VC (msvc.cxx). // diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index ea42469..8dceda7 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -248,14 +248,15 @@ namespace build2 load_module (rs, rs, "bin.rc.config", loc); } - // Load (optionally) the pkgconfig.config module. + // Load (optionally) the pkgconfig module. Note that it registers the + // pc{} target whether the pkg-config utility is found or not. // // @@ At some point we may also want to verify that targets matched // if it has already been loaded (by someone) else. Currently it // doesn't set pkgconfig.target. Perhaps only set if it was used // to derive the program name? // - if (!cast_false (rs["pkgconfig.config.loaded"])) + if (!cast_false (rs["pkgconfig.loaded"])) { // Prepare configuration hints. // @@ -266,7 +267,7 @@ namespace build2 const variable& t (vp.insert ("config.pkgconfig.target")); h.assign (t) = cast (rs["cc.target"]); - load_module (rs, rs, "pkgconfig.config", loc, true, h); + load_module (rs, rs, "pkgconfig", loc, true, h); } return true; diff --git a/build2/cc/install.cxx b/build2/cc/install.cxx index a0dc9b8..c65e7f6 100644 --- a/build2/cc/install.cxx +++ b/build2/cc/install.cxx @@ -94,12 +94,43 @@ namespace build2 { file* f; if ((f = t.is_a ()) != nullptr && tclass != "windows") - t.data (link_.derive_libs_paths (*f)); + { + const string* p (cast_null (t["bin.lib.prefix"])); + const string* s (cast_null (t["bin.lib.suffix"])); + t.data ( + link_.derive_libs_paths (*f, + p != nullptr ? p->c_str (): nullptr, + s != nullptr ? s->c_str (): nullptr)); + } } return r; } + target_state file_install:: + update_extra (action act, const target& t) const + { + // (Re)generate pkg-config's .pc file. While the target itself might be + // up-to-date from a previous run, there is no guarantee that .pc exists + // or also up-to-date. So to keep things simple we just regenerate it + // unconditionally. + // + // Also, if you are wondering why don't we just always produce this .pc, + // install or no install, the reason is unless and until we are updating + // for install, we have no idea where to things will be installed. + // + bool a; + const file* f; + + if ((a = (f = t.is_a ())) || + ( f = t.is_a ())) + { + pkgconfig_save (act, *f, a); + } + + return target_state::unchanged; + } + void file_install:: install_extra (const file& t, const install_dir& id) const { diff --git a/build2/cc/install.hxx b/build2/cc/install.hxx index a846fc8..2dd2b71 100644 --- a/build2/cc/install.hxx +++ b/build2/cc/install.hxx @@ -41,6 +41,15 @@ namespace build2 virtual bool uninstall_extra (const file&, const install_dir&) const override; + virtual target_state + update_extra (action, const target&) const override; + + private: + // pkg-config's .pc file generation (in pkgconfig.cxx). + // + void + pkgconfig_save (action, const file&, bool) const; + private: const link& link_; }; diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 1cb3a11..08b5314 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -19,6 +19,7 @@ #include #include +#include #include // c #include @@ -142,34 +143,33 @@ namespace build2 } auto link:: - derive_libs_paths (file& ls) const -> libs_paths + derive_libs_paths (file& ls, const char* pfx, const char* sfx) const + -> libs_paths { const char* ext (nullptr); - const char* pfx (nullptr); - const char* sfx (nullptr); bool win (tclass == "windows"); if (win) { if (tsys == "mingw32") - pfx = "lib"; + { + if (pfx == nullptr) + pfx = "lib"; + } ext = "dll"; } - else if (tclass == "macos") - { - pfx = "lib"; - ext = "dylib"; - } else { - pfx = "lib"; - ext = "so"; - } + if (pfx == nullptr) + pfx = "lib"; - if (auto l = ls["bin.lib.prefix"]) pfx = cast (l).c_str (); - if (auto l = ls["bin.lib.suffix"]) sfx = cast (l).c_str (); + if (tclass == "macos") + ext = "dylib"; + else + ext = "so"; + } // First sort out which extension we are using. // @@ -315,14 +315,6 @@ namespace build2 // Derive file name(s) and add ad hoc group members. // - - // Add if necessary and lock an ad hoc group member. - // - auto add_adhoc = [act, &bs] (target& t, const char* type) -> target_lock - { - return add_adhoc_member (act, t, *bs.find_target_type (type)); - }; - { target_lock libi; // Have to hold until after PDB member addition. @@ -391,6 +383,11 @@ namespace build2 } else { + if (auto l = t[ot == otype::e ? "bin.exe.prefix" : "bin.lib.prefix"]) + p = cast (l).c_str (); + if (auto l = t[ot == otype::e ? "bin.exe.suffix" : "bin.lib.suffix"]) + s = cast (l).c_str (); + switch (ot) { case otype::e: @@ -400,9 +397,6 @@ namespace build2 else e = ""; - if (auto l = t["bin.exe.prefix"]) p = cast (l).c_str (); - if (auto l = t["bin.exe.suffix"]) s = cast (l).c_str (); - t.derive_path (e, p, s); break; } @@ -412,13 +406,10 @@ namespace build2 e = "lib"; else { - p = "lib"; + if (p == nullptr) p = "lib"; e = "a"; } - if (auto l = t["bin.lib.prefix"]) p = cast (l).c_str (); - if (auto l = t["bin.lib.suffix"]) s = cast (l).c_str (); - t.derive_path (e, p, s); break; } @@ -428,9 +419,9 @@ namespace build2 // the DLL and we add libi{} import library as its member. // if (tclass == "windows") - libi = add_adhoc (t, "libi"); + libi = add_adhoc_member (act, t); - t.data (derive_libs_paths (t)); // Cache in target. + t.data (derive_libs_paths (t, p, s)); // Cache in target. if (libi) match_recipe (libi, group_recipe); // Set recipe and unlock. @@ -438,26 +429,57 @@ namespace build2 break; } } - } - // PDB - // - if (!lt.static_library () && - cid == compiler_id::msvc && - (find_option ("/DEBUG", t, c_loptions, true) || - find_option ("/DEBUG", t, x_loptions, true))) - { - // Add after the import library if any. + // Add VC's .pdb. // - target_lock pdb ( - add_adhoc (t.member == nullptr ? t : *t.member, "pdb")); + if (ot != otype::a && + cid == compiler_id::msvc && + (find_option ("/DEBUG", t, c_loptions, true) || + find_option ("/DEBUG", t, x_loptions, true))) + { + // Note: add after the import library if any. + // + target_lock pdb ( + add_adhoc_member (act, t, *bs.find_target_type ("pdb"))); + + // We call it foo.{exe,dll}.pdb rather than just foo.pdb because + // we can have both foo.exe and foo.dll in the same directory. + // + pdb.target->as ().derive_path (t.path (), "pdb"); + + match_recipe (pdb, group_recipe); // Set recipe and unlock. + } - // We call it foo.{exe,dll}.pdb rather than just foo.pdb because we - // can have both foo.exe and foo.dll in the same directory. + // Add pkg-config's .pc file. // - pdb.target->as ().derive_path (t.path (), "pdb"); + // Note that we do it here regardless of whether we are installing + // or not for two reasons. Firstly, it is not easy to detect this + // situation in apply() since the action may (and is) overridden to + // unconditional install. Secondly, always having the member takes + // care of cleanup automagically. The actual generation happens in + // the install rule. + // + if (ot != otype::e) + { + target_lock pc ( + add_adhoc_member ( + act, t, + ot == otype::a + ? pkgconfig::pca::static_type + : pkgconfig::pcs::static_type)); + + // Note that here we always use the lib name prefix, even on + // Windows with VC. The reason is the user needs a consistent name + // across platforms by which they can refere to the library. This + // is also the reason why we use the static/shared suffixes rather + // that a./.lib/.so/.dylib/.dll. + // + pc.target->as ().derive_path (nullptr, + (p == nullptr ? "lib" : p), + s); - match_recipe (pdb, group_recipe); // Set recipe and unlock. + match_recipe (pc, group_recipe); // Set recipe and unlock. + } } } @@ -1164,12 +1186,12 @@ namespace build2 { tracer trace (x, "link::perform_update"); - const file& t (xt.as ()); - const path& tp (t.path ()); - auto oop (act.outer_operation ()); bool for_install (oop == install_id || oop == uninstall_id); + const file& t (xt.as ()); + const path& tp (t.path ()); + const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); diff --git a/build2/cc/link.hxx b/build2/cc/link.hxx index c4c9c9e..cadc82c 100644 --- a/build2/cc/link.hxx +++ b/build2/cc/link.hxx @@ -70,7 +70,7 @@ namespace build2 }; libs_paths - derive_libs_paths (file&) const; + derive_libs_paths (file&, const char*, const char*) const; // Library handling. // diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 80f418c..3fc40ad 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -6,15 +6,20 @@ #include #include #include +#include #include #include +#include + #include +#include #include #include #include +#include using namespace std; using namespace butl; @@ -27,7 +32,7 @@ namespace build2 // Try to find a .pc file in the pkgconfig/ subdirectory of libd, trying // several names derived from stem. If not found, return false. If found, - // extract poptions, loptions, and libs, set the corresponding *.export.* + // load poptions, loptions, and libs, set the corresponding *.export.* // variables on targets, and return true. // // System library search paths (those extracted from the compiler) are @@ -37,35 +42,36 @@ namespace build2 // search_library() POV. // bool common:: - pkgconfig_extract (action act, - const scope& s, - lib& lt, - liba* at, - libs* st, - const optional& proj, - const string& stem, - const dir_path& libd, - const dir_paths& sysd) const + pkgconfig_load (action act, + const scope& s, + lib& lt, + liba* at, + libs* st, + const optional& proj, + const string& stem, + const dir_path& libd, + const dir_paths& sysd) const { - tracer trace (x, "pkgconfig_extract"); + tracer trace (x, "pkgconfig_load"); assert (pkgconfig != nullptr); assert (at != nullptr || st != nullptr); // When it comes to looking for .pc files we have to decide where to // search (which directory(ies)) as well as what to search for (which - // names). + // names). Suffix is our ".shared" or ".static" extension. // - path f; - auto search = [&f, &proj, &stem, &libd] (const dir_path& dir) -> bool + auto search_dir = [&proj, &stem, &libd] (const dir_path& dir, + const string& sfx) -> path { - // Check if we have this directory inrelative to this library's - // directory. + // Check if we have this subdirectory in this library's directory. // dir_path pkgd (dir_path (libd) /= dir); if (!exists (pkgd)) - return false; + return path (); + + path f; // See if there is a corresponding .pc file. About half of them called // foo.pc and half libfoo.pc (and one of the pkg-config's authors @@ -73,70 +79,90 @@ namespace build2 // things interesting, you know). // // Given the (general) import in the form %lib{}, we will - // first try .pc, then lib.pc. Maybe it also makes sense + // first try lib.pc, then .pc. Maybe it also makes sense // to try .pc, just in case. Though, according to pkg-config // docs, the .pc file should correspond to a library, not project. But // then you get something like zlib which calls it zlib.pc. So let's // just do it. // f = pkgd; - f /= stem; + f /= "lib"; + f += stem; + f += sfx; f += ".pc"; - if (exists (f)) - return true; + return f; f = pkgd; - f /= "lib"; - f += stem; + f /= stem; + f += sfx; f += ".pc"; - if (exists (f)) - return true; + return f; if (proj) { f = pkgd; f /= *proj; + f += sfx; f += ".pc"; - if (exists (f)) - return true; + return f; } - return false; + return path (); }; - // First always check the pkgconfig/ subdirectory in this library's - // directory. Even on platforms where this is not the canonical place, - // .pc files of autotools-based packages installed by the user often - // still end up there. - // - if (!search (dir_path ("pkgconfig"))) + auto search = [&search_dir, this] () -> pair { - // Platform-specific locations. + pair r; + + auto check = [&r, &search_dir] (const dir_path& d) -> bool + { + // First look for static/shared-specific files. + // + r.first = search_dir (d, ".static"); + r.second = search_dir (d, ".shared"); + + if (!r.first.empty () || !r.second.empty ()) + return true; + + // Then the common. + // + r.first = r.second = search_dir (d, string ()); + return !r.first.empty (); + }; + + // First always check the pkgconfig/ subdirectory in this library's + // directory. Even on platforms where this is not the canonical place, + // .pc files of autotools-based packages installed by the user often + // still end up there. // - if (tsys == "freebsd") + if (!check (dir_path ("pkgconfig"))) { - // On FreeBSD .pc files go to libdata/pkgconfig/, not lib/pkgconfig/. + // Platform-specific locations. // - if (!search ((dir_path ("..") /= "libdata") /= "pkgconfig")) - return false; + if (tsys == "freebsd") + { + // On FreeBSD .pc files go to libdata/pkgconfig/, not lib/pkgconfig/. + // + check ((dir_path ("..") /= "libdata") /= "pkgconfig"); + } } - else - return false; - } - // Ok, we are in business. Time to run pkg-config. To keep things - // simple, we run it multiple times, for --cflag/--libs and --static. + return r; + }; + + // To keep things simple, we run pkg-config multiple times, for + // --cflag/--libs and --static. // - auto extract = [&f, this] (const char* op, bool impl) -> string + auto extract = [this] (const path& f, const char* o, bool a) -> string { const char* args[] = { pkgconfig->recall_string (), - op, // --cflags/--libs - (impl ? "--static" : f.string ().c_str ()), - (impl ? f.string ().c_str () : nullptr), + o, // --cflags/--libs + (a ? "--static" : f.string ().c_str ()), + (a ? f.string ().c_str () : nullptr), nullptr }; @@ -144,10 +170,9 @@ namespace build2 *pkgconfig, args, [] (string& s) -> string {return move (s);}); }; - // On Windows pkg-config (at least the MSYS2 one which we are using) - // will escape backslahses in paths. In fact, it may escape things - // even on non-Windows platforms, for example, spaces. So we use a - // slightly modified version of next_word(). + // On Windows pkg-config will escape backslahses in paths. In fact, it + // may escape things even on non-Windows platforms, for example, + // spaces. So we use a slightly modified version of next_word(). // auto next = [] (const string& s, size_t& b, size_t& e) -> string { @@ -184,12 +209,13 @@ namespace build2 return r; }; - // First extract --cflags and set them as lib{}:export.poptions (i.e., - // they will be common for both liba{} and libs{}; later when we do - // split .pc files, we will have to run this twice). + // Extract --cflags and set them as lib?{}:export.poptions. Note that we + // still pass --static in case this is pkgconf which has Cflags.private. // + auto parse_cflags = [&trace, &extract, &next, this] + (target& t, const path& f, bool a) { - string cstr (extract ("--cflags", false)); + string cstr (extract (f, "--cflags", a)); strings pops; string o; @@ -243,7 +269,7 @@ namespace build2 if (!pops.empty ()) { - auto p (lt.vars.insert (c_export_poptions)); + auto p (t.vars.insert (c_export_poptions)); // The only way we could already have this value is if this same // library was also imported as a project (as opposed to installed). @@ -253,13 +279,15 @@ namespace build2 if (p.second) p.first.get () = move (pops); } - } + }; - // Now parse --libs into loptions/libs (interface and implementation). + // Parse --libs into loptions/libs (interface and implementation). // - auto parse_libs = [act, &s, &f, sysd, &next, this] ( - const string& lstr, target& t) + auto parse_libs = [act, &s, sysd, &extract, &next, this] + (target& t, const path& f, bool a) { + string lstr (extract (f, "--libs", a)); + strings lops; vector libs; @@ -274,7 +302,6 @@ namespace build2 // library. What we do at the moment is stop recognizing just // library names (without -l) after seeing an unknown option. // - bool arg (false), first (true), known (true), have_L; string o; for (size_t b (0), e (0); !(o = next (lstr, b, e)).empty (); ) @@ -438,8 +465,9 @@ namespace build2 all = false; } - // If all the -l's resolved and no other options, then drop all the - // -L's. If we have unknown options, then leave them in to be safe. + // If all the -l's resolved and there were no other options, then drop + // all the -L's. If we have unknown options, then leave them in to be + // safe. // if (all && known) lops.clear (); @@ -487,20 +515,187 @@ namespace build2 } }; - { - string lstr_int (extract ("--libs", false)); - string lstr_imp (extract ("--libs", true)); + pair ps (search ()); + const path& ap (ps.first); + const path& sp (ps.second); - parse_libs (lstr_int, lt); + if (ap.empty () && sp.empty ()) + return false; - // Currently, these will result in the same values but it will be - // different once we support split .pc files. - // - if (at != nullptr) parse_libs (lstr_imp, *at); - if (st != nullptr) parse_libs (lstr_imp, *st); + if (at != nullptr && !ap.empty ()) + { + parse_cflags (*at, ap, true); + parse_libs (*at, ap, true); + } + + if (st != nullptr && !sp.empty ()) + { + parse_cflags (*st, sp, false); + parse_libs (lt, sp, false); // Note: setting on lib{} (interface). } return true; } + + void file_install:: + pkgconfig_save (action act, const file& l, bool la) const + { + tracer trace (x, "pkgconfig_save"); + + const scope& bs (l.base_scope ()); + const scope& rs (*bs.root_scope ()); + + auto* pc (find_adhoc_member (l)); + assert (pc != nullptr); + + const path& p (pc->path ()); + + if (verb >= 2) + text << "cat >" << p; + + try + { + ofdstream os (p); + auto_rmfile arm (p); + + // @@ version may not be string, need to reverse. + // + os << "Name: " << cast (rs.vars[var_project]) << endl; + os << "Version: " << cast (rs.vars["version"]) << endl; + os << "Description: @@ TODO" << endl; + + // By default we assume things go into install.{include, lib}. + // + // @@ TODO: quoting/whitespace escaping. + // @@ TODO: support whole archive? + // + using install::resolve_dir; + + dir_path id (resolve_dir (l, cast (l["install.include"]))); + + auto save_poptions = [&l, &os] (const variable& var) + { + if (const strings* v = cast_null (l[var])) + { + for (auto i (v->begin ()); i != v->end (); ++i) + { + const string& o (*i); + size_t n (o.size ()); + + // Filter out -I (both -I and -I forms). + // + if (n >= 2 && o[0] == '-' && o[1] == 'I') + { + if (n == 2) + ++i; + + continue; + } + + os << ' ' << o; + } + } + }; + + // Given a library save its -l-style library name. + // + auto save_library = [&os] (const file& l) + { + // Use the .pc file name to derive the -l library name (in case of + // the shared library, l.path() may contain version). + // + auto* pc (find_adhoc_member (l)); + assert (pc != nullptr); + + // We also want to strip the lib prefix unless it is part of the + // target name while keeping custom library prefix/suffix, if any. + // + string n (pc->path ().leaf ().base ().base ().string ()); + if (n.size () > 3 && + path::traits::compare (n.c_str (), 3, "lib", 3) == 0 && + path::traits::compare (n.c_str (), n.size (), + l.name.c_str (), l.name.size ()) != 0) + n.erase (0, 3); + + os << " -l" << n; + }; + + // Cflags. + // + os << "Cflags:"; + os << " -I" << id; + save_poptions (c_export_poptions); + save_poptions (x_export_poptions); + os << endl; + + // Libs. + // + dir_path ld (resolve_dir (l, cast (l["install.lib"]))); + + os << "Libs:"; + os << " -L" << ld; + + // Now process ourselves as if we were being linked to something (so + // pretty similar to link::append_libraries()). + // + auto imp = [] (const file&, bool la) {return la;}; + + auto lib = [&os, &save_library] (const file* l, + const string& p, + lflags, + bool) + { + if (l != nullptr) + { + if (l->is_a () || l->is_a ()) // See through libux. + save_library (*l); + } + else + os << ' ' << p; // Something "system'y", pass as is. + }; + + auto opt = [&os] (const file&, + const string&, + bool, bool) + { + //@@ TODO: should we filter -L similar to -I? + + /* + // If we need an interface value, then use the group (lib{}). + // + if (const target* g = exp && l.is_a () ? l.group : &l) + { + const variable& var ( + com + ? (exp ? c_export_loptions : c_loptions) + : (t == x + ? (exp ? x_export_loptions : x_loptions) + : var_pool[t + (exp ? ".export.loptions" : ".loptions")])); + + append_options (args, *g, var); + } + */ + }; + + process_libraries ( + act, + bs, + linfo {otype::e, la ? lorder::a_s : lorder::s_a}, // System-default. + sys_lib_dirs, + l, la, + 0, // Link flags. + imp, lib, opt, + true); + + os << endl; + + os.close (); + arm.cancel (); + } + catch (const io_error& e) + { + fail << "unable to write " << p << ": " << e; + } + } } } diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 1d51be2..0e8118a 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -141,10 +141,11 @@ namespace build2 static const dir_path dir_root ("root"); - static const dir_path dir_sbin (dir_path ("exec_root") /= "sbin"); - static const dir_path dir_bin (dir_path ("exec_root") /= "bin"); - static const dir_path dir_lib (dir_path ("exec_root") /= "lib"); - static const dir_path dir_libexec (dir_path ("exec_root") /= "libexec"); + static const dir_path dir_sbin (dir_path ("exec_root") /= "sbin"); + static const dir_path dir_bin (dir_path ("exec_root") /= "bin"); + static const dir_path dir_lib (dir_path ("exec_root") /= "lib"); + static const dir_path dir_libexec (dir_path ("exec_root") /= "libexec"); + static const dir_path dir_pkgconfig (dir_path ("lib") /= "pkgconfig"); static const dir_path dir_data (dir_path ("data_root") /= "share"); static const dir_path dir_include (dir_path ("data_root") /= "include"); @@ -240,6 +241,7 @@ namespace build2 set_dir (s, rs, "bin", dir_bin); set_dir (s, rs, "lib", dir_lib); set_dir (s, rs, "libexec", dir_path (dir_libexec) /= n, true); + set_dir (s, rs, "pkgconfig", dir_pkgconfig, false, "644"); set_dir (s, rs, "data", dir_path (dir_data) /= n, true); set_dir (s, rs, "include", dir_include); diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index f2fc233..6b75cea 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -199,35 +199,30 @@ namespace build2 // if (a.operation () == update_id) { - // Save the prerequisite targets that we found since the - // call to match_delegate() below will wipe them out. + // Save the prerequisite targets that we found since the call to + // match_delegate() below will wipe them out. // prerequisite_targets pts; + pts.swap (t.prerequisite_targets); - if (!t.prerequisite_targets.empty ()) - pts.swap (t.prerequisite_targets); - - // Find the "real" update rule, that is, the rule that would - // have been found if we signalled that we do not match from - // match() above. + // Find the "real" update rule, that is, the rule that would have been + // found if we signalled that we do not match from match() above. // recipe d (match_delegate (a, t, *this).first); - // If we have no installable prerequisites, then simply redirect - // to it. - // - if (pts.empty ()) - return d; - - // Ok, the worst case scenario: we need to cause update of - // prerequisite targets and also delegate to the real update. - // - return [pts = move (pts), d = move (d)] ( - action a, const target& t) mutable -> target_state + return [pts = move (pts), d = move (d), this] + (action a, const target& t) mutable -> target_state { - // Do the target update first. + // Do the target update first (we cannot call noop_recipe). // - target_state r (execute_delegate (d, a, t)); + recipe_function** f (d.target ()); + target_state r (f != nullptr && *f == &noop_action + ? target_state::unchanged + : execute_delegate (d, a, t)); + + // Then the extra hook. + // + r |= update_extra (a, t); // Swap our prerequisite targets back in and execute. // @@ -251,10 +246,21 @@ namespace build2 } void file_rule:: - install_extra (const file&, const install_dir&) const {} + install_extra (const file&, const install_dir&) const + { + } bool file_rule:: - uninstall_extra (const file&, const install_dir&) const {return false;} + uninstall_extra (const file&, const install_dir&) const + { + return false; + } + + target_state file_rule:: + update_extra (action, const target&) const + { + return target_state::unchanged; + } struct install_dir { @@ -296,7 +302,7 @@ namespace build2 // for (const scope* p (&s); p != nullptr; p = p->parent_scope ()) { - if (l.belongs (*p)) // Ok since no target/type in lookup. + if (l.belongs (*p, true)) // Include target type/pattern-specific. { // The target can be in out or src. // @@ -388,6 +394,12 @@ namespace build2 return rs; } + dir_path + resolve_dir (const target& t, dir_path d) + { + return move (resolve (t, move (d)).back ().dir); + } + // On Windows we use MSYS2 install.exe and MSYS2 by default ignores // filesystem permissions (noacl mount option). And this means, for // example, that .exe that we install won't be runnable by Windows (MSYS2 diff --git a/build2/install/rule.hxx b/build2/install/rule.hxx index ad0d8ec..617beab 100644 --- a/build2/install/rule.hxx +++ b/build2/install/rule.hxx @@ -70,6 +70,12 @@ namespace build2 virtual bool uninstall_extra (const file&, const install_dir&) const; + // Extra update for install hooks. It is executed after the target has + // been updated but only for those that will actually be installed. + // + virtual target_state + update_extra (action, const target&) const; + // Installation "commands". // // If verbose is false, then only print the command at verbosity level 2 @@ -98,7 +104,7 @@ namespace build2 const path& name, bool verbose); - private: + protected: target_state perform_install (action, const target&) const; diff --git a/build2/install/utility.hxx b/build2/install/utility.hxx index dfd1915..21c0c44 100644 --- a/build2/install/utility.hxx +++ b/build2/install/utility.hxx @@ -51,6 +51,12 @@ namespace build2 { return install_mode (s, T::static_type, move (m)); } + + // Resolve relative installation directory path (e.g., include/libfoo) to + // its absolute directory path (e.g., /usr/include/libfoo). + // + dir_path + resolve_dir (const target&, dir_path); // rule.cxx } } diff --git a/build2/pkgconfig/init.cxx b/build2/pkgconfig/init.cxx index fa22421..9526aa7 100644 --- a/build2/pkgconfig/init.cxx +++ b/build2/pkgconfig/init.cxx @@ -10,6 +10,9 @@ #include #include +#include + +#include using namespace std; using namespace butl; @@ -128,7 +131,7 @@ namespace build2 bool init (scope& rs, - scope& bs, + scope&, const location& loc, unique_ptr&, bool, @@ -136,14 +139,15 @@ namespace build2 const variable_map& hints) { tracer trace ("pkgconfig::init"); - l5 ([&]{trace << "for " << bs.out_path ();}); + l5 ([&]{trace << "for " << rs.out_path ();}); // Load pkgconfig.config. // + bool conf (true); if (!cast_false (rs["pkgconfig.config.loaded"])) { if (!load_module (rs, rs, "pkgconfig.config", loc, optional, hints)) - return false; + conf = false; } else if (!cast_false (rs["pkgconfig.config.configured"])) { @@ -151,13 +155,21 @@ namespace build2 fail << "pkgconfig module could not be configured" << info << "re-run with -V option for more information"; - return false; + conf = false; } - // For now pkgconfig and pkgconfig.config is pretty much the same. + // Register the target type and configure its default "installability". + // + // Note that we do it whether we found pkg-config or not since these are + // used to produce .pc files which we do regardless. // + rs.target_types.insert (); + rs.target_types.insert (); - return true; + if (cast_false (rs["install.loaded"])) + install::install_path (rs, dir_path ("pkgconfig")); + + return conf; } } } diff --git a/build2/pkgconfig/target.cxx b/build2/pkgconfig/target.cxx new file mode 100644 index 0000000..7752f58 --- /dev/null +++ b/build2/pkgconfig/target.cxx @@ -0,0 +1,54 @@ +// file : build2/pkgconfig/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace pkgconfig + { + const target_type pc::static_type + { + "pc", + &file::static_type, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + false + }; + + extern const char pca_ext[] = "static.pc"; // VC14 rejects constexpr. + + const target_type pca::static_type + { + "pca", + &pc::static_type, + &file_factory, + &target_extension_fix, + &target_pattern_fix, + &target_print_0_ext_verb, // Fixed extension, no use printing. + &file_search, + false + }; + + extern const char pcs_ext[] = "shared.pc"; // VC14 rejects constexpr. + + const target_type pcs::static_type + { + "pcs", + &pc::static_type, + &file_factory, + &target_extension_fix, + &target_pattern_fix, + &target_print_0_ext_verb, // Fixed extension, no use printing. + &file_search, + false + }; + } +} diff --git a/build2/pkgconfig/target.hxx b/build2/pkgconfig/target.hxx new file mode 100644 index 0000000..feb8d80 --- /dev/null +++ b/build2/pkgconfig/target.hxx @@ -0,0 +1,48 @@ +// file : build2/pkgconfig/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PKGCONFIG_TARGET_HXX +#define BUILD2_PKGCONFIG_TARGET_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace pkgconfig + { + class pc: public file + { + public: + using file::file; + + public: + static const target_type static_type; + }; + + class pca: public pc // .static.pc + { + public: + using pc::pc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class pcs: public pc // .shared.pc + { + public: + using pc::pc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_PKGCONFIG_TARGET_HXX diff --git a/build2/target.cxx b/build2/target.cxx index 3ee9850..40c73c0 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -700,26 +700,6 @@ namespace build2 false }; - template - static pair> - file_factory (const target_type& tt, - dir_path d, - dir_path o, - string n, - optional e) - { - // A generic file target type doesn't imply any extension while a very - // specific one (say man1) may have a fixed extension. So if one wasn't - // specified and this is not a dynamically derived target type, then set - // it to fixed ext rather than unspecified. For file{} we make it empty - // which means we treat file{foo} as file{foo.}. - // - if (!e && ext != nullptr && tt.factory == &file_factory) - e = string (ext); - - return make_pair (new T (move (d), move (o), move (n)), move (e)); - } - extern const char file_ext_var[] = "extension"; // VC14 rejects constexpr. extern const char file_ext_def[] = ""; diff --git a/build2/target.hxx b/build2/target.hxx index 7cc7f95..3af533f 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -250,7 +250,8 @@ namespace build2 // - Member variable lookup skips the ad hoc group (since the group is // the first member, this is normally what we want). // - // Use add_adhoc_member() from algorithms to add an ad hoc member. + // Use add_adhoc_member(), find_adhoc_member() from algorithms to manage + // ad hoc members. // const_ptr member = nullptr; @@ -1686,8 +1687,8 @@ namespace build2 virtual const target_type& dynamic_type () const {return static_type;} }; - // Common implementation of the target factory, extension, and - // search functions. + // Common implementation of the target factory, extension, and search + // functions. // template pair> @@ -1700,6 +1701,26 @@ namespace build2 return make_pair (new T (move (d), move (o), move (n)), move (e)); } + template + static pair> + file_factory (const target_type& tt, + dir_path d, + dir_path o, + string n, + optional e) + { + // A generic file target type doesn't imply any extension while a very + // specific one (say man1) may have a fixed extension. So if one wasn't + // specified and this is not a dynamically derived target type, then set + // it to fixed ext rather than unspecified. For file{} itself we make it + // empty which means we treat file{foo} as file{foo.}. + // + if (!e && ext != nullptr && tt.factory == &file_factory) + e = string (ext); + + return make_pair (new T (move (d), move (o), move (n)), move (e)); + } + // Return fixed target extension. // template diff --git a/build2/variable.cxx b/build2/variable.cxx index 8e32b30..fbdedb3 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -1359,7 +1359,7 @@ namespace build2 vm.typify (const_cast (*v), var); } - return lookup (v, &vm); + return lookup (*v, vm); } } } diff --git a/build2/variable.hxx b/build2/variable.hxx index f0218fe..decc300 100644 --- a/build2/variable.hxx +++ b/build2/variable.hxx @@ -336,13 +336,18 @@ namespace build2 const value_type* operator-> () const {return value;} // Return true if this value belongs to the specified scope or target. - // Note that it can also be a target type/pattern-specific value (in - // which case it won't belong to either). + // Note that it can also be a target type/pattern-specific value in which + // case it won't belong to either unless we pass true as a second argument + // to consider it belonging to a scope (note that this test is expensive). // template bool belongs (const T& x) const {return vars == &x.vars;} + template + bool + belongs (const T& x, bool target_type_pattern) const; + lookup (): value (nullptr), vars (nullptr) {} template diff --git a/build2/variable.txx b/build2/variable.txx index 1373948..f75ffd6 100644 --- a/build2/variable.txx +++ b/build2/variable.txx @@ -6,6 +6,28 @@ namespace build2 { + template + bool lookup:: + belongs (const T& x, bool t) const + { + if (vars == &x.vars) + return true; + + if (t) + { + for (const auto& p1: x.target_vars) // variable_type_map + { + for (const auto& p2: p1.second) // variable_pattern_map + { + if (vars == &p2.second) + return true; + } + } + } + + return false; + } + // This one will be SFINAE'd out unless T is a simple value. // template -- cgit v1.1