From 984a0217af64c734938c330b09ed21d1253dea8d Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 12 Sep 2017 17:31:52 +0300 Subject: Make use of libpkgconf library --- build2/algorithm.cxx | 4 +- build2/algorithm.ixx | 3 +- build2/buildfile | 3 +- build2/cc/common.cxx | 2 +- build2/cc/common.hxx | 1 + build2/cc/pkgconfig.cxx | 806 +++++++++++++++++++++++++++++++---------- build2/version/snapshot.cxx | 2 +- manifest | 1 + unit-tests/function/driver.cxx | 2 +- 9 files changed, 635 insertions(+), 189 deletions(-) diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 361c80f..6976343 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -599,7 +599,7 @@ namespace build2 return make_pair (false, target_state::unknown); if (task_count == nullptr) - return match_impl (a, l, /*step*/ false, try_match); + return match_impl (a, l, false /* step */, try_match); // Pass "disassembled" lock since the scheduler queue doesn't support // task destruction. Also pass our diagnostics stack (this is safe since @@ -616,7 +616,7 @@ namespace build2 phase_lock pl (run_phase::match); { target_lock l {&t, offset}; // Reassemble. - match_impl (a, l, /*step*/ false, try_match); + match_impl (a, l, false /* step */, try_match); // Unlock withing the match phase. } }, diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx index 03728db..928386d 100644 --- a/build2/algorithm.ixx +++ b/build2/algorithm.ixx @@ -220,7 +220,8 @@ namespace build2 { assert (phase == run_phase::match); - pair r (match (a, t, 0, nullptr, /*try_match*/ true)); + pair r ( + match (a, t, 0, nullptr, true /* try_match */)); if (r.first) { diff --git a/build2/buildfile b/build2/buildfile index 2b24ec9..2bb1afa 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -2,7 +2,8 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -import libs = libbutl%lib{butl} +import libs = libbutl%lib{butl} +import libs += libpkgconf%lib{pkgconf} exe{b}: cxx{b} libu{b} diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index 5dc61b6..e06898e 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -873,7 +873,7 @@ namespace build2 !pkgconfig_load (act, *p.scope, *lt, a, s, p.proj, name, - *pd, sysd)) + *pd, sysd, *usrd)) { 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 d9aeb40..5a245ca 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -273,6 +273,7 @@ namespace build2 const optional&, const string&, const dir_path&, + const dir_paths&, const dir_paths&) const; // pkgconfig.cxx // Alternative search logic for VC (msvc.cxx). diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 0071bcd..22ab774 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -2,6 +2,13 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +// In order not to complicate the bootstrap procedure with libpkgconf building +// exclude functionality that involves reading of .pc files. +// +#ifndef BUILD2_BOOTSTRAP +# include +#endif + #include #include #include @@ -27,10 +34,405 @@ using namespace butl; namespace build2 { +#ifndef BUILD2_BOOTSTRAP + // Load package information from a .pc file. Filter out the -I/-L options + // that refer to system directories. + // + // Note that the prerequisite package .pc files search order is as follows: + // + // - in directory of the specified file + // - in pc_dirs directories (in the natural order) + // + class pkgconf + { + public: + using path_type = build2::path; + + path_type path; + + public: + explicit + pkgconf (path_type, + const dir_paths& pc_dirs, + const dir_paths& sys_inc_dirs, + const dir_paths& sys_lib_dirs); + + // Create a special empty object. Querying package information on such + // an object is illegal. + // + pkgconf () = default; + + ~pkgconf (); + + // Movable-only type. + // + pkgconf (pkgconf&& p) + : path (move (p.path)), + client_ (p.client_), + pkg_ (p.pkg_) + { + p.client_ = nullptr; + p.pkg_ = nullptr; + } + + pkgconf& + operator= (pkgconf&& p) + { + if (this != &p) + { + this->~pkgconf (); + new (this) pkgconf (move (p)); // Assume noexcept move-construction. + } + return *this; + } + + pkgconf (const pkgconf&) = delete; + pkgconf& operator= (const pkgconf&) = delete; + + strings + cflags (bool stat) const; + + strings + libs (bool stat) const; + + string + variable (const char*) const; + + string + variable (const string& s) const {return variable (s.c_str ());} + + private: + // Keep them as raw pointers not to deal with API thread-unsafety in + // deleters and introducing additional mutex locks. + // + pkgconf_client_t* client_ = nullptr; + pkgconf_pkg_t* pkg_ = nullptr; + }; + + // Currently the library is not thread-safe, even on the pkgconf_client_t + // level (see issue #128 for details). + // + // @@ An update: seems that the obvious thread-safety issues are fixed. + // However, let's keep mutex locking for now not to introduce potential + // issues before we make sure there are no other ones. + // + static mutex pkgconf_mutex; + + // The package dependency traversal depth limit. + // + static const int pkgconf_max_depth = 100; + + // Normally the error_handler() callback can be called multiple times to + // report a single error (once per message line), to produce a multi-line + // message like this: + // + // Package foo was not found in the pkg-config search path.\n + // Perhaps you should add the directory containing `foo.pc'\n + // to the PKG_CONFIG_PATH environment variable\n + // Package 'foo', required by 'bar', not found\n + // + // For the above example callback will be called 4 times. To suppress all the + // junk we will use PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS to get just: + // + // Package 'foo', required by 'bar', not found\n + // + static const int pkgconf_flags = PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; + + static bool + pkgconf_error_handler (const char* msg, const pkgconf_client_t*, const void*) + { + error << runtime_error (msg); // Sanitize the message. + return true; + } + + // Deleters. Note that they are thread-safe. + // + struct fragments_deleter + { + void operator() (pkgconf_list_t* f) const {pkgconf_fragment_free (f);} + }; + + // Convert fragments to strings. Skip the -I/-L options that refer to system + // directories. + // + static strings + to_strings (const pkgconf_list_t& frags, + char type, + const pkgconf_list_t& sysdirs) + { + assert (type == 'I' || type == 'L'); + + strings r; + + auto add = [&r] (const pkgconf_fragment_t* frag) + { + string s; + if (frag->type != '\0') + { + s += '-'; + s += frag->type; + } + + s += frag->data; + r.push_back (move (s)); + }; + + // Option that is separated from its value, for example: + // + // -I /usr/lib + // + const pkgconf_fragment_t* opt (nullptr); + + pkgconf_node_t *node; + PKGCONF_FOREACH_LIST_ENTRY(frags.head, node) + { + auto frag (static_cast (node->data)); + + // Add the separated option and directory, unless the latest is a system + // one. + // + if (opt != nullptr) + { + // Note that we should restore the directory path that was + // (mis)interpreted as an option, for example: + // + // -I -Ifoo + // + // In the above example option '-I' is followed by directory '-Ifoo', + // which is represented by libpkgconf library as fragment 'foo' with + // type 'I'. + // + if (!pkgconf_path_match_list ( + frag->type == '\0' + ? frag->data + : (string ({'-', frag->type}) + frag->data).c_str (), + &sysdirs)) + { + add (opt); + add (frag); + } + + opt = nullptr; + continue; + } + + // Skip the -I/-L option if it refers to a system directory. + // + if (frag->type == type) + { + // The option is separated from a value, that will (presumably) follow. + // + if (*frag->data == '\0') + { + opt = frag; + continue; + } + + if (pkgconf_path_match_list (frag->data, &sysdirs)) + continue; + } + + add (frag); + } + + if (opt != nullptr) // Add the dangling option. + add (opt); + + return r; + } + + // Note that some libpkgconf functions can potentially return NULL, failing + // to allocate the required memory block. However, we will not check the + // returned value for NULL as the library doesn't do so, prior to filling the + // allocated structures. So such a code complication on our side would be + // useless. Also, for some functions the NULL result has a special semantics, + // for example "not found". + // + pkgconf:: + pkgconf (path_type p, + const dir_paths& pc_dirs, + const dir_paths& sys_lib_dirs, + const dir_paths& sys_inc_dirs) + : path (move (p)) + { + auto add_dirs = [] (pkgconf_list_t& dir_list, + const dir_paths& dirs, + bool suppress_dups, + bool cleanup = false) + { + if (cleanup) + { + pkgconf_path_free (&dir_list); + dir_list = PKGCONF_LIST_INITIALIZER; + } + + for (const auto& d: dirs) + pkgconf_path_add (d.string ().c_str (), &dir_list, suppress_dups); + }; + + mlock l (pkgconf_mutex); + + // Initialize the client handle. + // + unique_ptr c ( + pkgconf_client_new (pkgconf_error_handler, + nullptr /* error_handler_data */), + [] (pkgconf_client_t* c) {pkgconf_client_free (c);}); + + pkgconf_client_set_flags (c.get (), pkgconf_flags); + + // Note that the system header and library directory lists are + // automatically pre-filled by the pkgconf_client_new() call (see above). + // We will re-create these lists from scratch. + // + add_dirs (c->filter_libdirs, + sys_lib_dirs, + false /* suppress_dups */, + true /* cleanup */); + + add_dirs (c->filter_includedirs, + sys_inc_dirs, + false /* suppress_dups */, + true /* cleanup */); + + // Note that the loaded file directory is added to the (yet empty) search + // list. Also note that loading of the prerequisite packages is delayed + // until flags retrieval, and their file directories are not added to the + // search list. + // + pkg_ = pkgconf_pkg_find (c.get (), path.string ().c_str ()); + + if (pkg_ == nullptr) + fail << "package '" << path << "' not found or invalid"; + + // Add the .pc file search directories. + // + assert (c->dir_list.length == 1); // Package file directory (see above). + add_dirs (c->dir_list, pc_dirs, true /* suppress_dups */); + + client_ = c.release (); + } + + pkgconf:: + ~pkgconf () + { + if (client_ != nullptr) // Not empty. + { + assert (pkg_ != nullptr); + + mlock l (pkgconf_mutex); + pkgconf_pkg_unref (client_, pkg_); + pkgconf_client_free (client_); + } + } + + strings pkgconf:: + cflags (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + + pkgconf_client_set_flags ( + client_, + pkgconf_flags | + + // Walk through the private package dependencies (Requires.private) + // besides the public ones while collecting the flags. Note that we do + // this for both static and shared linking. + // + PKGCONF_PKG_PKGF_SEARCH_PRIVATE | + + // Collect flags from Cflags.private besides those from Cflags for the + // static linking. + // + (stat + ? PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS + : 0)); + + pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. + int e (pkgconf_pkg_cflags (client_, pkg_, &f, pkgconf_max_depth)); + + if (e != PKGCONF_PKG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr fd (&f); // Auto-deleter. + return to_strings (f, 'I', client_->filter_includedirs); + } + + strings pkgconf:: + libs (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + + pkgconf_client_set_flags ( + client_, + pkgconf_flags | + + // Additionally collect flags from the private dependency packages + // (see above) and from the Libs.private value for the static linking. + // + (stat + ? PKGCONF_PKG_PKGF_SEARCH_PRIVATE | + PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS + : 0)); + + pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. + int e (pkgconf_pkg_libs (client_, pkg_, &f, pkgconf_max_depth)); + + if (e != PKGCONF_PKG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr fd (&f); // Auto-deleter. + return to_strings (f, 'L', client_->filter_libdirs); + } + + string pkgconf:: + variable (const char* name) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + const char* r (pkgconf_tuple_find (client_, &pkg_->vars, name)); + return r != nullptr ? string (r) : string (); + } + +#endif + namespace cc { using namespace bin; + // In pkg-config backslashes, spaces, etc are escaped with a backslash. + // + static string + escape (const string& s) + { + string r; + + for (size_t p (0);;) + { + size_t sp (s.find_first_of ("\\ ", p)); + + if (sp != string::npos) + { + r.append (s, p, sp - p); + r += '\\'; + r += s[sp]; + p = sp + 1; + } + else + { + r.append (s, p, sp); + break; + } + } + + return r; + } + // 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, // load poptions, loptions, libs, and modules, set the corresponding @@ -39,11 +441,15 @@ namespace build2 // MT-safe. // // System library search paths (those extracted from the compiler) are - // passed in sys_sp and should already be extracted. + // passed in top_sysd while the user-provided (via -L) in top_usrd. // // Note that scope and link order should be "top-level" from the // search_library() POV. // + // Also note that the bootstrapped version of build2 will not search for + // .pc files, always returning false (see above for the reasoning). + // +#ifndef BUILD2_BOOTSTRAP bool common:: pkgconfig_load (action act, const scope& s, @@ -53,27 +459,67 @@ namespace build2 const optional& proj, const string& stem, const dir_path& libd, - const dir_paths& sysd) const + const dir_paths& top_sysd, + const dir_paths& top_usrd) const { 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). Suffix is our ".shared" or ".static" extension. + // Iterate over pkgconf directories that correspond to the specified + // library's directory, passing them to the callback function, until + // the function returns false. + // + // 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. // - auto search_dir = [&proj, &stem, &libd] (const dir_path& dir, - const string& sfx) -> path + using callback = function; + auto pkgconf_dir = [this] (const dir_path& d, const callback& f) -> bool { - // Check if we have this subdirectory in this library's directory. + dir_path pd (d); + if (exists (pd /= "pkgconfig") && !f (move (pd))) + return false; + + // Platform-specific locations. // - dir_path pkgd (dir_path (libd) /= dir); + if (tsys == "freebsd") + { + // On FreeBSD .pc files go to libdata/pkgconfig/, not lib/pkgconfig/. + // + pd = d; + if (exists (((pd /= "..") /= "libdata") /= "pkgconfig") && + !f (move (pd))) + return false; + } + + return true; + }; - if (!exists (pkgd)) - return path (); + // Same as above but iterate over pkgconfig directories for multiple + // library directories. + // + auto pkgconf_dirs = [&pkgconf_dir] (const dir_paths& ds, + const callback& f) -> bool + { + for (const auto& d: ds) + { + if (!pkgconf_dir (d, f)) + return false; + } + return true; + }; + + // 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). Suffix is our ".shared" or ".static" extension. + // + auto search_dir = [&proj, &stem] (const dir_path& dir, + const string& sfx) -> path + { path f; // See if there is a corresponding .pc file. About half of them called @@ -88,7 +534,7 @@ namespace build2 // then you get something like zlib which calls it zlib.pc. So let's // just do it. // - f = pkgd; + f = dir; f /= "lib"; f += stem; f += sfx; @@ -96,7 +542,7 @@ namespace build2 if (exists (f)) return f; - f = pkgd; + f = dir; f /= stem; f += sfx; f += ".pc"; @@ -105,7 +551,7 @@ namespace build2 if (proj) { - f = pkgd; + f = dir; f /= *proj; f += sfx; f += ".pc"; @@ -116,11 +562,16 @@ namespace build2 return path (); }; - auto search = [&search_dir, this] () -> pair + auto search = + [&libd, &search_dir, &pkgconf_dir, this] () -> pair { pair r; - auto check = [&r, &search_dir] (const dir_path& d) -> bool + // Return false (and so stop to iterate) if .pc file is found. + // + // Note that we rely on "small function object" optimization here. + // + auto check = [&r, &search_dir] (dir_path&& d) -> bool { // First look for static/shared-specific files. // @@ -128,120 +579,34 @@ namespace build2 r.second = search_dir (d, ".shared"); if (!r.first.empty () || !r.second.empty ()) - return true; + return false; // Then the common. // r.first = r.second = search_dir (d, string ()); - return !r.first.empty (); + 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 (!check (dir_path ("pkgconfig"))) - { - // Platform-specific locations. - // - if (tsys == "freebsd") - { - // On FreeBSD .pc files go to libdata/pkgconfig/, not lib/pkgconfig/. - // - check ((dir_path ("..") /= "libdata") /= "pkgconfig"); - } - } - - return r; - }; - - // To keep things simple, we run pkg-config multiple times, for - // --cflag/--libs, with and without --static. - // - auto extract = [this] (const path& f, const char* o, bool a = false) - { - const char* args[] = { - pkgconfig->recall_string (), - o, // --cflags/--libs/--variable= - (a ? "--static" : f.string ().c_str ()), - (a ? f.string ().c_str () : nullptr), - nullptr - }; - - return run ( - *pkgconfig, args, [] (string& s) -> string {return move (s);}); - }; - - // 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 - { - 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]; - } - + pkgconf_dir (libd, check); return r; }; // 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) + auto parse_cflags = [&trace, this] (target& t, const pkgconf& pc, bool a) { - string cstr (extract (f, "--cflags", a)); strings pops; - string o; - char arg ('\0'); - for (size_t b (0), e (0); !(o = next (cstr, b, e)).empty (); ) + bool arg (false); + for (auto& o: pc.cflags (a)) { - // Filter out /usr/local/include since most platforms/compilers - // search in there (at the end, as in other system header - // directories) by default. And for those that don't (like FreeBSD) - // we should just fix it ourselves (see config_module::init ()). - // - // Failed that /usr/local/include may appear before "more specific" - // directories which can lead to the installed headers being picked - // up instead. - // - if (arg != '\0') + if (arg) { - if (arg == 'I' && o == "/usr/local/include") - pops.pop_back (); - else - pops.push_back (move (o)); - - arg = '\0'; + // Can only be an argument for -I, -D, -U options. + // + pops.push_back (move (o)); + arg = false; continue; } @@ -253,22 +618,18 @@ namespace build2 o[0] == '-' && (o[1] == 'I' || o[1] == 'D' || o[1] == 'U')) { - if (!(n > 2 && - o[1] == 'I' && - o.compare (2, string::npos, "/usr/local/include") == 0)) - pops.push_back (move (o)); - - if (n == 2) - arg = o[1]; + pops.push_back (move (o)); + arg = (n == 2); continue; } - l4 ([&]{trace << "ignoring " << f << " --cflags option " << o;}); + l4 ([&]{trace << "ignoring " << pc.path << " --cflags option " + << o;}); } - if (arg != '\0') + if (arg) fail << "argument expected after " << pops.back () << - info << "while parsing pkg-config --cflags " << f; + info << "while parsing pkg-config --cflags " << pc.path; if (!pops.empty ()) { @@ -287,11 +648,9 @@ namespace build2 // Parse --libs into loptions/libs (interface and implementation). If // ps is not NULL, add each resolves library target as a prerequisite. // - auto parse_libs = [act, &s, sysd, &extract, &next, this] - (target& t, const path& f, bool a, prerequisites* ps) + auto parse_libs = [act, &s, top_sysd, this] + (target& t, const pkgconf& pc, bool a, prerequisites* ps) { - string lstr (extract (f, "--libs", a)); - strings lops; vector libs; @@ -307,8 +666,7 @@ namespace build2 // 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 (); ) + for (auto& o: pc.libs (a)) { if (arg) { @@ -346,6 +704,14 @@ namespace build2 continue; } + // @@ If by some reason this is the library itself (doesn't go + // first or libpkgconf parsed libs in some bizarre way) we will + // hang trying to lock it's target inside search_library() (or + // fail an assertion if run serially) as by now it is already + // locked. To be safe we probably shouldn't rely on the position + // and filter out all occurrences of the library itself (by + // name?) and complain if none were encountered. + // libs.push_back (name (move (o))); continue; } @@ -358,11 +724,25 @@ namespace build2 if (arg) fail << "argument expected after " << lops.back () << - info << "while parsing pkg-config --libs " << f; + info << "while parsing pkg-config --libs " << pc.path; + + // Space-separated list of escaped library flags. + // + auto lflags = [&pc, a] () -> string + { + string r; + for (const auto& o: pc.libs (a)) + { + if (!r.empty ()) + r += ' '; + r += escape (o); + } + return r; + }; if (first) - fail << "library expected in '" << lstr << "'" << - info << "while parsing pkg-config --libs " << f; + fail << "library expected in '" << lflags () << "'" << + info << "while parsing pkg-config --libs " << pc.path; // Resolve -lfoo into the library file path using our import installed // machinery (i.e., we are going to call search_library() that will @@ -436,8 +816,8 @@ namespace build2 dir_path d (move (p)); if (d.relative ()) - fail << "relative -L directory in '" << lstr << "'" << - info << "while parsing pkg-config --libs " << f; + fail << "relative -L directory in '" << lflags () << "'" << + info << "while parsing pkg-config --libs " << pc.path; usrd->push_back (move (d)); } @@ -453,7 +833,7 @@ namespace build2 prerequisite_key pk { nullopt, {&lib::static_type, &out, &out, &name, nullopt}, &s}; - if (const target* lt = search_library (act, sysd, usrd, pk)) + if (const target* lt = search_library (act, top_sysd, usrd, pk)) { // We used to pick a member but that doesn't seem right since the // same target could be used with different link orders. @@ -521,12 +901,51 @@ namespace build2 } }; + // 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 + { + 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 modules and add them to the prerequisites. // - auto parse_modules = [&trace, &extract, &next, this] - (const path& f, prerequisites& ps) + auto parse_modules = [&trace, &next, this] + (const pkgconf& pc, prerequisites& ps) { - string mstr (extract (f, "--variable=modules")); + string mstr (pc.variable ("modules")); string m; for (size_t b (0), e (0); !(m = next (mstr, b, e)).empty (); ) @@ -538,7 +957,8 @@ namespace build2 p == 0 || // Empty name. p == m.size () - 1) // Empty path. fail << "invalid module information in '" << mstr << "'" << - info << "while parsing pkg-config --variable=modules " << f; + info << "while parsing pkg-config --variable=modules " + << pc.path; string mn (m, 0, p); path mp (m, p + 1, string::npos); @@ -546,10 +966,8 @@ namespace build2 // Extract module properties, if any. // - string pp ( - extract (f, ("--variable=module_preprocessed." + mn).c_str ())); - string se ( - extract (f, ("--variable=module_symexport." + mn).c_str ())); + string pp (pc.variable ("module_preprocessed." + mn)); + string se (pc.variable ("module_symexport." + mn)); // For now we assume these are C++ modules. There aren't any other // kind currently but if there were we would need to encode this @@ -612,24 +1030,55 @@ namespace build2 // liba{} would require weeding out duplicates that are already in // lib{}. // - prerequisites ps; + prerequisites prs; + + pkgconf apc; + pkgconf spc; + + // Create the .pc files search directory list. + // + dir_paths pc_dirs; + + // Note that we rely on "small function object" optimization here. + // + auto add_pc_dir = [&pc_dirs] (dir_path&& d) -> bool + { + pc_dirs.emplace_back (move (d)); + return true; + }; + + pkgconf_dir (libd, add_pc_dir); + pkgconf_dirs (top_usrd, add_pc_dir); + pkgconf_dirs (top_sysd, add_pc_dir); // First sort out the interface dependencies (which we are setting on // lib{}). If we have the shared .pc variant, then we use that. // Otherwise -- static but extract without the --static option (see also // the saving logic). // - const path& ip (sp.empty () ? ap : sp); // Interface path. - parse_libs (lt, ip, false, &ps); + pkgconf& ipc (sp.empty () ? apc : spc); // Interface package info. - if (at != nullptr && !ap.empty ()) + // @@ Currently sys_inc_dirs doesn't contain the compiler-extracted + // paths. + // + bool pa (at != nullptr && !ap.empty ()); + if (pa || sp.empty ()) + apc = pkgconf (ap, pc_dirs, sys_lib_dirs, sys_inc_dirs); + + bool ps (st != nullptr && !sp.empty ()); + if (ps || ap.empty ()) + spc = pkgconf (sp, pc_dirs, sys_lib_dirs, sys_inc_dirs); + + parse_libs (lt, ipc, false, &prs); + + if (pa) { - parse_cflags (*at, ap, true); - parse_libs (*at, ap, true, nullptr); + parse_cflags (*at, apc, true); + parse_libs (*at, apc, true, nullptr); } - if (st != nullptr && !sp.empty ()) - parse_cflags (*st, sp, false); + if (ps) + parse_cflags (*st, spc, false); // For now we assume static and shared variants export the same set of // modules. While technically possible, having a different set will @@ -637,21 +1086,40 @@ namespace build2 // libraries) and life is short. // if (modules) - parse_modules (ip, ps); + parse_modules (ipc, prs); assert (!lt.has_prerequisites ()); - if (!ps.empty ()) - lt.prerequisites (move (ps)); + if (!prs.empty ()) + lt.prerequisites (move (prs)); // Bless the library group with a "trust me it exists" timestamp. Failed // that, if we add it as a prerequisite (like we do above), the fallback // file rule won't match. // - lt.mtime (file_mtime (ip)); + lt.mtime (file_mtime (ipc.path)); return true; } +#else + + bool common:: + pkgconfig_load (action, + const scope&, + lib&, + liba*, + libs*, + const optional&, + const string&, + const dir_path&, + const dir_paths&, + const dir_paths&) const + { + return false; + } + +#endif + void link:: pkgconfig_save (action act, const file& l, bool la) const { @@ -698,33 +1166,7 @@ namespace build2 os << "URL: " << *u << endl; } - // In pkg-config backslashes, spaces, etc are escaped with a - // backslash. - // - auto escape = [] (const string& s) -> string - { - string r; - for (size_t p (0);;) - { - size_t sp (s.find_first_of ("\\ ", p)); - - if (sp != string::npos) - { - r.append (s, p, sp - p); - r += '\\'; - r += s[sp]; - p = sp + 1; - } - else - { - r.append (s, p, sp); - break; - } - } - return r; - }; - - auto save_poptions = [&l, &os, &escape] (const variable& var) + auto save_poptions = [&l, &os] (const variable& var) { if (const strings* v = cast_null (l[var])) { diff --git a/build2/version/snapshot.cxx b/build2/version/snapshot.cxx index 143c200..ee7c2f3 100644 --- a/build2/version/snapshot.cxx +++ b/build2/version/snapshot.cxx @@ -24,7 +24,7 @@ namespace build2 // .git can be either a directory or a file in case of a submodule. // - if (build2::entry_exists (src_root / git, /* follow_symlinks */ true)) + if (build2::entry_exists (src_root / git, true /* follow_symlinks */)) return extract_snapshot_git (src_root); return snapshot (); diff --git a/manifest b/manifest index 1f7c021..3ac160d 100644 --- a/manifest +++ b/manifest @@ -17,3 +17,4 @@ depends: * bpkg >= 0.6.0- # @@ Should probably become conditional dependency. requires: ? cli ; Only required if changing .cli files. depends: libbutl [0.7.0-a.0.1 0.7.0-a.1) +depends: libpkgconf [1.3.9-a.0.1 1.3.9-a.1) diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx index 9286a48..80c4120 100644 --- a/unit-tests/function/driver.cxx +++ b/unit-tests/function/driver.cxx @@ -56,7 +56,7 @@ namespace build2 f["scoped"] = [](const scope&, names a) {return a;}; f["scoped_void"] = [](const scope&, names) {}; - f["scoped"] = &scoped; + f["scoped"] = &scoped; f["scoped_void"] = &scoped_void; f[".qual"] = []() {return "abc";}; -- cgit v1.1