aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/algorithm.cxx4
-rw-r--r--build2/algorithm.ixx3
-rw-r--r--build2/buildfile3
-rw-r--r--build2/cc/common.cxx2
-rw-r--r--build2/cc/common.hxx1
-rw-r--r--build2/cc/pkgconfig.cxx806
-rw-r--r--build2/version/snapshot.cxx2
-rw-r--r--manifest1
-rw-r--r--unit-tests/function/driver.cxx2
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<bool, target_state> r (match (a, t, 0, nullptr, /*try_match*/ true));
+ pair<bool, target_state> 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<string>&,
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 <libpkgconf/libpkgconf.h>
+#endif
+
#include <build2/scope.hxx>
#include <build2/target.hxx>
#include <build2/context.hxx>
@@ -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<const pkgconf_fragment_t*> (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<pkgconf_client_t, void (*) (pkgconf_client_t*)> 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<pkgconf_list_t, fragments_deleter> 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<pkgconf_list_t, fragments_deleter> 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<string>& 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<bool (dir_path&& d)>;
+ 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<path, path>
+ auto search =
+ [&libd, &search_dir, &pkgconf_dir, this] () -> pair<path, path>
{
pair<path, path> 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=<name>
- (a ? "--static" : f.string ().c_str ()),
- (a ? f.string ().c_str () : nullptr),
- nullptr
- };
-
- return run<string> (
- *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<name> 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<string>&,
+ 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<strings> (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";};