diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-08-26 16:37:16 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-08-26 16:37:16 +0200 |
commit | f0edc0e2b67fa43c4e2410c7d3d8f1841d576749 (patch) | |
tree | 25ca1dd8c20649a1a9a35355670a1f5c8b99695c | |
parent | e347540d400c552917ca7067c4571f1028f6e1af (diff) |
Add pkg-config support for import installed
Redesign library importing/exporting while at it.
-rw-r--r-- | build2/b.cxx | 4 | ||||
-rw-r--r-- | build2/bin/init.cxx | 4 | ||||
-rw-r--r-- | build2/buildfile | 140 | ||||
-rw-r--r-- | build2/c/init.cxx | 2 | ||||
-rw-r--r-- | build2/cc/common | 4 | ||||
-rw-r--r-- | build2/cc/compile | 8 | ||||
-rw-r--r-- | build2/cc/compile.cxx | 108 | ||||
-rw-r--r-- | build2/cc/init.cxx | 20 | ||||
-rw-r--r-- | build2/cc/link | 28 | ||||
-rw-r--r-- | build2/cc/link.cxx | 395 | ||||
-rw-r--r-- | build2/cc/msvc.cxx | 1 | ||||
-rw-r--r-- | build2/cc/pkgconfig.cxx | 446 | ||||
-rw-r--r-- | build2/cc/utility | 13 | ||||
-rw-r--r-- | build2/cc/utility.cxx | 40 | ||||
-rw-r--r-- | build2/context.txx | 2 | ||||
-rw-r--r-- | build2/cxx/init.cxx | 2 | ||||
-rw-r--r-- | build2/pkgconfig/init | 37 | ||||
-rw-r--r-- | build2/pkgconfig/init.cxx | 159 | ||||
-rw-r--r-- | build2/utility | 2 | ||||
-rw-r--r-- | build2/utility.cxx | 11 | ||||
-rw-r--r-- | tests/import/installed/buildfile | 4 |
21 files changed, 1122 insertions, 308 deletions
diff --git a/build2/b.cxx b/build2/b.cxx index 81e1b2f..bb4f0fe 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -47,6 +47,7 @@ using namespace std; #include <build2/cli/init> #include <build2/test/init> #include <build2/install/init> +#include <build2/pkgconfig/init> using namespace build2; @@ -228,6 +229,9 @@ main (int argc, char* argv[]) bm["bin.rc.config"] = mf {nullptr, &bin::rc_config_init}; bm["bin.rc"] = mf {nullptr, &bin::rc_init}; + bm["pkgconfig.config"] = mf {nullptr, &pkgconfig::config_init}; + bm["pkgconfig"] = mf {nullptr, &pkgconfig::init}; + bm["cc.core.vars"] = mf {nullptr, &cc::core_vars_init}; bm["cc.core.config"] = mf {nullptr, &cc::core_config_init}; bm["cc.core"] = mf {nullptr, &cc::core_init}; diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 125bc7d..bf7dd69 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -30,8 +30,8 @@ namespace build2 // Default config.bin.*.lib values. // static const strings exe_lib {"shared", "static"}; - static const strings liba_lib {"static"}; - static const strings libs_lib {"shared"}; + static const strings liba_lib {"static", "shared"}; + static const strings libs_lib {"shared", "static"}; bool config_init (scope& r, diff --git a/build2/buildfile b/build2/buildfile index 3aa515f..ad5a023 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -4,75 +4,77 @@ import libs = libbutl%lib{butl} -exe{b}: \ - {hxx ixx cxx}{ algorithm } \ - { cxx}{ b } \ - {hxx ixx cxx}{ b-options } \ - {hxx txx cxx}{ context } \ - {hxx cxx}{ depdb } \ - {hxx cxx}{ diagnostics } \ - {hxx cxx}{ dump } \ - {hxx ixx cxx}{ file } \ - {hxx txx cxx}{ filesystem } \ - {hxx cxx}{ lexer } \ - {hxx cxx}{ module } \ - {hxx ixx cxx}{ name } \ - {hxx cxx}{ operation } \ - {hxx cxx}{ parser } \ - {hxx cxx}{ prerequisite } \ - {hxx cxx}{ rule } \ - {hxx }{ rule-map } \ - {hxx cxx}{ scope } \ - {hxx cxx}{ search } \ - {hxx cxx}{ spec } \ - {hxx ixx txx cxx}{ target } \ - {hxx }{ target-key } \ - {hxx }{ target-type } \ - {hxx cxx}{ token } \ - {hxx }{ types } \ - {hxx cxx}{ types-parsers } \ - {hxx ixx txx cxx}{ utility } \ - {hxx ixx txx cxx}{ variable } \ - {hxx }{ version } \ - bin/{hxx cxx}{ guess } \ - bin/{hxx cxx}{ init } \ - bin/{hxx cxx}{ rule } \ - bin/{hxx cxx}{ target } \ - c/{hxx cxx}{ init } \ - c/{hxx }{ target } \ - cc/{hxx }{ common } \ - cc/{hxx cxx}{ compile } \ - cc/{hxx cxx}{ guess } \ - cc/{hxx cxx}{ init } \ - cc/{hxx cxx}{ install } \ - cc/{hxx cxx}{ link } \ - cc/{hxx cxx}{ module } \ - cc/{ cxx}{ msvc } \ - cc/{hxx cxx}{ target } \ - cc/{hxx }{ types } \ - cc/{hxx ixx cxx}{ utility } \ - cc/{ cxx}{ windows-manifest } \ - cc/{ cxx}{ windows-rpath } \ - cli/{hxx cxx}{ init } \ - cli/{hxx cxx}{ rule } \ - cli/{hxx cxx}{ target } \ - config/{hxx cxx}{ init } \ - config/{hxx }{ module } \ - config/{hxx cxx}{ operation } \ - config/{hxx txx cxx}{ utility } \ - cxx/{hxx cxx}{ init } \ - cxx/{hxx cxx}{ target } \ - dist/{hxx cxx}{ init } \ - dist/{hxx cxx}{ operation } \ - dist/{hxx cxx}{ rule } \ -install/{hxx cxx}{ init } \ -install/{hxx cxx}{ operation } \ -install/{hxx cxx}{ rule } \ -install/{hxx }{ utility } \ - test/{hxx cxx}{ init } \ - test/{hxx cxx}{ operation } \ - test/{hxx cxx}{ rule } \ -$libs +exe{b}: \ + {hxx ixx cxx}{ algorithm } \ + { cxx}{ b } \ + {hxx ixx cxx}{ b-options } \ + {hxx txx cxx}{ context } \ + {hxx cxx}{ depdb } \ + {hxx cxx}{ diagnostics } \ + {hxx cxx}{ dump } \ + {hxx ixx cxx}{ file } \ + {hxx txx cxx}{ filesystem } \ + {hxx cxx}{ lexer } \ + {hxx cxx}{ module } \ + {hxx ixx cxx}{ name } \ + {hxx cxx}{ operation } \ + {hxx cxx}{ parser } \ + {hxx cxx}{ prerequisite } \ + {hxx cxx}{ rule } \ + {hxx }{ rule-map } \ + {hxx cxx}{ scope } \ + {hxx cxx}{ search } \ + {hxx cxx}{ spec } \ + {hxx ixx txx cxx}{ target } \ + {hxx }{ target-key } \ + {hxx }{ target-type } \ + {hxx cxx}{ token } \ + {hxx }{ types } \ + {hxx cxx}{ types-parsers } \ + {hxx ixx txx cxx}{ utility } \ + {hxx ixx txx cxx}{ variable } \ + {hxx }{ version } \ + bin/{hxx cxx}{ guess } \ + bin/{hxx cxx}{ init } \ + bin/{hxx cxx}{ rule } \ + bin/{hxx cxx}{ target } \ + c/{hxx cxx}{ init } \ + c/{hxx }{ target } \ + cc/{hxx }{ common } \ + cc/{hxx cxx}{ compile } \ + cc/{hxx cxx}{ guess } \ + cc/{hxx cxx}{ init } \ + cc/{hxx cxx}{ install } \ + cc/{hxx cxx}{ link } \ + cc/{hxx cxx}{ module } \ + cc/{ cxx}{ msvc } \ + cc/{ cxx}{ pkgconfig } \ + cc/{hxx cxx}{ target } \ + cc/{hxx }{ types } \ + cc/{hxx ixx cxx}{ utility } \ + cc/{ cxx}{ windows-manifest } \ + cc/{ cxx}{ windows-rpath } \ + cli/{hxx cxx}{ init } \ + cli/{hxx cxx}{ rule } \ + cli/{hxx cxx}{ target } \ + config/{hxx cxx}{ init } \ + config/{hxx }{ module } \ + config/{hxx cxx}{ operation } \ + config/{hxx txx cxx}{ utility } \ + cxx/{hxx cxx}{ init } \ + cxx/{hxx cxx}{ target } \ + dist/{hxx cxx}{ init } \ + dist/{hxx cxx}{ operation } \ + dist/{hxx cxx}{ rule } \ +pkgconfig/{hxx cxx}{ init } \ + install/{hxx cxx}{ init } \ + install/{hxx cxx}{ operation } \ + install/{hxx cxx}{ rule } \ + install/{hxx }{ utility } \ + test/{hxx cxx}{ init } \ + test/{hxx cxx}{ operation } \ + test/{hxx cxx}{ rule } \ + $libs # Pass our compiler target to be used as build2 host. # diff --git a/build2/c/init.cxx b/build2/c/init.cxx index be72655..c846ae1 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -228,6 +228,8 @@ namespace build2 cast<string> (r[cm.x_target_system]), cast<string> (r[cm.x_target_class]), + cast_null<process_path> (r["pkgconfig.path"]), + c::static_type, hdr, inc diff --git a/build2/cc/common b/build2/cc/common index ff6841f..c25683c 100644 --- a/build2/cc/common +++ b/build2/cc/common @@ -96,6 +96,8 @@ namespace build2 const string& tsys; // x.target.system const string& tclass; // x.target.class + const process_path* pkgconfig; // pkgconfig.path (can be NULL). + const target_type& x_src; // Source target type (c{}, cxx{}). // Array of target types that are considered headers. Keep them in the @@ -131,6 +133,7 @@ namespace build2 const string& tg, const string& sys, const string& class_, + const process_path* pkgc, const target_type& src, const target_type* const* hdr, const target_type* const* inc) @@ -140,6 +143,7 @@ namespace build2 x_install (install), x_uninstall (uninstall), cid (id), ctg (tg), tsys (sys), tclass (class_), + pkgconfig (pkgc), x_src (src), x_hdr (hdr), x_inc (inc) {} }; diff --git a/build2/cc/compile b/build2/cc/compile index 2f1a0eb..5bff6b1 100644 --- a/build2/cc/compile +++ b/build2/cc/compile @@ -41,6 +41,12 @@ namespace build2 perform_clean (action, target&) const; private: + void + append_lib_options (cstrings&, target&) const; + + void + hash_lib_options (sha256&, target&) const; + // Mapping of include prefixes (e.g., foo in <foo/bar>) for auto- // generated headers to directories where they will be generated. // @@ -57,7 +63,7 @@ namespace build2 append_prefixes (prefix_map&, target&, const variable&) const; void - append_lib_prefixes (prefix_map&, target&, lorder) const; + append_lib_prefixes (prefix_map&, target&) const; prefix_map build_prefix_map (target&, lorder) const; diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index 56122e8..fdd4231 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -62,6 +62,49 @@ namespace build2 return nullptr; } + // Append or hash library options from a pair of *.export.* variables + // (first one is cc.export.*) recursively, prerequisite libraries first. + // + void compile:: + append_lib_options (cstrings& args, target& xt) const + { + file& l (static_cast<file&> (xt)); + + auto opt = [&args, this] (file& l, const string& t, bool com, bool exp) + { + assert (exp); + + const variable& var ( + com + ? c_export_poptions + : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); + + append_options (args, l, var); + }; + + link_.process_libraries (l, l.is_a<liba> (), true, nullptr, opt); + } + + void compile:: + hash_lib_options (sha256& cs, target& xt) const + { + file& l (static_cast<file&> (xt)); + + auto opt = [&cs, this] (file& l, const string& t, bool com, bool exp) + { + assert (exp); + + const variable& var ( + com + ? c_export_poptions + : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); + + hash_options (cs, l, var); + }; + + link_.process_libraries (l, l.is_a<liba> (), true, nullptr, opt); + } + recipe compile:: apply (action a, target& xt, const match_result& mr) const { @@ -131,14 +174,18 @@ namespace build2 // optional<dir_paths> lib_paths; // Extract lazily. + lorder lo; + if (a.operation () == update_id) + lo = link_order (bs, ct); + for (prerequisite_member p: group_prerequisite_members (a, t)) { // A dependency on a library is there so that we can get its // *.export.poptions. In particular, making sure it is executed before // us will only restrict parallelism. But we do need to pre-match it - // in order to get its prerequisite_targets populated. This is the - // "library meta-information protocol". See also append_lib_options() - // above. + // in order to get its imports resolved and prerequisite_targets + // populated. This is the "library meta-information protocol". See + // also append_lib_options(). // if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libs> ()) { @@ -149,7 +196,8 @@ namespace build2 // any, they would be set by search_library()). // if (p.proj () == nullptr || - link_.search_library (lib_paths, p.prerequisite) == nullptr) + link_.search_library ( + lib_paths, p.prerequisite, lo) == nullptr) { match_only (a, p.search ()); } @@ -216,18 +264,16 @@ namespace build2 // Hash *.export.poptions from prerequisite libraries. // - lorder lo (link_order (bs, ct)); for (prerequisite& p: group_prerequisites (t)) { target* pt (p.target); // Already searched and matched. if (lib* l = pt->is_a<lib> ()) pt = &link_member (*l, lo); + else if (!pt->is_a<liba> () && !pt->is_a<libs> ()) + continue; - if (pt->is_a<liba> () || pt->is_a<libs> ()) - hash_lib_options (cs, *pt, lo, - c_export_poptions, - x_export_poptions); + hash_lib_options (cs, *pt); } hash_options (cs, t, c_poptions); @@ -399,22 +445,23 @@ namespace build2 // recursively, prerequisite libraries first. // void compile:: - append_lib_prefixes (prefix_map& m, target& l, lorder lo) const + append_lib_prefixes (prefix_map& m, target& xt) const { - for (target* t: l.prerequisite_targets) + file& l (static_cast<file&> (xt)); + + auto opt = [&m, this] (file& l, const string& t, bool com, bool exp) { - if (t == nullptr) - continue; + assert (exp); - if (lib* l = t->is_a<lib> ()) - t = &link_member (*l, lo); // Pick one of the members. + const variable& var ( + com + ? c_export_poptions + : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); - if (t->is_a<liba> () || t->is_a<libs> ()) - append_lib_prefixes (m, *t, lo); - } + append_prefixes (m, l, var); + }; - append_prefixes (m, l, c_export_poptions); - append_prefixes (m, l, x_export_poptions); + link_.process_libraries (l, l.is_a<liba> (), true, nullptr, opt); } auto compile:: @@ -430,10 +477,11 @@ namespace build2 target* pt (p.target); // Already searched and matched. if (lib* l = pt->is_a<lib> ()) - pt = &link_member (*l, lo); // Pick one of the members. + pt = &link_member (*l, lo); + else if (!pt->is_a<liba> () && !pt->is_a<libs> ()) + continue; - if (pt->is_a<liba> () || pt->is_a<libs> ()) - append_lib_prefixes (m, *pt, lo); + append_lib_prefixes (m, *pt); } // Then process our own. @@ -657,11 +705,10 @@ namespace build2 if (lib* l = pt->is_a<lib> ()) pt = &link_member (*l, lo); + else if (!pt->is_a<liba> () && !pt->is_a<libs> ()) + continue; - if (pt->is_a<liba> () || pt->is_a<libs> ()) - append_lib_options (args, *pt, lo, - c_export_poptions, - x_export_poptions); + append_lib_options (args, *pt); } append_options (args, t, c_poptions); @@ -1330,11 +1377,10 @@ namespace build2 if (lib* l = pt->is_a<lib> ()) pt = &link_member (*l, lo); + else if (!pt->is_a<liba> () && !pt->is_a<libs> ()) + continue; - if (pt->is_a<liba> () || pt->is_a<libs> ()) - append_lib_options (args, *pt, lo, - c_export_poptions, - x_export_poptions); + append_lib_options (args, *pt); } append_options (args, t, c_poptions); diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index 0f1fda7..066367d 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -65,6 +65,9 @@ namespace build2 // "c", "cxx"). Currenly only set for libraries and is used to decide // which *.libs to use during static linking. // + // It can also be the special "cc" value which means a C-common library + // but specific language is not known. Used in import installed logic. + // v.insert<string> ("cc.type"); return true; @@ -228,6 +231,23 @@ namespace build2 load_module ("bin.rc.config", r, b, loc); } + // Load (optionally) the pkgconfig.config module. + // + // @@ 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 (first && !cast_false<bool> (b["pkgconfig.config.loaded"])) + { + // Prepare configuration hints. + // + variable_map h; + h.assign ("config.pkgconfig.target") = cast<string> (r["cc.target"]); + + load_module ("pkgconfig.config", r, r, loc, true, h); + } + return true; } diff --git a/build2/cc/link b/build2/cc/link index aaf1cd7..474c299 100644 --- a/build2/cc/link +++ b/build2/cc/link @@ -40,6 +40,15 @@ namespace build2 friend class compile; void + process_libraries (file&, + bool, + bool, + const function<void (const path&)>&, + const function<void (file&, + const string&, + bool, + bool)>&) const; + void append_libraries (strings&, file&, bool) const; void @@ -62,6 +71,15 @@ namespace build2 dir_paths extract_library_paths (scope&) const; + bool + pkgconfig_extract (scope&, + file&, + const string*, + const string&, + const dir_path&, + optional<dir_paths>&, + lorder) const; + // Alternative search logic for VC (msvc.cxx). // bin::liba* @@ -75,10 +93,12 @@ namespace build2 const prerequisite_key&) const; target* - search_library (optional<dir_paths>& spc, prerequisite& p) const + search_library (optional<dir_paths>& spc, + prerequisite& p, + lorder lo) const { if (p.target == nullptr) // First check the cache. - p.target = search_library (spc, p.key ()); + p.target = search_library (spc, p.key (), lo); return p.target; } @@ -86,7 +106,9 @@ namespace build2 // Note that pk's scope should not be NULL (even if dir is absolute). // target* - search_library (optional<dir_paths>&, const prerequisite_key&) const; + search_library (optional<dir_paths>&, + const prerequisite_key&, + lorder) const; // Windows-specific (windows-manifest.cxx). // diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 7e639ed..a4fa1dd 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -202,7 +202,9 @@ namespace build2 } target* link:: - search_library (optional<dir_paths>& spc, const prerequisite_key& p) const + search_library (optional<dir_paths>& spc, + const prerequisite_key& p, + lorder lo) const { tracer trace (x, "link::search_library"); @@ -437,7 +439,8 @@ namespace build2 // Add the "using static/shared library" macro (used, for example, to // handle DLL export). The absence of either of these macros would mean - // some other build system that cannot distinguish between the two. + // some other build system that cannot distinguish between the two (and + // no pkg-config information). // auto add_macro = [this] (target& t, const char* suffix) { @@ -446,6 +449,11 @@ namespace build2 // messing with custom values. And if we are adding, then use the // generic cc.export. // + // The only way we could already have this value is if this same + // library was also imported as a project (as opposed to installed). + // Unlikely but possible. In this case the values were set by the + // export stub and we shouldn't touch them. + // if (!t.vars[x_export_poptions]) { auto p (t.vars.insert (c_export_poptions)); @@ -478,11 +486,38 @@ namespace build2 } }; - if (a != nullptr) - add_macro (*a, "STATIC"); + // Mark as a "cc" library unless already marked. + // + auto mark_cc = [this] (target& t) -> bool + { + auto p (t.vars.insert (c_type)); + + if (p.second) + p.first.get () = string ("cc"); + + return p.second; + }; + + // If the library already has cc.type, then assume it was either already + // imported or was matched by a rule. + // + if (a != nullptr && mark_cc (*a)) + { + // Only add the default macro if we could not extract more precise + // 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 (*p.scope, *a, p.proj, name, *pd, spc, lo)) + add_macro (*a, "STATIC"); + } - if (s != nullptr) - add_macro (*s, "SHARED"); + if (s != nullptr && mark_cc (*s)) + { + if (pkgconfig == nullptr || + !pkgconfig_extract (*p.scope, *s, p.proj, name, *pd, spc, lo)) + add_macro (*s, "SHARED"); + } if (l) { @@ -606,6 +641,8 @@ namespace build2 if (t.group != nullptr) t.group->prerequisite_targets.clear (); // lib{}'s + scope& bs (t.base_scope ()); + lorder lo (link_order (bs, lt)); optional<dir_paths> lib_paths; // Extract lazily. for (prerequisite_member p: group_prerequisite_members (a, t)) @@ -617,7 +654,7 @@ namespace build2 // Handle imported libraries. // if (p.proj () != nullptr) - pt = search_library (lib_paths, p.prerequisite); + pt = search_library (lib_paths, p.prerequisite, lo); if (pt == nullptr) { @@ -810,7 +847,7 @@ namespace build2 // Handle imported libraries. // if (p.proj () != nullptr) - pt = search_library (lib_paths, p.prerequisite); + pt = search_library (lib_paths, p.prerequisite, lo); // The rest is the same basic logic as in search_and_match(). // @@ -1023,9 +1060,12 @@ namespace build2 } } - // Recursively append/hash prerequisite libraries. Only interface + // Recursively process prerequisite libraries. Only interface // (*.export.libs) for shared libraries, interface and implementation - // (both prerequisite and from *.libs) for static libraries. + // (both prerequisite and from *.libs, unless overriden) for static + // libraries (unless iface_only is true, in which case we use + // *.export.libs even for static libraries which means *.export.libs + // should be set on lib{}, not libs{}). // // Note that here we assume that an interface library is also an // implementation (since we don't use *.export.libs in static link). We @@ -1034,72 +1074,111 @@ namespace build2 // listed as a prerequisite of this library). // void link:: - append_libraries (strings& args, file& l, bool la) const + process_libraries ( + file& l, + bool la, + bool iface_only, + const function<void (const path&)>& proc_lib, + const function<void (file&, + const string& type, + bool com, /* cc. */ + bool exp) /* export. */>& proc_opt) const { - for (target* p: l.prerequisite_targets) + // See what type of library this is (C, C++, etc). Use it do decide + // which x.libs variable name to use. If it's unknown, then we only + // look into prerequisites. + // + const string* t (cast_null<string> (l.vars[c_type])); + + lookup c_e_libs; + lookup x_e_libs; + + if (t != nullptr) { - bool a; - file* f; + // If static, then the explicit export override should be set on the + // liba{} target itself. Note also that we only check for *.libs. If + // one doesn't have any libraries but needs to set, say, *.loptions, + // then *.libs should be set to NULL or empty (this is why we check + // for result being defined). + // + // @@ Should we set it in import installed then? + // + c_e_libs = la && !iface_only + ? l.vars[c_export_libs] + : l[c_export_libs]; - if ((a = (f = p->is_a<liba> ()) != nullptr) - || (f = p->is_a<libs> ()) != nullptr) + if (*t != "cc") { - if (la) - args.push_back (relative (f->path ()).string ()); // string()&& + const variable& var (*t == x + ? x_export_libs + : var_pool[*t + ".export.libs"]); - append_libraries (args, *f, a); + x_e_libs = la && !iface_only ? l.vars[var] : l[var]; } } - if (la) + // Only go into prerequisites (implementation dependencies) if this is a + // static library and it's not using explicit export. For shared library + // or if iface_only, we are only interested in interface dependencies + // which come from the *.export.libs below. + // + if (la && !iface_only && !c_e_libs.defined () && !x_e_libs.defined ()) { - // See what type of library this is (C, C++, etc). Use it do decide - // which x.libs variable name. If it is not a C-common library, then - // it probably doesn't have cc.libs either. - // - if (const string* t = cast_null<string> (l.vars[c_type])) + for (target* p: l.prerequisite_targets) { - // @@ Shouldn't, ideally, we filter loptions to only include -L? - // - append_options (args, l, c_loptions); - append_options (args, l, c_libs); - - append_options (args, - l, - *t == x ? x_loptions : var_pool[*t + ".loptions"]); - append_options (args, - l, - *t == x ? x_libs : var_pool[*t + ".libs"]); + bool a; + file* f; + + if ((a = (f = p->is_a<liba> ()) != nullptr) + || (f = p->is_a<libs> ()) != nullptr) + { + if (proc_lib) + proc_lib (f->path ()); + + process_libraries (*f, a, iface_only, proc_lib, proc_opt); + } } } - else - { - scope* bs (nullptr); // Resolve lazily. - optional<lorder> lo; // Calculate lazily. - optional<dir_paths> spc; // Extract lazily. - auto append = [&args, &l, &bs, &lo, &spc, this] (const variable& var) - { - const names* ns (cast_null<names> (l[var])); - if (ns == nullptr || ns->empty ()) - return; + // Process libraries (recursively) from *.export.libs (of type names) + // handling import, etc. + // + scope* bs (nullptr); // Resolve lazily. + optional<lorder> lo; // Calculate lazily. + optional<dir_paths> spc; // Extract lazily. - args.reserve (args.size () + ns->size ()); + auto proc_int = + [&l, la, iface_only, &proc_lib, &proc_opt, &bs, &lo, &spc, this] ( + const lookup& lu) + { + const names* ns (cast_null<names> (lu)); + if (ns == nullptr || ns->empty ()) + return; - for (const name& n: *ns) + for (const name& n: *ns) + { + if (n.simple ()) { - if (n.simple ()) - args.push_back (n.value); - else - { - if (bs == nullptr) - bs = &l.base_scope (); + // This is something like -lpthread or shell32.lib so should be + // a valid path. + // + if (proc_lib) + proc_lib (path (n.value)); + } + else + { + // This is a potentially project-qualified target. + // + if (bs == nullptr) + bs = &l.base_scope (); - if (!lo) - lo = link_order (*bs, otype::s); // We know it's libs{}. + if (!lo) + lo = link_order (*bs, la ? otype::a : otype::s); - file& t (resolve_library (n, *bs, *lo, spc)); + file& t (resolve_library (n, *bs, *lo, spc)); + if (proc_lib) + { // This can happen if the target is mentioned in *.export.libs // (i.e., it is an interface dependency) but not in the // library's prerequisites (i.e., it is not an implementation @@ -1107,116 +1186,148 @@ namespace build2 // if (t.path ().empty ()) fail << "target " << t << " is out of date" << - info << "mentioned in " << var.name << " of target " << l << + info << "mentioned in *.export.libs of target " << l << info << "is it a prerequisite of " << l << "?"; - args.push_back (relative (t.path ()).string ()); + proc_lib (t.path ()); } - } - }; - - // @@ Should we also pick one based on cc.type? And also *.poptions in - // compile? Feels right. - // - append_options (args, l, c_export_loptions); - append (c_export_libs); - append_options (args, l, x_export_loptions); - append (x_export_libs); - } - } + // Process it recursively. + // + process_libraries ( + t, t.is_a<liba> (), iface_only, proc_lib, proc_opt); + } + } + }; - void link:: - hash_libraries (sha256& cs, file& l, bool la) const - { - for (target* p: l.prerequisite_targets) + // Process libraries from *.libs (of type strings). + // + auto proc_imp = [&proc_lib] (const lookup& lu) { - bool a; - file* f; + const strings* ns (cast_null<strings> (lu)); + if (ns == nullptr || ns->empty ()) + return; - if ((a = (f = p->is_a<liba> ()) != nullptr) - || (f = p->is_a<libs> ()) != nullptr) + for (const string& n: *ns) { - if (la) - cs.append (f->path ().string ()); - - hash_libraries (cs, *f, a); + // This is something like -lpthread or shell32.lib so should be a + // valid path. + // + proc_lib (path (n)); } - } + }; + + // If it is not a C-common library, then it probably doesn't have any of + // the *.libs and we are done. + // + if (t == nullptr) + return; - if (la) + // If all we know it's a C-common library, then in both cases we only + // look for cc.export.libs. + // + if (*t == "cc") { - // See what type of library this is (C, C++, etc). Use it do decide - // which x.libs variable name. If it is not a C-common library, then - // it probably doesn't have cc.libs either. - // - if (const string* t = cast_null<string> (l.vars[c_type])) - { - // @@ Shouldn't, ideally, we filter loptions to only include -L? - // - hash_options (cs, l, c_loptions); - hash_options (cs, l, c_libs); - - hash_options (cs, - l, - *t == x ? x_loptions : var_pool[*t + ".loptions"]); - hash_options (cs, - l, - *t == x ? x_libs : var_pool[*t + ".libs"]); - } + proc_opt (l, *t, true, true); + if (c_e_libs) proc_int (c_e_libs); } else { - scope* bs (nullptr); // Resolve lazily. - optional<lorder> lo; // Calculate lazily. - optional<dir_paths> spc; // Extract lazily. + bool same (*t == x); // Same as us. + auto& vp (var_pool); - auto hash = [&cs, &l, &bs, &lo, &spc, this] (const variable& var) + if (la && !iface_only) { - const names* ns (cast_null<names> (l[var])); - if (ns == nullptr || ns->empty ()) - return; + // Static: as discussed above, here we can have two situations: + // explicit export or default export. + // + if (c_e_libs.defined () || x_e_libs.defined ()) + { + // NOTE: should this not be from l.vars rather than l? Or perhaps + // we can assume non-common values will be set on libs{}/liba{}. + // + proc_opt (l, *t, true, true); + if (c_e_libs) proc_int (c_e_libs); - for (const name& n: *ns) + proc_opt (l, *t, false, true); + if (x_e_libs) proc_int (x_e_libs); + } + else { - if (n.simple ()) - cs.append (n.value); - else - { - if (bs == nullptr) - bs = &l.base_scope (); + // For default export we use the same options/libs as were used to + // build the library. Since libraries in (non-export) *.libs are + // not targets, we don't need to recurse. + // + proc_opt (l, *t, true, false); + if (proc_lib) proc_imp (l[c_libs]); + + proc_opt (l, *t, false, false); + if (proc_lib) proc_imp (l[same ? x_libs : vp[*t + ".libs"]]); + } + } + else + { + // Shared or iface_only: only add *.export.* (interface + // dependencies). + // + proc_opt (l, *t, true, true); + if (c_e_libs) proc_int (c_e_libs); + + proc_opt (l, *t, false, true); + if (x_e_libs) proc_int (x_e_libs); + } + } + } - if (!lo) - lo = link_order (*bs, otype::s); // We know it's libs{}. + void link:: + append_libraries (strings& args, file& l, bool la) const + { + auto lib = [&args] (const path& l) + { + args.push_back (relative (l).string ()); + }; - file& t (resolve_library (n, *bs, *lo, spc)); + auto opt = [&args, this] (file& l, const string& t, bool com, bool exp) + { + 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, l, var); + }; - // This can happen if the target is mentioned in *.export.libs - // (i.e., it is an interface dependency) but not in the - // library's prerequisites (i.e., it is not an implementation - // dependency). - // - if (t.path ().empty ()) - fail << "target " << t << " is out of date" << - info << "mentioned in " << var.name << " of target " << l << - info << "is it a prerequisite of " << l << "?"; + process_libraries (l, la, false, lib, opt); + } - cs.append (t.path ().string ()); - } - } - }; + void link:: + hash_libraries (sha256& cs, file& l, bool la) const + { + auto lib = [&cs] (const path& l) + { + cs.append (l.string ()); + }; - hash_options (cs, l, c_export_loptions); - hash (c_export_libs); + auto opt = [&cs, this] (file& l, const string& t, bool com, bool exp) + { + 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")])); + + hash_options (cs, l, var); + }; - hash_options (cs, l, x_export_loptions); - hash (x_export_libs); - } + process_libraries (l, la, false, lib, opt); } - // The name can be a simple value (e.g., -lpthread or shell32.lib), an - // absolute target name (e.g., /tmp/libfoo/lib{foo}) or a potentially - // project-qualified relative target name (e.g., libfoo%lib{foo}). + // The name can be an absolute target name (e.g., /tmp/libfoo/lib{foo}) or + // a potentially project-qualified relative target name (e.g., + // libfoo%lib{foo}). // // Note that the scope, search paths, and the link order should all be // derived from the library target that mentioned this name. This way we @@ -1257,7 +1368,7 @@ namespace build2 dir_path out; prerequisite_key pk {n.proj, {tt, &n.dir, &out, &n.value, ext}, &s}; - xt = search_library (spc, pk); + xt = search_library (spc, pk, lo); if (xt == nullptr) { diff --git a/build2/cc/msvc.cxx b/build2/cc/msvc.cxx index d088c5b..216bbf2 100644 --- a/build2/cc/msvc.cxx +++ b/build2/cc/msvc.cxx @@ -14,7 +14,6 @@ #include <build2/bin/target> #include <build2/cc/types> - #include <build2/cc/link> using namespace std; diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx new file mode 100644 index 0000000..2122ea5 --- /dev/null +++ b/build2/cc/pkgconfig.cxx @@ -0,0 +1,446 @@ +// file : build2/cc/msvc.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build2/scope> +#include <build2/target> +#include <build2/context> +#include <build2/variable> +#include <build2/filesystem> +#include <build2/diagnostics> + +#include <build2/bin/target> + +#include <build2/cc/link> +#include <build2/cc/types> +#include <build2/cc/utility> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + using namespace bin; + + // 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.* + // variables on lib, and return true. + // + // System library search paths (those extracted from the compiler) are + // passed in sys_sp and should already be extracted. + // + // Note that scope and link order should be "top-level" from the + // search_library() POV. + // + bool link:: + pkgconfig_extract (scope& s, + file& lt, + const string* proj, + const string& stem, + const dir_path& libd, + optional<dir_paths>& sys_sp, + lorder lo) const + { + tracer trace (x, "link::pkgconfig_extract"); + + assert (sys_sp); + assert (pkgconfig != nullptr); + + // Check if we have the pkgconfig/ subdirectory in this library's + // directory. + // + dir_path pkgd (dir_path (libd) /= "pkgconfig"); + + if (!dir_exists (pkgd)) + return false; + + // Now 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 + // suggests that some of you should call yours foolib.pc, just to keep + // things interesting, you know). + // + // Given the (general) import in the form <proj>%lib{<stem>}, we will + // first try <stem>.pc, then lib<stem>.pc. Maybe it also makes sense to + // try <proj>.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. + // + path f; + f = pkgd; + f /= stem; + f += ".pc"; + + if (!file_exists (f)) + { + f = pkgd; + f /= "lib"; + f += stem; + f += ".pc"; + + if (!file_exists (f)) + { + if (proj != nullptr) + { + f = pkgd; + f /= *proj; + f += ".pc"; + + if (!file_exists (f)) + return false; + } + else + return false; + } + } + + // Ok, we are in business. Time to run pkg-config. To keep things + // simple, we run it twice, first time for --cflag, then for --libs. + // + bool la (lt.is_a<liba> ()); + + const char* args[] = { + pkgconfig->initial, + nullptr, // --cflags/--libs + (la ? "--static" : f.string ().c_str ()), + (la ? f.string ().c_str () : nullptr), + nullptr + }; + + auto retline = [] (string& s) -> string {return move (s);}; + + args[1] = "--cflags"; + string cstr (run<string> (*pkgconfig, args, retline)); + + args[1] = "--libs"; + string lstr (run<string> (*pkgconfig, args, retline)); + + // 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(). + // + auto next = [] (const string& s, size_t& b, size_t& e) -> string + { + string r; + size_t n (s.size ()); + + if (b != e) + b = e; + + // Skip leading delimiters. + // + for (; b != n && s[b] == ' '; ++b) ; + + if (b == n) + { + e = n; + return r; + } + + // Find first trailing delimiter while taking care of escapes. + // + r = s[b]; + for (e = b + 1; e != n && s[e] != ' '; ++e) + { + if (s[e] == '\\') + { + if (++e == n) + fail << "dangling escape in pkg-config output '" << s << "'"; + } + + r += s[e]; + } + + return r; + }; + + // Parse --cflags into poptions. + // + { + strings pops; + + bool arg (false); + string o; + for (size_t b (0), e (0); !(o = next (cstr, b, e)).empty (); ) + { + if (arg) + { + pops.push_back (move (o)); + arg = false; + continue; + } + + size_t n (o.size ()); + + // We only keep -I, -D and -U. + // + if (n >= 2 && + o[0] == '-' && + (o[1] == 'I' || o[1] == 'D' || o[1] == 'U')) + { + pops.push_back (move (o)); + arg = (n == 2); + continue; + } + + l4 ([&]{trace << "ignoring " << f << " --cflags option " << o;}); + } + + if (arg) + fail << "argument expected after " << pops.back () << + info << "while parsing pkg-config --cflags output of " << f; + + if (!pops.empty ()) + { + auto p (lt.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). + // Unlikely but possible. In this case the values were set by the + // export stub and we shouldn't touch them. + // + if (p.second) + p.first.get () = move (pops); + } + } + + // Parse --libs into loptions/libs. + // + { + strings lops; + names libs; + + // Normally we will have zero or more -L's followed by one or more + // -l's, with the first one being the library itself. But sometimes + // we may have other linker options, for example, -Wl,... or + // -pthread. It's probably a bad idea to ignore them. Also, + // theoretically, we could have just the library name/path. + // + // The tricky part, of course, is to know whether what follows after + // an option we don't recognize is its argument or another option or + // 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 (); ) + { + if (arg) + { + // Can only be an argument for an loption. + // + lops.push_back (move (o)); + arg = false; + continue; + } + + size_t n (o.size ()); + + // See if this is -L. + // + if (n >= 2 && o[0] == '-' && o[1] == 'L') + { + have_L = true; + lops.push_back (move (o)); + arg = (n == 2); + continue; + } + + // See if that's -l or just the library name/path. + // + if ((known && o[0] != '-') || + (n > 2 && o[0] == '-' && o[1] == 'l')) + { + // First one is the library itself, which we skip. Note that we + // don't verify this and theoretically it could be some other + // library, but we haven't encountered such a beast yet. + // + if (first) + { + first = false; + continue; + } + + libs.push_back (name (move (o), false)); + continue; + } + + // Otherwise we assume it is some other loption. + // + known = false; + lops.push_back (move (o)); + } + + if (arg) + fail << "argument expected after " << lops.back () << + info << "while parsing pkg-config --libs output of " << f; + + if (first) + fail << "library expected in '" << lstr << "'" << + info << "while parsing pkg-config --libs output of " << f; + + // Resolve -lfoo into the library file path using our import installed + // machinery (i.e., we are going to call search_library() that will + // probably call us again, and so on). + // + // The reason we do it is the link order. For general libraries it + // shouldn't matter if we imported them via an export stub, direct + // import installed, or via a .pc file (which we could have generated + // from the export stub). The exception is "system libraries" (which + // are really the extension of libc) such as -lm, -ldl, -lpthread, + // etc. Those we will detect and leave as -l*. + // + // If we managed to resolve all the -l's (sans system), then we can + // omit -L's for nice and tidy command line. + // + bool all (true); + optional<dir_paths> sp; // Populate lazily. + + for (name& n: libs) + { + string& l (n.value); + + // These ones are common/standard/POSIX. + // + if (l[0] != '-' || // e.g., shell32.lib + l == "-lm" || + l == "-ldl" || + l == "-lrt" || + l == "-lpthread") + continue; + + // Note: these list are most likely incomplete. + // + if (tclass == "linux") + { + // Some extras from libc (see libc6-dev) and other places. + // + if (l == "-lanl" || + l == "-lcrypt" || + l == "-lnsl" || + l == "-lresolv" || + l == "-lgcc") + continue; + } + else if (tclass == "macosx") + { + if (l == "-lSystem") + continue; + } + + // Prepare the search paths. + // + if (have_L && !sp) + { + sp = dir_paths (); + + // First enter the -L paths from the .pc file so that they take + // precedence. + // + for (auto i (lops.begin ()); i != lops.end (); ++i) + { + const string& o (*i); + + if (o.size () >= 2 && o[0] == '-' && o[1] == 'L') + { + string p; + + if (o.size () == 2) + p = *++i; // We've verified it's there. + else + p = string (o, 2); + + dir_path d (move (p)); + + if (d.relative ()) + fail << "relative -L directory in '" << lstr << "'" << + info << "while parsing pkg-config --libs output of " << f; + + sp->push_back (move (d)); + } + } + + // Then append system paths. + // + sp->insert (sp->end (), sys_sp->begin (), sys_sp->end ()); + } + + // @@ OUT: for now we assume out is undetermined, just like in + // link::resolve_library(). + // + dir_path out; + string name (l, 2); // Sans -l. + const string* ext (nullptr); + + prerequisite_key pk { + nullptr, {&lib::static_type, &out, &out, &name, ext}, &s}; + + if (lib* lt = static_cast<lib*> ( + search_library (have_L ? sp : sys_sp, pk, lo))) + { + file& f (static_cast<file&> (link_member (*lt, lo))); + l = f.path ().string (); + } + else + // If we couldn't find the library, then leave it as -l. + // + 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 && known) + lops.clear (); + + if (!lops.empty ()) + { + if (cid == "msvc") + { + // Translate -L to /LIBPATH. + // + for (auto i (lops.begin ()); i != lops.end (); ) + { + string& o (*i); + size_t n (o.size ()); + + if (n >= 2 && o[0] == '-' && o[1] == 'L') + { + o.replace (0, 2, "/LIBPATH:"); + + if (n == 2) + { + o += *++i; // We've verified it's there. + i = lops.erase (i); + continue; + } + } + + ++i; + } + } + + auto p (lt.vars.insert (c_export_loptions)); + + if (p.second) + p.first.get () = move (lops); + } + + if (!libs.empty ()) + { + auto p (lt.vars.insert (c_export_libs)); + + if (p.second) + p.first.get () = move (libs); + } + } + + return true; + } + } +} diff --git a/build2/cc/utility b/build2/cc/utility index ae19d56..1ef65d8 100644 --- a/build2/cc/utility +++ b/build2/cc/utility @@ -43,19 +43,6 @@ namespace build2 // target& link_member (bin::lib&, lorder); - - // Append or hash library options from a pair of *.export.* variables - // (first one is cc.export.*) recursively, prerequisite libraries first. - // - void - append_lib_options (cstrings&, target&, lorder, - const variable&, - const variable&); - - void - hash_lib_options (sha256&, target&, lorder, - const variable&, - const variable&); } } diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 773ba8f..7be86e2 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -71,45 +71,5 @@ namespace build2 return *r; } - - void - append_lib_options (cstrings& args, target& l, lorder lo, - const variable& cv, - const variable& xv) - { - using namespace bin; - - for (target* t: l.prerequisite_targets) - { - if (lib* l = t->is_a<lib> ()) - t = &link_member (*l, lo); // Pick one of the members. - - if (t->is_a<liba> () || t->is_a<libs> ()) - append_lib_options (args, *t, lo, cv, xv); - } - - append_options (args, l, cv); - append_options (args, l, xv); - } - - void - hash_lib_options (sha256& csum, target& l, lorder lo, - const variable& cv, - const variable& xv) - { - using namespace bin; - - for (target* t: l.prerequisite_targets) - { - if (lib* l = t->is_a<lib> ()) - t = &link_member (*l, lo); // Pick one of the members. - - if (t->is_a<liba> () || t->is_a<libs> ()) - hash_lib_options (csum, *t, lo, cv, xv); - } - - hash_options (csum, l, cv); - hash_options (csum, l, xv); - } } } diff --git a/build2/context.txx b/build2/context.txx index 6ff61c9..f40ecdc 100644 --- a/build2/context.txx +++ b/build2/context.txx @@ -12,7 +12,7 @@ namespace build2 const dir_path& b (*relative_base); - if (b.empty ()) + if (p.simple () || b.empty ()) return p; if (p.sub (b)) diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index 8202139..e504284 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -235,6 +235,8 @@ namespace build2 cast<string> (r[cm.x_target_system]), cast<string> (r[cm.x_target_class]), + cast_null<process_path> (r["pkgconfig.path"]), + cxx::static_type, hdr, inc diff --git a/build2/pkgconfig/init b/build2/pkgconfig/init new file mode 100644 index 0000000..53821ae --- /dev/null +++ b/build2/pkgconfig/init @@ -0,0 +1,37 @@ +// file : build2/pkgconfig/init -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PKGCONFIG_INIT +#define BUILD2_PKGCONFIG_INIT + +#include <build2/types> +#include <build2/utility> + +#include <build2/module> + +namespace build2 +{ + namespace pkgconfig + { + bool + config_init (scope&, + scope&, + const location&, + unique_ptr<module_base>&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr<module_base>&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_PKGCONFIG_INIT diff --git a/build2/pkgconfig/init.cxx b/build2/pkgconfig/init.cxx new file mode 100644 index 0000000..b7100e3 --- /dev/null +++ b/build2/pkgconfig/init.cxx @@ -0,0 +1,159 @@ +// file : build2/pkgconfig/init.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build2/pkgconfig/init> + +#include <build2/scope> +#include <build2/target> +#include <build2/variable> +#include <build2/diagnostics> + +#include <build2/config/utility> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace pkgconfig + { + bool + config_init (scope& rs, + scope& bs, + const location& l, + unique_ptr<module_base>&, + bool, + bool optional, + const variable_map& hints) + { + tracer trace ("pkgconfig::config_init"); + l5 ([&]{trace << "for " << bs.out_path ();}); + + if (&rs != &bs) + fail (l) << "pkgconfig.config loaded for non-root scope"; + + // Enter variables. + // + // config.pkgconfig.target is a hint. + // + auto& vp (var_pool); + + const variable& c_x (vp.insert<path> ("config.pkgconfig", true)); + const variable& c_x_tgt (vp.insert<string> ("config.pkgconfig.target")); + const variable& x_path (vp.insert<process_path> ("pkgconfig.path")); + + // Configure. + // + + // Adjust module priority (between compilers and binutils). + // + config::save_module (rs, "pkgconfig", 325); + + process_path pp; + bool new_val (false); // Set any new values? + + auto p (config::omitted (rs, c_x)); + + if (const value* v = p.first) + { + const path& x (cast<path> (*v)); + + try + { + // If this is a user-specified value, then it's non-optional. + // + pp = process::path_search (x, true); + new_val = p.second; + } + catch (const process_error& e) + { + fail << "unable to execute " << x << ": " << e.what (); + } + } + + string d; // Default name (pp.initial may be its shallow copy). + + // If we have a target hint, then next try <triplet>-pkg-config. + // + if (pp.empty ()) + { + if (const string* t = cast_null<string> (hints[c_x_tgt])) + { + d = *t; + d += "-pkg-config"; + + l5 ([&]{trace << "trying " << d;}); + pp = process::try_path_search (d, true); + } + } + + // Finallly, try just pkg-config. + // + if (pp.empty ()) + { + d = "pkg-config"; + + l5 ([&]{trace << "trying " << d;}); + pp = process::try_path_search (d, true); + } + + bool conf (!pp.empty ()); + + if (!conf && !optional) + fail (l) << "unable to find pkg-config program"; + + // Config report. + // + if (verb >= (new_val ? 2 : 3)) + { + diag_record dr (text); + dr << "pkgconfig " << project (rs) << '@' << rs.out_path () << '\n'; + + if (conf) + dr << " pkg-config " << pp; + else + dr << " pkg-config " << "not found, leaving unconfigured"; + } + + if (conf) + rs.assign (x_path) = move (pp); + + return conf; + } + + bool + init (scope& rs, + scope& bs, + const location& loc, + unique_ptr<module_base>&, + bool, + bool optional, + const variable_map& hints) + { + tracer trace ("pkgconfig::init"); + l5 ([&]{trace << "for " << bs.out_path ();}); + + // Load pkgconfig.config. + // + if (!cast_false<bool> (rs["pkgconfig.config.loaded"])) + { + if (!load_module ("pkgconfig.config", rs, rs, loc, optional, hints)) + return false; + } + else if (!cast_false<bool> (rs["pkgconfig.config.configured"])) + { + if (!optional) + fail << "pkgconfig module could not be configured" << + info << "re-run with -V option for more information"; + + return false; + } + + // For now pkgconfig and pkgconfig.config is pretty much the same. + // + + return true; + } + } +} diff --git a/build2/utility b/build2/utility index 3ccca61..7f05dd0 100644 --- a/build2/utility +++ b/build2/utility @@ -80,7 +80,7 @@ namespace build2 // Start a process with the specified arguments printing the command at // verbosity level 3 and higher. Redirect STDOUT to a pipe. If error is - // false, then redirecting STDERR to STDOUT (this can used to suppress + // false, then redirecting STDERR to STDOUT (this can be used to suppress // diagnostics from the child process). Issue diagnostics and throw failed // in case of an error. // diff --git a/build2/utility.cxx b/build2/utility.cxx index ef90084..db0d6b2 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -31,10 +31,15 @@ namespace build2 ostream& operator<< (ostream& os, const process_path& p) { - os << p.recall_string (); + if (p.empty ()) + os << "<empty>"; + else + { + os << p.recall_string (); - if (!p.effect.empty ()) - os << '@' << p.effect.string (); // Suppress relative(). + if (!p.effect.empty ()) + os << '@' << p.effect.string (); // Suppress relative(). + } return os; } diff --git a/tests/import/installed/buildfile b/tests/import/installed/buildfile index be051b2..ae9d705 100644 --- a/tests/import/installed/buildfile +++ b/tests/import/installed/buildfile @@ -1,6 +1,8 @@ using cxx -import libs += lib{z} +import libs += zlib%lib{z} +import libs += libsqlite3%lib{sqlite3} +import libs += libpq%lib{pq} #lib{driver}: cxx{driver} $libs |