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/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 +++++++++++++++++++++++++++++++++++++----------- 8 files changed, 396 insertions(+), 138 deletions(-) (limited to 'build2/cc') 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; + } + } } } -- cgit v1.1