aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc/pkgconfig.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cc/pkgconfig.cxx')
-rw-r--r--libbuild2/cc/pkgconfig.cxx2125
1 files changed, 1435 insertions, 690 deletions
diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx
index 75c7227..046fbc8 100644
--- a/libbuild2/cc/pkgconfig.cxx
+++ b/libbuild2/cc/pkgconfig.cxx
@@ -1,13 +1,6 @@
// file : libbuild2/cc/pkgconfig.cxx -*- C++ -*-
// 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 <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -25,436 +18,25 @@
#include <libbuild2/cc/utility.hxx>
#include <libbuild2/cc/common.hxx>
+#include <libbuild2/cc/pkgconfig.hxx>
#include <libbuild2/cc/compile-rule.hxx>
#include <libbuild2/cc/link-rule.hxx>
-#ifndef BUILD2_BOOTSTRAP
-
-// Note that the libpkgconf library doesn't provide the version macro that we
-// could use to compile the code conditionally against different API versions.
-// Thus, we need to sense the pkgconf_client_new() function signature
-// ourselves to call it properly.
-//
-namespace details
-{
- void*
- pkgconf_cross_personality_default (); // Never called.
-}
-
-using namespace details;
-
-template <typename H>
-static inline pkgconf_client_t*
-call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*),
- H error_handler,
- void* error_handler_data)
-{
- return f (error_handler, error_handler_data);
-}
-
-template <typename H, typename P>
-static inline pkgconf_client_t*
-call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*, P),
- H error_handler,
- void* error_handler_data)
-{
- return f (error_handler,
- error_handler_data,
- ::pkgconf_cross_personality_default ());
-}
-
-#endif
-
-using namespace std;
-using namespace butl;
+using namespace std; // VC16
namespace build2
{
-#ifndef BUILD2_BOOTSTRAP
-
- // Load package information from a .pc file. Filter out the -I/-L options
- // that refer to system directories. This makes sure all the system search
- // directories are "pushed" to the back which minimizes the chances of
- // picking up wrong (e.g., old installed version) header/library.
- //
- // 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 that 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
- //
- // Also disable merging options like -framework into a single fragment, if
- // possible.
- //
- static const int pkgconf_flags =
- PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS
-#ifdef PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS
- | PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS
-#endif
- ;
-
- 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 (
- call_pkgconf_client_new (&pkgconf_client_new,
- pkgconf_error_handler,
- nullptr /* 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.
//
+ // @@ TODO: handle empty values (save as ''?)
+ //
+ // Note: may contain variable expansions (e.g, ${pcfiledir}) so unclear
+ // if can use quoting.
+ //
static string
escape (const string& s)
{
@@ -481,6 +63,35 @@ namespace build2
return r;
}
+ // Resolve metadata value type from type name. Return in the second half
+ // of the pair whether this is a dir_path-based type.
+ //
+ static pair<const value_type*, bool>
+ metadata_type (const string& tn)
+ {
+ bool d (false);
+ const value_type* r (nullptr);
+
+ if (tn == "bool") r = &value_traits<bool>::value_type;
+ else if (tn == "int64") r = &value_traits<int64_t>::value_type;
+ else if (tn == "uint64") r = &value_traits<uint64_t>::value_type;
+ else if (tn == "string") r = &value_traits<string>::value_type;
+ else if (tn == "path") r = &value_traits<path>::value_type;
+ else if (tn == "dir_path") {r = &value_traits<dir_path>::value_type; d = true;}
+ else if (tn == "int64s") r = &value_traits<int64s>::value_type;
+ else if (tn == "uint64s") r = &value_traits<uint64s>::value_type;
+ else if (tn == "strings") r = &value_traits<strings>::value_type;
+ else if (tn == "paths") r = &value_traits<paths>::value_type;
+ else if (tn == "dir_paths") {r = &value_traits<dir_paths>::value_type; d = true;}
+
+ return make_pair (r, d);
+ }
+
+ // In order not to complicate the bootstrap procedure with libpkg-config
+ // building, exclude functionality that involves reading of .pc files.
+ //
+#ifndef BUILD2_BOOTSTRAP
+
// 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
@@ -497,9 +108,8 @@ namespace build2
// 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
- // Derive pkgconf search directories from the specified library search
+ // Derive pkg-config search directories from the specified library search
// directory passing them to the callback function for as long as it
// returns false (e.g., not found). Return true if the callback returned
// true.
@@ -543,8 +153,8 @@ namespace build2
return false;
}
- // Search for the .pc files in the pkgconf directories that correspond to
- // the specified library directory. If found, return static (first) and
+ // Search for the .pc files in the pkg-config directories that correspond
+ // to the specified library directory. If found, return static (first) and
// shared (second) library .pc files. If common is false, then only
// consider our .static/.shared files.
//
@@ -554,6 +164,8 @@ namespace build2
const string& stem,
bool common) const
{
+ tracer trace (x, "pkgconfig_search");
+
// 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.
@@ -575,28 +187,36 @@ namespace build2
// then you get something like zlib which calls it zlib.pc. So let's
// just do it.
//
- f = dir;
- f /= "lib";
- f += stem;
- f += sfx;
- f += ".pc";
- if (exists (f))
- return f;
+ // And as you think you've covered all the bases, someone decides to
+ // play with the case (libXau.* vs xau.pc). So let's also try the
+ // lower-case versions of the stem unless we are on a case-insensitive
+ // filesystem.
+ //
+ auto check = [&dir, & sfx, &f] (const string& n)
+ {
+ f = dir;
+ f /= n;
+ f += sfx;
+ f += ".pc";
+ return exists (f);
+ };
- f = dir;
- f /= stem;
- f += sfx;
- f += ".pc";
- if (exists (f))
+ if (check ("lib" + stem) || check (stem))
return f;
+#ifndef _WIN32
+ string lstem (lcase (stem));
+
+ if (lstem != stem)
+ {
+ if (check ("lib" + lstem) || check (lstem))
+ return f;
+ }
+#endif
+
if (proj)
{
- f = dir;
- f /= proj->string ();
- f += sfx;
- f += ".pc";
- if (exists (f))
+ if (check (proj->string ()))
return f;
}
@@ -636,15 +256,18 @@ namespace build2
if (pkgconfig_derive (libd, check))
{
+ l6 ([&]{trace << "found " << libd << stem << " in "
+ << (d.a.empty () ? d.a : d.s).directory ();});
+
r.first = move (d.a);
r.second = move (d.s);
}
return r;
- };
+ }
bool common::
- pkgconfig_load (action a,
+ pkgconfig_load (optional<action> act,
const scope& s,
lib& lt,
liba* at,
@@ -653,7 +276,8 @@ namespace build2
const string& stem,
const dir_path& libd,
const dir_paths& top_sysd,
- const dir_paths& top_usrd) const
+ const dir_paths& top_usrd,
+ pair<bool, bool> metaonly) const
{
assert (at != nullptr || st != nullptr);
@@ -663,12 +287,16 @@ namespace build2
if (p.first.empty () && p.second.empty ())
return false;
- pkgconfig_load (a, s, lt, at, st, p, libd, top_sysd, top_usrd);
+ pkgconfig_load (
+ act, s, lt, at, st, p, libd, top_sysd, top_usrd, metaonly);
return true;
}
+ // Action should be absent if called during the load phase. If metaonly is
+ // true then only load the metadata.
+ //
void common::
- pkgconfig_load (action a,
+ pkgconfig_load (optional<action> act,
const scope& s,
lib& lt,
liba* at,
@@ -676,7 +304,8 @@ namespace build2
const pair<path, path>& paths,
const dir_path& libd,
const dir_paths& top_sysd,
- const dir_paths& top_usrd) const
+ const dir_paths& top_usrd,
+ pair<bool /* a */, bool /* s */> metaonly) const
{
tracer trace (x, "pkgconfig_load");
@@ -687,24 +316,120 @@ namespace build2
assert (!ap.empty () || !sp.empty ());
- // Extract --cflags and set them as lib?{}:export.poptions. Note that we
- // still pass --static in case this is pkgconf which has Cflags.private.
+ const scope& rs (*s.root_scope ());
+
+ const dir_path* sysroot (
+ cast_null<abs_dir_path> (rs["config.cc.pkgconfig.sysroot"]));
+
+ // Append -I<dir> or -L<dir> option suppressing duplicates. Also handle
+ // the sysroot rewrite.
+ //
+ auto append_dir = [sysroot] (strings& ops, string&& o)
+ {
+ char c (o[1]);
+
+ // @@ Should we normalize the path for good measure? But on the other
+ // hand, most of the time when it's not normalized, it will likely
+ // be "consistently-relative", e.g., something like
+ // ${prefix}/lib/../include. I guess let's wait and see for some
+ // real-world examples.
+ //
+ // Well, we now support generating relocatable .pc files that have
+ // a bunch of -I${pcfiledir}/../../include and -L${pcfiledir}/.. .
+ //
+ // On the other hand, there could be symlinks involved and just
+ // normalize() may not be correct.
+ //
+ // Note that we do normalize -L paths in the usrd logic later
+ // (but not when setting as *.export.loptions).
+
+ if (sysroot != nullptr)
+ {
+ // Notes:
+ //
+ // - The path might not be absolute (we only rewrite absolute ones).
+ //
+ // - Do this before duplicate suppression since options in ops
+ // already have the sysroot rewritten.
+ //
+ // - Check if the path already starts with sysroot since some .pc
+ // files might already be in a good shape (e.g., because they use
+ // ${pcfiledir} to support relocation properly).
+ //
+ const char* op (o.c_str () + 2);
+ size_t on (o.size () - 2);
+
+ if (path_traits::absolute (op, on))
+ {
+ const string& s (sysroot->string ());
+
+ const char* sp (s.c_str ());
+ size_t sn (s.size ());
+
+ if (!path_traits::sub (op, on, sp, sn)) // Already in sysroot.
+ {
+ // Find the first directory seperator that seperates the root
+ // component from the rest of the path (think /usr/include,
+ // c:\install\include). We need to replace the root component
+ // with sysroot. If there is no separator (say, -Ic:) or the
+ // path after the separator is empty (say, -I/), then we replace
+ // the entire path.
+ //
+ size_t p (path_traits::find_separator (o, 2));
+ if (p == string::npos || p + 1 == o.size ())
+ p = o.size ();
+
+ o.replace (2, p - 2, s);
+ }
+ }
+ }
+
+ for (const string& x: ops)
+ {
+ if (x.size () > 2 && x[0] == '-' && x[1] == c)
+ {
+ if (path_traits::compare (x.c_str () + 2, x.size () - 2,
+ o.c_str () + 2, o.size () - 2) == 0)
+ return; // Duplicate.
+ }
+ }
+
+ ops.push_back (move (o));
+ };
+
+ // Extract --cflags and set them as lib?{}:export.poptions returing the
+ // pointer to the set value. If [as]pops are not NULL, then only keep
+ // options that are present in both.
//
- auto parse_cflags = [&trace, this] (target& t,
- const pkgconf& pc,
- bool la)
+ auto parse_cflags =[&trace,
+ this,
+ &append_dir] (target& t,
+ const pkgconfig& pc,
+ bool la,
+ const strings* apops = nullptr,
+ const strings* spops = nullptr)
+ -> const strings*
{
+ // Note that we normalize `-[IDU] <arg>` to `-[IDU]<arg>`.
+ //
strings pops;
- bool arg (false);
- for (auto& o: pc.cflags (la))
+ char arg ('\0'); // Option with pending argument.
+ for (string& o: pc.cflags (la))
{
if (arg)
{
// Can only be an argument for -I, -D, -U options.
//
- pops.push_back (move (o));
- arg = false;
+ o.insert (0, 1, arg);
+ o.insert (0, 1, '-');
+
+ if (arg == 'I')
+ append_dir (pops, move (o));
+ else
+ pops.push_back (move (o));
+
+ arg = '\0';
continue;
}
@@ -713,11 +438,17 @@ namespace build2
// We only keep -I, -D and -U.
//
if (n >= 2 &&
- o[0] == '-' &&
- (o[1] == 'I' || o[1] == 'D' || o[1] == 'U'))
+ o[0] == '-' && (o[1] == 'I' || o[1] == 'D' || o[1] == 'U'))
{
- pops.push_back (move (o));
- arg = (n == 2);
+ if (n > 2)
+ {
+ if (o[1] == 'I')
+ append_dir (pops, move (o));
+ else
+ pops.push_back (move (o));
+ }
+ else
+ arg = o[1];
continue;
}
@@ -726,7 +457,7 @@ namespace build2
}
if (arg)
- fail << "argument expected after " << pops.back () <<
+ fail << "argument expected after -" << arg <<
info << "while parsing pkg-config --cflags " << pc.path;
if (!pops.empty ())
@@ -739,19 +470,45 @@ namespace build2
// export stub and we shouldn't touch them.
//
if (p.second)
- p.first.get () = move (pops);
+ {
+ // If required, only keep common stuff. While removing the entries
+ // is not the most efficient way, it is simple.
+ //
+ if (apops != nullptr || spops != nullptr)
+ {
+ for (auto i (pops.begin ()); i != pops.end (); )
+ {
+ if ((apops != nullptr && find (
+ apops->begin (), apops->end (), *i) == apops->end ()) ||
+ (spops != nullptr && find (
+ spops->begin (), spops->end (), *i) == spops->end ()))
+ i = pops.erase (i);
+ else
+ ++i;
+ }
+ }
+
+ p.first = move (pops);
+ return &p.first.as<strings> ();
+ }
}
+
+ return nullptr;
};
// Parse --libs into loptions/libs (interface and implementation). If
- // ps is not NULL, add each resolves library target as a prerequisite.
+ // ps is not NULL, add each resolved library target as a prerequisite.
//
- auto parse_libs = [a, &s, top_sysd, this] (target& t,
- bool binless,
- const pkgconf& pc,
- bool la,
- prerequisites* ps)
+ auto parse_libs = [this,
+ &append_dir,
+ act, &s, top_sysd] (target& t,
+ bool binless,
+ const pkgconfig& pc,
+ bool la,
+ prerequisites* ps)
{
+ // Note that we normalize `-L <arg>` to `-L<arg>`.
+ //
strings lops;
vector<name> libs;
@@ -760,22 +517,29 @@ namespace build2
// library is binless. 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.
+ // name/path. Note that (after some meditation) we consider -pthread
+ // a special form of -l.
//
// 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;
- for (auto& o: pc.libs (la))
+ bool first (true), known (true), have_L (false);
+
+ string self; // The library itself (-l of just name/path).
+
+ char arg ('\0'); // Option with pending argument.
+ for (string& o: pc.libs (la))
{
if (arg)
{
- // Can only be an argument for an loption.
+ // Can only be an argument for an -L option.
//
- lops.push_back (move (o));
- arg = false;
+ o.insert (0, 1, arg);
+ o.insert (0, 1, '-');
+ append_dir (lops, move (o));
+ arg = '\0';
continue;
}
@@ -785,44 +549,54 @@ namespace build2
//
if (n >= 2 && o[0] == '-' && o[1] == 'L')
{
+ if (n > 2)
+ append_dir (lops, move (o));
+ else
+ arg = o[1];
have_L = true;
- lops.push_back (move (o));
- arg = (n == 2);
continue;
}
- // See if that's -l or just the library name/path.
+ // See if that's -l, -pthread, or just the library name/path.
//
- if ((known && o[0] != '-') ||
- (n > 2 && o[0] == '-' && o[1] == 'l'))
+ if ((known && n != 0 && o[0] != '-') ||
+ (n > 2 && o[0] == '-' && (o[1] == 'l' || o == "-pthread")))
{
// Unless binless, the 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.
//
+ // What we have enountered (e.g., in the Magick++ library) is the
+ // library itself repeated in Libs.private. So now we save it and
+ // filter all its subsequent occurences.
+ //
+ // @@ 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.
+ //
+ // Note also that the same situation can occur if we have a
+ // binful library for which we could not find the library
+ // binary and are treating it as binless. We now have a diag
+ // frame around the call to search_library() to help diagnose
+ // such situations.
+ //
if (first)
{
first = false;
if (!binless)
+ {
+ self = move (o);
+ continue;
+ }
+ }
+ else
+ {
+ if (!binless && o == self)
continue;
}
- // @@ If by some reason this is the library itself (doesn't go
- // first or libpkgconf parsed libs in some bizarre way) we will
- // have a dependency cycle by trying to lock its target inside
- // search_library() 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.
- //
- // Note also that the same situation can occur if we have a
- // binful library for which we could not find the library
- // binary and are treating it as binless. We now have a diag
- // frame around the call to search_library() to help diagnose
- // such situations.
- //
libs.push_back (name (move (o)));
continue;
}
@@ -834,7 +608,7 @@ namespace build2
}
if (arg)
- fail << "argument expected after " << lops.back () <<
+ fail << "argument expected after -" << arg <<
info << "while parsing pkg-config --libs " << pc.path;
// Space-separated list of escaped library flags.
@@ -842,7 +616,7 @@ namespace build2
auto lflags = [&pc, la] () -> string
{
string r;
- for (const auto& o: pc.libs (la))
+ for (const string& o: pc.libs (la))
{
if (!r.empty ())
r += ' ';
@@ -851,7 +625,7 @@ namespace build2
return r;
};
- if (first && !binless)
+ if (!binless && self.empty ())
fail << "library expected in '" << lflags () << "'" <<
info << "while parsing pkg-config --libs " << pc.path;
@@ -864,8 +638,8 @@ namespace build2
// import installed, or via a .pc file (which we could have generated
// from the export stub). The exception is "runtime libraries" (which
// are really the extension of libc or the operating system in case of
- // Windows) such as -lm, -ldl, -lpthread, etc. Those we will detect
- // and leave as -l*.
+ // Windows) such as -lm, -ldl, -lpthread (or its -pthread variant),
+ // etc. Those we will detect and leave as -l*.
//
// If we managed to resolve all the -l's (sans runtime), then we can
// omit -L's for a nice and tidy command line.
@@ -891,26 +665,57 @@ namespace build2
if (l[0] != '-') // e.g., just shell32.lib
continue;
- else if (cmp ("advapi32") ||
- cmp ("bcrypt") ||
- cmp ("crypt32") ||
- cmp ("d3d", 3) || // d3d*
- cmp ("gdi32") ||
- cmp ("imagehlp") ||
- cmp ("mswsock") ||
- cmp ("msxml", 5) || // msxml*
- cmp ("normaliz") ||
- cmp ("odbc32") ||
- cmp ("ole32") ||
- cmp ("oleaut32") ||
- cmp ("rpcrt4") ||
- cmp ("secur32") ||
- cmp ("shell32") ||
- cmp ("shlwapi") ||
- cmp ("version") ||
- cmp ("ws2") ||
- cmp ("ws2_32") ||
- cmp ("wsock32"))
+ else if (cmp ("advapi32") ||
+ cmp ("authz") ||
+ cmp ("bcrypt") ||
+ cmp ("comdlg32") ||
+ cmp ("crypt32") ||
+ cmp ("d2d1") ||
+ cmp ("d3d", 3) || // d3d*
+ cmp ("dbgeng") ||
+ cmp ("dbghelp") ||
+ cmp ("dnsapi") ||
+ cmp ("dwmapi") ||
+ cmp ("dwrite") ||
+ cmp ("dxgi") ||
+ cmp ("dxguid") ||
+ cmp ("gdi32") ||
+ cmp ("glu32") ||
+ cmp ("imagehlp") ||
+ cmp ("imm32") ||
+ cmp ("iphlpapi") ||
+ cmp ("kernel32") ||
+ cmp ("mincore") ||
+ cmp ("mpr") ||
+ cmp ("msimg32") ||
+ cmp ("mswsock") ||
+ cmp ("msxml", 5) || // msxml*
+ cmp ("netapi32") ||
+ cmp ("normaliz") ||
+ cmp ("odbc32") ||
+ cmp ("ole32") ||
+ cmp ("oleaut32") ||
+ cmp ("opengl32") ||
+ cmp ("powrprof") ||
+ cmp ("psapi") ||
+ cmp ("rpcrt4") ||
+ cmp ("secur32") ||
+ cmp ("setupapi") ||
+ cmp ("shell32") ||
+ cmp ("shlwapi") ||
+ cmp ("synchronization") ||
+ cmp ("user32") ||
+ cmp ("userenv") ||
+ cmp ("uuid") ||
+ cmp ("version") ||
+ cmp ("windowscodecs") ||
+ cmp ("winhttp") ||
+ cmp ("winmm") ||
+ cmp ("winspool") ||
+ cmp ("ws2") ||
+ cmp ("ws2_32") ||
+ cmp ("wsock32") ||
+ cmp ("wtsapi32"))
{
if (tsys == "win32-msvc")
{
@@ -921,6 +726,11 @@ namespace build2
}
continue;
}
+ else if (tsys == "mingw32")
+ {
+ if (l == "-pthread")
+ continue;
+ }
}
else
{
@@ -930,6 +740,7 @@ namespace build2
l == "-lm" ||
l == "-ldl" ||
l == "-lrt" ||
+ l == "-pthread" ||
l == "-lpthread")
continue;
@@ -948,7 +759,11 @@ namespace build2
}
else if (tclass == "macos")
{
- if (l == "-lSystem")
+ // Note that Mac OS has libiconv in /usr/lib/ which only comes
+ // in the shared variant. So we treat it as system.
+ //
+ if (l == "-lSystem" ||
+ l == "-liconv")
continue;
}
else if (tclass == "bsd")
@@ -965,18 +780,13 @@ namespace build2
{
usrd = dir_paths ();
- for (auto i (lops.begin ()); i != lops.end (); ++i)
+ for (const string& o: lops)
{
- const string& o (*i);
-
- if (o.size () >= 2 && o[0] == '-' && o[1] == 'L')
+ // Note: always in the -L<dir> form (see above).
+ //
+ 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);
+ string p (o, 2);
try
{
@@ -987,6 +797,7 @@ namespace build2
<< lflags () << "'" <<
info << "while parsing pkg-config --libs " << pc.path;
+ d.normalize ();
usrd->push_back (move (d));
}
catch (const invalid_path& e)
@@ -1017,7 +828,7 @@ namespace build2
dr << info (f) << "while resolving pkg-config dependency " << l;
});
- lt = search_library (a, top_sysd, usrd, pk);
+ lt = search_library (act, top_sysd, usrd, pk);
}
if (lt != nullptr)
@@ -1066,40 +877,32 @@ namespace build2
{
// Translate -L to /LIBPATH.
//
- for (auto i (lops.begin ()); i != lops.end (); )
+ for (string& o: lops)
{
- string& o (*i);
size_t n (o.size ());
- if (n >= 2 && o[0] == '-' && o[1] == 'L')
+ // Note: always in the -L<dir> form (see above).
+ //
+ 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 (t.vars.insert (c_export_loptions));
if (p.second)
- p.first.get () = move (lops);
+ p.first = move (lops);
}
// Set even if empty (export override).
//
{
- auto p (t.vars.insert (la ? c_export_imp_libs : c_export_libs));
+ auto p (t.vars.insert (la ? c_export_impl_libs : c_export_libs));
if (p.second)
- p.first.get () = move (libs);
+ p.first = move (libs);
}
};
@@ -1107,6 +910,10 @@ namespace build2
// may escape things even on non-Windows platforms, for example,
// spaces. So we use a slightly modified version of next_word().
//
+ // @@ TODO: handle quotes (e.g., empty values; see parse_metadata()).
+ // I wonder what we get here if something is quoted in the
+ // .pc file.
+ //
auto next = [] (const string& s, size_t& b, size_t& e) -> string
{
string r;
@@ -1142,15 +949,123 @@ namespace build2
return r;
};
- // Parse modules and add them to the prerequisites.
+ // Parse the build2.metadata variable value and, if user is true,
+ // extract the user metadata, if any, and set extracted variables on the
+ // specified target.
//
- auto parse_modules = [&trace, &next, &s, this]
- (const pkgconf& pc, prerequisites& ps)
+ auto parse_metadata = [&next] (target& t,
+ pkgconfig& pc,
+ const string& md,
+ bool user)
{
- string mstr (pc.variable ("cxx_modules"));
+ const location loc (pc.path);
+
+ context& ctx (t.ctx);
+
+ optional<uint64_t> ver;
+ optional<string> pfx;
+
+ variable_pool* vp (nullptr); // Resolve lazily.
+
+ string s;
+ for (size_t b (0), e (0); !(s = next (md, b, e)).empty (); )
+ {
+ if (!ver)
+ {
+ try
+ {
+ ver = value_traits<uint64_t>::convert (name (s), nullptr);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (loc) << "invalid version in build2.metadata variable: "
+ << e;
+ }
+
+ if (*ver != 1)
+ fail (loc) << "unexpected metadata version " << *ver;
+
+ if (!user)
+ return;
+
+ continue;
+ }
+
+ if (!pfx)
+ {
+ if (s.empty ())
+ fail (loc) << "empty variable prefix in build2.metadata varible";
+
+ pfx = s;
+ continue;
+ }
+
+ // The rest is variable name/type pairs.
+ //
+ size_t p (s.find ('/'));
+
+ if (p == string::npos)
+ fail (loc) << "expected name/type pair instead of '" << s << "'";
+
+ string vn (s, 0, p);
+ string tn (s, p + 1);
+
+ optional<string> val (pc.variable (vn));
+
+ if (!val)
+ fail (loc) << "metadata variable " << vn << " not set";
+
+ pair<const value_type*, bool> vt (metadata_type (tn));
+ if (vt.first == nullptr)
+ fail (loc) << "unknown metadata type " << tn;
+
+ names ns;
+ for (size_t b (0), e (0); !(s = next (*val, b, e)).empty (); )
+ {
+ ns.push_back (vt.second
+ ? name (dir_path (move (s)))
+ : name (move (s)));
+ }
+
+ // These should be public (qualified) variables so go straight for
+ // the public variable pool.
+ //
+ if (vp == nullptr)
+ vp = &ctx.var_pool.rw (); // Load phase if user==true.
+
+ const variable& var (vp->insert (move (vn)));
+
+ value& v (t.assign (var));
+ v.assign (move (ns), &var);
+ typify (v, *vt.first, &var);
+ }
+
+ if (!ver)
+ fail (loc) << "version expected in build2.metadata variable";
+
+ if (!pfx)
+ return; // No user metadata.
+
+ // Set export.metadata to indicate the presence of user metadata.
+ //
+ t.assign (ctx.var_export_metadata) = names {
+ name (std::to_string (*ver)), name (move (*pfx))};
+ };
+
+ // Parse modules, enter them as targets, and add them to the
+ // prerequisites.
+ //
+ auto parse_modules = [&trace, this,
+ &next, &s, &lt] (const pkgconfig& pc,
+ prerequisites& ps)
+ {
+ optional<string> val (pc.variable ("cxx.modules"));
+
+ if (!val)
+ return;
string m;
- for (size_t b (0), e (0); !(m = next (mstr, b, e)).empty (); )
+ for (size_t b (0), e (0); !(m = next (*val, b, e)).empty (); )
{
// The format is <name>=<path> with `..` used as a partition
// separator (see pkgconfig_save() for details).
@@ -1159,18 +1074,26 @@ namespace build2
if (p == string::npos ||
p == 0 || // Empty name.
p == m.size () - 1) // Empty path.
- fail << "invalid module information in '" << mstr << "'" <<
- info << "while parsing pkg-config --variable=cxx_modules "
+ fail << "invalid module information in '" << *val << "'" <<
+ info << "while parsing pkg-config --variable=cxx.modules "
<< pc.path;
string mn (m, 0, p);
path mp (m, p + 1, string::npos);
+
+ // Must be absolute but may not be normalized due to a relocatable
+ // .pc file. We assume there are no symlink shenanigans that would
+ // require realize().
+ //
+ if (!mp.normalized ())
+ mp.normalize ();
+
path mf (mp.leaf ());
// Extract module properties, if any.
//
- string pp (pc.variable ("cxx_module_preprocessed." + mn));
- string se (pc.variable ("cxx_module_symexport." + mn));
+ optional<string> pp (pc.variable ("cxx.module_preprocessed." + mn));
+ optional<string> se (pc.variable ("cxx.module_symexport." + mn));
// Replace the partition separator.
//
@@ -1189,16 +1112,16 @@ namespace build2
target_decl::implied,
trace));
- target& mt (tl.first);
+ file& mt (tl.first.as<file> ());
// If the target already exists, then setting its variables is not
// MT-safe. So currently we only do it if we have the lock (and thus
- // nobody can see this target yet) assuming that this has already
+ // nobody can see this target yet) verifying that this has already
// been done otherwise.
//
// @@ This is not quite correct, though: this target could already
// exist but for a "different purpose" (e.g., it could be used as
- // a header).
+ // a header). Well, maybe it shouldn't.
//
// @@ Could setting it in the rule-specific vars help? (But we
// are not matching a rule for it.) Note that we are setting
@@ -1207,41 +1130,107 @@ namespace build2
//
if (tl.second.owns_lock ())
{
+ mt.path (move (mp));
mt.vars.assign (c_module_name) = move (mn);
// Set module properties. Note that if unspecified we should still
// set them to their default values since the hosting project may
- // have them set to incompatible value.
+ // have them set to incompatible values.
//
{
value& v (mt.vars.assign (x_preprocessed)); // NULL
- if (!pp.empty ()) v = move (pp);
+ if (pp)
+ v = move (*pp);
}
{
- mt.vars.assign (x_symexport) = (se == "true");
+ mt.vars.assign (x_symexport) = (se && *se == "true");
}
tl.second.unlock ();
}
+ else
+ {
+ if (!mt.vars[c_module_name])
+ fail << "unexpected metadata for module target " << mt <<
+ info << "module is expected to have assigned name" <<
+ info << "make sure this module is used via " << lt
+ << " prerequisite";
+ }
ps.push_back (prerequisite (mt));
}
};
- // For now we only populate prerequisites for lib{}. To do it for
- // liba{} would require weeding out duplicates that are already in
- // lib{}.
- //
- // Currently, this information is only used by the modules machinery to
- // resolve module names to module files (but we cannot only do this if
- // modules are enabled since the same installed library can be used by
- // multiple builds).
+ // Parse importable headers, enter them as targets, and add them to
+ // the prerequisites.
//
- prerequisites prs;
+ auto parse_headers = [&trace, this,
+ &next, &s, &lt] (const pkgconfig& pc,
+ const target_type& tt,
+ const char* lang,
+ prerequisites& ps)
+ {
+ string var (string (lang) + ".importable_headers");
+ optional<string> val (pc.variable (var));
+
+ if (!val)
+ return;
+
+ string h;
+ for (size_t b (0), e (0); !(h = next (*val, b, e)).empty (); )
+ {
+ path hp (move (h));
+
+ // Must be absolute but may not be normalized due to a relocatable
+ // .pc file. We assume there are no symlink shenanigans that would
+ // require realize().
+ //
+ if (!hp.normalized ())
+ hp.normalize ();
+
+ path hf (hp.leaf ());
+
+ auto tl (
+ s.ctx.targets.insert_locked (
+ tt,
+ hp.directory (),
+ dir_path (),
+ hf.base ().string (),
+ hf.extension (),
+ target_decl::implied,
+ trace));
+
+ file& ht (tl.first.as<file> ());
+
+ // If the target already exists, then setting its variables is not
+ // MT-safe. So currently we only do it if we have the lock (and thus
+ // nobody can see this target yet) verifying that this has already
+ // been done otherwise.
+ //
+ if (tl.second.owns_lock ())
+ {
+ ht.path (move (hp));
+ ht.vars.assign (c_importable) = true;
+ tl.second.unlock ();
+ }
+ else
+ {
+ if (!cast_false<bool> (ht.vars[c_importable]))
+ fail << "unexpected metadata for existing header target " << ht <<
+ info << "header is expected to be marked importable" <<
+ info << "make sure this header is used via " << lt
+ << " prerequisite";
+ }
- pkgconf apc;
- pkgconf spc;
+ ps.push_back (prerequisite (ht));
+ }
+ };
+
+ // Load the information from the pkg-config files.
+ //
+ pkgconfig apc;
+ pkgconfig spc;
// Create the .pc files search directory list.
//
@@ -1249,9 +1238,16 @@ namespace build2
// Note that we rely on the "small function object" optimization here.
//
- auto add_pc_dir = [&pc_dirs] (dir_path&& d) -> bool
+ auto add_pc_dir = [&trace, &pc_dirs] (dir_path&& d) -> bool
{
- pc_dirs.emplace_back (move (d));
+ // Suppress duplicated.
+ //
+ if (find (pc_dirs.begin (), pc_dirs.end (), d) == pc_dirs.end ())
+ {
+ l6 ([&]{trace << "search path " << d;});
+ pc_dirs.emplace_back (move (d));
+ }
+
return false;
};
@@ -1261,18 +1257,115 @@ namespace build2
bool pa (at != nullptr && !ap.empty ());
if (pa || sp.empty ())
- apc = pkgconf (ap, pc_dirs, sys_lib_dirs, sys_inc_dirs);
+ apc = pkgconfig (ap, pc_dirs, sys_lib_dirs, sys_hdr_dirs);
bool ps (st != nullptr && !sp.empty ());
if (ps || ap.empty ())
- spc = pkgconf (sp, pc_dirs, sys_lib_dirs, sys_inc_dirs);
+ spc = pkgconfig (sp, pc_dirs, sys_lib_dirs, sys_hdr_dirs);
+
+ // Load the user metadata if we are in the load phase. Otherwise just
+ // determine if we have metadata.
+ //
+ // Note also that we are not failing here if the metadata was requested
+ // but not present (potentially only partially) letting the caller
+ // (i.e., the import machinery) verify that the export.metadata was set
+ // on the target being imported. This would also allow supporting
+ // optional metadata.
+ //
+ bool apc_meta (false);
+ bool spc_meta (false);
+ if (!act)
+ {
+ // We can only do it during the load phase.
+ //
+ assert (lt.ctx.phase == run_phase::load);
+
+ pkgconfig& ipc (ps ? spc : apc); // As below.
+
+ // Since it's not easy to say if things are the same, we load a copy
+ // into the group and each member, if any.
+ //
+ // @@ TODO: check if already loaded? Don't we have the same problem
+ // below with reloading the rest for lt? What if we passed NULL
+ // in this case (and I suppose another bool in metaonly)?
+ //
+ if (optional<string> md = ipc.variable ("build2.metadata"))
+ parse_metadata (lt, ipc, *md, true);
+
+ if (pa)
+ {
+ if (optional<string> md = apc.variable ("build2.metadata"))
+ {
+ parse_metadata (*at, apc, *md, true);
+ apc_meta = true;
+ }
+ }
+
+ if (ps)
+ {
+ if (optional<string> md = spc.variable ("build2.metadata"))
+ {
+ parse_metadata (*st, spc, *md, true);
+ spc_meta = true;
+ }
+ }
+
+ // If we only need metadata, then we are done.
+ //
+ if (at != nullptr && metaonly.first)
+ {
+ pa = false;
+ at = nullptr;
+ }
+
+ if (st != nullptr && metaonly.second)
+ {
+ ps = false;
+ st = nullptr;
+ }
+
+ if (at == nullptr && st == nullptr)
+ return;
+ }
+ else
+ {
+ if (pa)
+ {
+ if (optional<string> md = apc.variable ("build2.metadata"))
+ {
+ parse_metadata (*at, apc, *md, false);
+ apc_meta = true;
+ }
+ }
+
+ if (ps)
+ {
+ if (optional<string> md = spc.variable ("build2.metadata"))
+ {
+ parse_metadata (*st, spc, *md, false);
+ spc_meta = true;
+ }
+ }
+ }
// 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).
//
- pkgconf& ipc (ps ? spc : apc); // Interface package info.
+ pkgconfig& ipc (ps ? spc : apc); // Interface package info.
+ bool ipc_meta (ps ? spc_meta : apc_meta);
+
+ // For now we only populate prerequisites for lib{}. To do it for
+ // liba{} would require weeding out duplicates that are already in
+ // lib{}.
+ //
+ // Currently, this information is only used by the modules machinery to
+ // resolve module names to module files (but we cannot only do this if
+ // modules are enabled since the same installed library can be used by
+ // multiple builds).
+ //
+ prerequisites prs;
parse_libs (
lt,
@@ -1281,23 +1374,61 @@ namespace build2
false,
&prs);
+ const strings* apops (nullptr);
if (pa)
{
- parse_cflags (*at, apc, true);
+ apops = parse_cflags (*at, apc, true);
parse_libs (*at, at->path ().empty (), apc, true, nullptr);
}
+ const strings* spops (nullptr);
if (ps)
- parse_cflags (*st, spc, false);
+ spops = parse_cflags (*st, spc, false);
+
+ // Also set common poptions for the group. In particular, this makes
+ // sure $lib_poptions() in the "common interface" mode works for the
+ // installed libraries.
+ //
+ // Note that if there are no poptions set for either, then we cannot
+ // possibly have a common subset.
+ //
+ if (apops != nullptr || spops != nullptr)
+ parse_cflags (lt, ipc, false, apops, spops);
+
+ // @@ TODO: we can now load cc.type if there is metadata (but need to
+ // return this rather than set, see search_library() for
+ // details).
+
+ // Load the bin.whole flag (whole archive).
+ //
+ if (at != nullptr && (pa ? apc_meta : spc_meta))
+ {
+ // Note that if unspecified we leave it unset letting the consumer
+ // override it, if necessary (see the bin.lib lookup semantics for
+ // details).
+ //
+ if (optional<string> v = (pa ? apc : spc).variable ("bin.whole"))
+ {
+ at->vars.assign ("bin.whole") = (*v == "true");
+ }
+ }
// For now we assume static and shared variants export the same set of
- // modules. While technically possible, having a different set will most
- // likely lead to all sorts of complications (at least for installed
- // libraries) and life is short.
+ // modules/importable headers. While technically possible, having
+ // different sets will most likely lead to all sorts of complications
+ // (at least for installed libraries) and life is short.
//
- if (modules)
+ if (modules && ipc_meta)
+ {
parse_modules (ipc, prs);
+ // We treat headers outside of any project as C headers (see
+ // enter_header() for details).
+ //
+ parse_headers (ipc, h::static_type /* **x_hdrs */, x, prs);
+ parse_headers (ipc, h::static_type, "c", prs);
+ }
+
assert (!lt.has_prerequisites ());
if (!prs.empty ())
lt.prerequisites (move (prs));
@@ -1315,7 +1446,7 @@ namespace build2
}
bool common::
- pkgconfig_load (action,
+ pkgconfig_load (optional<action>,
const scope&,
lib&,
liba*,
@@ -1324,13 +1455,14 @@ namespace build2
const string&,
const dir_path&,
const dir_paths&,
- const dir_paths&) const
+ const dir_paths&,
+ pair<bool, bool>) const
{
return false;
}
void common::
- pkgconfig_load (action,
+ pkgconfig_load (optional<action>,
const scope&,
lib&,
liba*,
@@ -1338,7 +1470,8 @@ namespace build2
const pair<path, path>&,
const dir_path&,
const dir_paths&,
- const dir_paths&) const
+ const dir_paths&,
+ pair<bool, bool>) const
{
assert (false); // Should never be called.
}
@@ -1352,6 +1485,11 @@ namespace build2
// file must be generated based on the static library to get accurate
// Libs.private.
//
+ // The other things that we omit from the common variant are -l options
+ // for binless libraries (so that it's usable from other build systems) as
+ // well as metadata (which could become incomplete due the previous
+ // omissions; for example, importable headers metadata).
+ //
void link_rule::
pkgconfig_save (action a,
const file& l,
@@ -1371,26 +1509,147 @@ namespace build2
/* */ pcs::static_type)));
assert (t != nullptr);
+ const path& p (t->path ());
+
+ // If we are uninstalling, skip regenerating the file if it already
+ // exists (I think we could have skipped this even if it doesn't exist,
+ // but let's keep things close to the install case).
+ //
+ if (ctx.current_action ().outer_operation () == uninstall_id)
+ {
+ if (exists (p))
+ return;
+ }
+
// This is the lib{} group if we are generating the common file and the
// target itself otherwise.
//
- const file& g (common ? l.group->as<file> () : l);
+ const target& g (common ? *l.group : l);
// By default we assume things go into install.{include, lib}.
//
+ // If include.lib does not resolve, then assume this is update-for-
+ // install without actual install and remove the file if it exists.
+ //
+ // @@ Shouldn't we use target's install value rather than install.lib
+ // in case it gets installed into a custom location? I suppose one
+ // can now use cc.pkgconfig.lib to customize this.
+ //
using install::resolve_dir;
- dir_path idir (resolve_dir (g, cast<dir_path> (g["install.include"])));
- dir_path ldir (resolve_dir (g, cast<dir_path> (g["install.lib"])));
+ small_vector<dir_path, 1> ldirs;
- const path& p (t->path ());
+ if (const dir_paths* ds = cast_null<dir_paths> (g[c_pkgconfig_lib]))
+ {
+ for (const dir_path& d: *ds)
+ {
+ bool f (ldirs.empty ());
+
+ ldirs.push_back (resolve_dir (g, d, {}, !f /* fail_unknown */));
+
+ if (f && ldirs.back ().empty ())
+ break;
+ }
+ }
+ else
+ ldirs.push_back (resolve_dir (g,
+ cast<dir_path> (g["install.lib"]),
+ {},
+ false /* fail_unknown */));
+
+ if (!ldirs.empty () && ldirs.front ().empty ())
+ {
+ rmfile (ctx, p, 3 /* verbosity */);
+ return;
+ }
+
+ small_vector<dir_path, 1> idirs;
+
+ if (const dir_paths* ds = cast_null<dir_paths> (g[c_pkgconfig_include]))
+ {
+ for (const dir_path& d: *ds)
+ idirs.push_back (resolve_dir (g, d));
+ }
+ else
+ idirs.push_back (resolve_dir (g,
+ cast<dir_path> (g["install.include"])));
+ // Note that generation can take some time if we have a large number of
+ // prerequisite libraries.
+ //
if (verb >= 2)
text << "cat >" << p;
+ else if (verb)
+ print_diag ("pc", g, *t);
if (ctx.dry_run)
return;
+ // See if we should be generating a relocatable .pc file and if so get
+ // its installation location. The plan is to make all absolute paths
+ // that we write relative to this location and prefix them with the
+ // built-in ${pcfiledir} variable (which supported by everybody: the
+ // original pkg-config, pkgconf, and our libpkg-config library).
+ //
+ dir_path rel_base;
+ if (cast_false<bool> (rs["install.relocatable"]))
+ {
+ path f (install::resolve_file (*t));
+ if (!f.empty ()) // Shouldn't happen but who knows.
+ rel_base = f.directory ();
+ }
+
+ // Note: reloc_*path() expect absolute and normalized paths.
+ //
+ // Note also that reloc_path() can be used on dir_path to get the path
+ // without the trailing slash.
+ //
+ auto reloc_path = [&rel_base,
+ s = string ()] (const path& p,
+ const char* what) mutable
+ -> const string&
+ {
+ if (rel_base.empty ())
+ return p.string ();
+
+ try
+ {
+ s = p.relative (rel_base).string ();
+ }
+ catch (const invalid_path&)
+ {
+ fail << "unable to make " << what << " path " << p << " relative to "
+ << rel_base;
+ }
+
+ if (!s.empty ()) s.insert (0, 1, path_traits::directory_separator);
+ s.insert (0, "${pcfiledir}");
+ return s;
+ };
+
+ auto reloc_dir_path = [&rel_base,
+ s = string ()] (const dir_path& p,
+ const char* what) mutable
+ -> const string&
+ {
+ if (rel_base.empty ())
+ return (s = p.representation ());
+
+ try
+ {
+ s = p.relative (rel_base).representation ();
+ }
+ catch (const invalid_path&)
+ {
+ fail << "unable to make " << what << " path " << p << " relative to "
+ << rel_base;
+ }
+
+ if (!s.empty ()) s.insert (0, 1, path_traits::directory_separator);
+ s.insert (0, "${pcfiledir}");
+ return s;
+ };
+
auto_rmfile arm (p);
try
@@ -1408,6 +1667,20 @@ namespace build2
fail << "no version variable in project " << n <<
info << "while generating " << p;
+ // When comparing versions, pkg-config uses RPM semantics, which is
+ // basically comparing each all-digit/alpha fragments in order.
+ // This means, for example, a semver with a pre-release will be
+ // compared incorrectly (pre-release will be greater than the final
+ // version). We could detect if this project uses stdver and chop
+ // off any pre-release information (so, essentially only saving the
+ // major.minor.patch part). But that means such .pc files will
+ // contain inaccurate version information. And seeing that we don't
+ // recommend using pkg-config (rather primitive) package dependency
+ // support, having complete version information for documentation
+ // seems more important.
+ //
+ // @@ Maybe still makes sense to only save version.project_id?
+ //
const string& v (cast<string> (vl));
os << "Name: " << n << endl;
@@ -1432,13 +1705,12 @@ namespace build2
for (auto i (v->begin ()); i != v->end (); ++i)
{
const string& o (*i);
- size_t n (o.size ());
// Filter out -I (both -I<dir> and -I <dir> forms).
//
- if (n >= 2 && o[0] == '-' && o[1] == 'I')
+ if (o[0] == '-' && o[1] == 'I')
{
- if (n == 2)
+ if (o.size () == 2)
++i;
continue;
@@ -1449,9 +1721,9 @@ namespace build2
}
};
- // Given a library target, save its -l-style library name.
+ // Given a library target, return its -l-style library name.
//
- auto save_library_target = [&os, this] (const file& l)
+ auto save_library_target = [this] (const file& l) -> string
{
// If available (it may not, in case of import-installed libraris),
// use the .pc file name to derive the -l library name (in case of
@@ -1479,22 +1751,34 @@ namespace build2
}
else
{
- // Derive -l-name from the file name in a fuzzy, platform-specific
- // manner.
- //
- n = l.path ().leaf ().base ().string ();
+ const path& p (l.path ());
- if (cclass != compiler_class::msvc)
- strip_lib ();
+ if (p.empty ()) // Binless.
+ {
+ // For a binless library the target name is all it can possibly
+ // be.
+ //
+ n = l.name;
+ }
+ else
+ {
+ // Derive -l-name from the file name in a fuzzy, platform-
+ // specific manner.
+ //
+ n = p.leaf ().base ().string ();
+
+ if (cclass != compiler_class::msvc)
+ strip_lib ();
+ }
}
- os << " -l" << n;
+ return "-l" + n;
};
- // Given a (presumably) compiler-specific library name, save its
+ // Given a (presumably) compiler-specific library name, return its
// -l-style library name.
//
- auto save_library_name = [&os, this] (const string& n)
+ auto save_library_name = [this] (const string& n) -> string
{
if (tsys == "win32-msvc")
{
@@ -1504,23 +1788,20 @@ namespace build2
if (p != string::npos && icasecmp (n.c_str () + p + 1, "lib") == 0)
{
- os << " -l" << string (n, 0, p);
- return;
+ return "-l" + string (n, 0, p);
}
- // Fall through and save as is.
+ // Fall through and return as is.
}
- os << ' ' << n;
+ return n;
};
- // @@ TODO: support whole archive?
- //
-
// Cflags.
//
os << "Cflags:";
- os << " -I" << escape (idir.string ());
+ for (const dir_path& d: idirs)
+ os << " -I" << escape (reloc_path (d, "header search"));
save_poptions (x_export_poptions);
save_poptions (c_export_poptions);
os << endl;
@@ -1533,51 +1814,117 @@ namespace build2
// dependencies if we don't have the shared variant (see the load
// logic for details). And also for the common .pc file, naturally.
//
- //@@ TODO: would be nice to weed out duplicates. But is it always
- // safe? Think linking archives: will have to keep duplicates in
- // the second position, not first. Gets even trickier with the
- // Libs.private split.
- //
{
os << "Libs:";
// While we don't need it for a binless library itselt, it may be
// necessary to resolve its binful dependencies.
//
- os << " -L" << escape (ldir.string ());
+ for (const dir_path& d: ldirs)
+ os << " -L" << escape (reloc_path (d, "library search"));
// Now process ourselves as if we were being linked to something (so
- // pretty similar to link_rule::append_libraries()).
+ // pretty similar to link_rule::append_libraries()). We also reuse
+ // the link_rule's machinery to suppress duplicates.
//
+ appended_libraries ls;
+ strings args;
bool priv (false);
- auto imp = [&priv] (const file&, bool la) {return priv && la;};
- auto lib = [&save_library_target,
- &save_library_name] (const file* const* c,
- const string& p,
- lflags,
- bool)
+ struct data
+ {
+ ofdstream& os;
+ appended_libraries* pls; // Previous.
+ appended_libraries* ls; // Current.
+ strings& args;
+ bool common;
+ } d {os, nullptr, &ls, args, common};
+
+ auto imp = [&priv] (const target&, bool la) {return priv && la;};
+
+ auto lib = [&d, &save_library_target, &save_library_name] (
+ const target* const* lc,
+ const small_vector<reference_wrapper<const string>, 2>& ns,
+ lflags,
+ const string*,
+ bool)
{
- const file* l (c != nullptr ? *c : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
+
+ // Suppress duplicates from the previous run (Libs/Libs.private
+ // split).
+ //
+ if (d.pls != nullptr)
+ {
+ // Doesn't feel like we can prune here: we may have seen this
+ // interface library but not its implementation dependencies.
+ //
+ if ((l != nullptr
+ ? d.pls->find (*l)
+ : d.pls->find (ns)) != nullptr)
+ return true;
+ }
+
+ // Suppress duplicates (see append_libraries() for details).
+ //
+ // Note that we use the original name for duplicate tracking.
+ //
+ appended_library* al (l != nullptr
+ ? &d.ls->append (*l, d.args.size ())
+ : d.ls->append (ns, d.args.size ()));
+
+ if (al != nullptr && al->end != appended_library::npos)
+ {
+ d.ls->hoist (d.args, *al);
+ return true;
+ }
if (l != nullptr)
{
if (l->is_a<libs> () || l->is_a<liba> ()) // See through libux.
- save_library_target (*l);
+ {
+ // Omit binless libraries from the common .pc file (see
+ // above).
+ //
+ // Note that in this case we still want to recursively
+ // traverse such libraries since they may still link to some
+ // non-binless system libraries (-lm, etc).
+ //
+ if (!d.common || !l->path ().empty ())
+ d.args.push_back (save_library_target (*l));
+ }
}
else
- save_library_name (p); // Something "system'y", save as is.
+ {
+ // Something "system'y", save as is.
+ //
+ for (const string& n: ns)
+ d.args.push_back (save_library_name (n));
+ }
+
+ if (al != nullptr)
+ al->end = d.args.size (); // Close.
+
+ return true;
};
- auto opt = [] (const file&,
- const string&,
- bool, bool)
+ auto opt = [&d] (const target& lt, const string&, bool, bool)
{
+ const file& l (lt.as<file> ());
+
//@@ TODO: should we filter -L similar to -I?
//@@ TODO: how will the Libs/Libs.private work?
- //@@ TODO: remember to use escape()
+ //@@ TODO: remember to use reloc_*() and escape().
+
+ if (d.pls != nullptr && d.pls->find (l) != nullptr)
+ return true;
// See link_rule::append_libraries().
+
+ if (d.ls->append (l, d.args.size ()).end != appended_library::npos)
+ return true;
+
+ return true;
};
// Pretend we are linking an executable using what would be normal,
@@ -1585,89 +1932,461 @@ namespace build2
//
linfo li {otype::e, la ? lorder::a_s : lorder::s_a};
+ library_cache lib_cache;
process_libraries (a, bs, li, sys_lib_dirs,
l, la, 0, // Link flags.
- imp, lib, opt, !binless);
+ imp, lib, opt,
+ !binless /* self */,
+ false /* proc_opt_group */, // @@ !priv?
+ &lib_cache);
+
+ for (const string& a: args)
+ os << ' ' << a;
os << endl;
if (la)
{
os << "Libs.private:";
+ args.clear ();
priv = true;
+
+ // Use previous appended_libraries to weed out entries that are
+ // already in Libs.
+ //
+ appended_libraries als;
+ d.pls = d.ls;
+ d.ls = &als;
+
process_libraries (a, bs, li, sys_lib_dirs,
l, la, 0, // Link flags.
- imp, lib, opt, false);
+ imp, lib, opt,
+ false /* self */,
+ false /* proc_opt_group */, // @@ !priv?
+ &lib_cache);
+
+ for (const string& a: args)
+ os << ' ' << a;
+ os << endl;
+
+ // See also bin.whole below.
+ }
+ }
+
+ // Save metadata unless this is the common .pc file (see above).
+ //
+ if (common)
+ {
+ os.close ();
+ arm.cancel ();
+ return;
+ }
+
+ // The build2.metadata variable is a general indication of the
+ // metadata being present. Its value is the metadata version
+ // optionally followed by the user metadata variable prefix and
+ // variable list (see below for details). Having only the version
+ // indicates the absense of user metadata.
+ //
+ // See if we have the user metadata.
+ //
+ lookup um (g[ctx.var_export_metadata]); // Target visibility.
+
+ if (um && !um->empty ())
+ {
+ const names& ns (cast<names> (um));
+
+ // First verify the version.
+ //
+ uint64_t ver;
+ try
+ {
+ // Note: does not change the passed name.
+ //
+ ver = value_traits<uint64_t>::convert (
+ ns[0], ns[0].pair ? &ns[1] : nullptr);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid metadata version in library " << g << ": " << e
+ << endf;
+ }
+
+ if (ver != 1)
+ fail << "unexpected metadata version " << ver << " in library "
+ << g;
+
+ // Next verify the metadata variable prefix.
+ //
+ if (ns.size () != 2 || !ns[1].simple ())
+ fail << "invalid metadata variable prefix in library " << g;
+
+ const string& pfx (ns[1].value);
+
+ // Now find all the target-specific variables with this prefix.
+ //
+ // If this is the common .pc file, then we only look in the group.
+ // Otherwise, in the member and the group.
+ //
+ // To allow setting different values for the for-install and
+ // development build cases (required when a library comes with
+ // additional "assets"), we recognize the special .for_install
+ // variable name suffix: if there is a both <prefix>.<name> and
+ // <prefix>.<name>.for_install variables, then here we take the
+ // value from the latter. Note that we don't consider just
+ // <prefix>.for_install as special (so it's available to the user).
+ //
+ // We only expect a handful of variables so let's use a vector and
+ // linear search instead of a map.
+ //
+ struct binding
+ {
+ const string* name; // Name to be saved (without .for_install).
+ const variable* var; // Actual variable (potentially .for_install).
+ const value* val; // Actual value.
+ };
+ vector<binding> vars;
+
+ auto append = [&l, &pfx, &vars,
+ tmp = string ()] (const target& t, bool dup) mutable
+ {
+ for (auto p (t.vars.lookup_namespace (pfx));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable* var (&p.first->first.get ());
+
+ // Handle .for_install.
+ //
+ // The plan is as follows: if this is .for_install, then just
+ // verify we also have the value without the suffix and skip
+ // it. Otherwise, check if there also the .for_install variant
+ // and if so, use that instead. While we could probably do this
+ // more efficiently by remembering what we saw in vars, this is
+ // not performance-sensitive and so we keep it simple for now.
+ //
+ const string* name;
+ {
+ const string& v (var->name);
+ size_t n (v.size ());
+
+ if (n > pfx.size () + 1 + 12 && // <prefix>..for_install
+ v.compare (n - 12, 12, ".for_install") == 0)
+ {
+ tmp.assign (v, 0, n - 12);
+
+ if (t.vars.find (tmp) == t.vars.end ())
+ fail << v << " variant without " << tmp << " in library "
+ << l;
+
+ continue;
+ }
+ else
+ {
+ name = &v;
+
+ tmp = v; tmp += ".for_install";
+
+ auto i (t.vars.find (tmp));
+ if (i != t.vars.end ())
+ var = &i->first.get ();
+ }
+ }
+
+ if (dup)
+ {
+ if (find_if (vars.begin (), vars.end (),
+ [name] (const binding& p)
+ {
+ return *p.name == *name;
+ }) != vars.end ())
+ continue;
+ }
+
+ // Re-lookup the value in order to apply target type/pattern
+ // specific prepends/appends.
+ //
+ lookup l (t[*var]);
+ assert (l.defined ());
+
+ vars.push_back (binding {name, var, l.value});
+ }
+ };
+
+ append (g, false);
+
+ if (!common)
+ {
+ if (l.group != nullptr)
+ append (*l.group, true);
+ }
+
+ // First write the build2.metadata variable with the version,
+ // prefix, and all the variable names/types (which should not
+ // require any escaping).
+ //
+ os << endl
+ << "build2.metadata = " << ver << ' ' << pfx;
+
+ for (const binding& b: vars)
+ {
+ const variable& var (*b.var);
+ const value& val (*b.val);
+
+ // There is no notion of NULL in pkg-config variables and it's
+ // probably best not to conflate them with empty.
+ //
+ if (val.null)
+ fail << "null value in exported variable " << var
+ << " of library " << l;
+
+ if (val.type == nullptr)
+ fail << "untyped value in exported variable " << var
+ << " of library " << l;
+
+ // Tighten this to only a sensible subset of types (see
+ // parsing/serialization code for some of the potential problems).
+ //
+ if (!metadata_type (val.type->name).first)
+ fail << "unsupported value type " << val.type->name
+ << " in exported variable " << var << " of library " << l;
+
+ os << " \\" << endl
+ << *b.name << '/' << val.type->name;
+ }
+
+ os << endl
+ << endl;
+
+ // Now the variables themselves.
+ //
+ string s; // Reuse the buffer.
+ for (const binding& b: vars)
+ {
+ const variable& var (*b.var);
+ const value& val (*b.val);
+
+ names ns;
+ names_view nv (reverse (val, ns, true /* reduce */));
+
+ os << *b.name << " =";
+
+ auto append = [&rel_base,
+ &reloc_path,
+ &reloc_dir_path,
+ &l, &var, &val, &s] (const name& v)
+ {
+ // If this is absolute path or dir_path, then attempt to
+ // relocate. Without that the result will not be relocatable.
+ //
+ if (v.simple ())
+ {
+ path p;
+ if (!rel_base.empty () &&
+ val.type != nullptr &&
+ (val.type->is_a<path> () || val.type->is_a<paths> ()) &&
+ (p = path (v.value)).absolute ())
+ {
+ p.normalize ();
+ s += reloc_path (p, var.name.c_str ());
+ }
+ else
+ s += v.value;
+ }
+ else if (v.directory ())
+ {
+ if (!rel_base.empty () && v.dir.absolute ())
+ {
+ dir_path p (v.dir);
+ p.normalize ();
+ s += reloc_dir_path (p, var.name.c_str ());
+ }
+ else
+ s += v.dir.representation ();
+ }
+ else
+ // It seems like we shouldn't end up here due to the type
+ // check but let's keep it for good measure.
+ //
+ fail << "simple or directory value expected instead of '"
+ << v << "' in exported variable " << var << " of library "
+ << l;
+ };
+
+ for (auto i (nv.begin ()); i != nv.end (); ++i)
+ {
+ s.clear ();
+ append (*i);
+
+ if (i->pair)
+ {
+ // @@ What if the value contains the pair character? Maybe
+ // quote the halves in this case? Note: need to handle in
+ // parse_metadata() above if enable here. Note: none of the
+ // types currently allowed use pairs.
+#if 0
+ s += i->pair;
+ append (*++i);
+#else
+ fail << "pair in exported variable " << var << " of library "
+ << l;
+#endif
+ }
+
+ os << ' ' << escape (s);
+ }
+
os << endl;
}
}
+ else
+ {
+ // No user metadata.
+ //
+ os << endl
+ << "build2.metadata = 1" << endl;
+ }
- // If we have modules, list them in the modules variable. We also save
- // some extra info about them (yes, the rabbit hole runs deep). This
- // code is pretty similar to compiler::search_modules().
+ // Save cc.type (see init() for the format documentation).
+ //
+ // Note that this value is set by link_rule and therefore should
+ // be there.
+ //
+ {
+ const string& t (
+ cast<string> (
+ l.state[a].lookup_original (
+ c_type, true /* target_only */).first));
+
+ // If common, then only save the language (the rest could be
+ // static/shared-specific; strictly speaking even the language could
+ // be, but that seems far fetched).
+ //
+ os << endl
+ << "cc.type = " << (common ? string (t, 0, t.find (',')) : t)
+ << endl;
+ }
+
+ // Save the bin.whole (whole archive) flag (see the link rule for
+ // details on the lookup semantics).
+ //
+ if (la)
+ {
+ // Note: go straight for the public variable pool.
+ //
+ if (cast_false<bool> (l.lookup_original (
+ ctx.var_pool["bin.whole"],
+ true /* target_only */).first))
+ {
+ os << endl
+ << "bin.whole = true" << endl;
+ }
+ }
+
+ // If we have modules and/or importable headers, list them in the
+ // respective variables. We also save some extra info about modules
+ // (yes, the rabbit hole runs deep). This code is pretty similar to
+ // compiler::search_modules().
+ //
+ // Note that we want to convey the importable headers information even
+ // if modules are not enabled.
//
- if (modules)
{
struct module
{
string name;
path file;
- string pp;
+ string preprocessed;
bool symexport;
};
- vector<module> modules;
+ vector<module> mods;
+
+ // If we were to ever support another C-based language (e.g.,
+ // Objective-C) and libraries that can use a mix of languages (e.g.,
+ // C++ and Objective-C), then we would need to somehow reverse-
+ // lookup header target type to language. Let's hope we don't.
+ //
+ vector<path> x_hdrs;
+ vector<path> c_hdrs;
+ // We need to (recursively) see through libu*{}. See similar logic
+ // in search_modules().
+ //
// Note that the prerequisite targets are in the member, not the
- // group (for now we don't support different sets of modules for
- // static/shared library; see load above for details).
+ // group (for now we don't support different sets of modules/headers
+ // for static/shared library; see load above for details).
//
- for (const target* pt: l.prerequisite_targets[a])
+ auto collect = [a, this,
+ &mods,
+ &x_hdrs, &c_hdrs] (const target& l,
+ const auto& collect) -> void
{
- // @@ UTL: we need to (recursively) see through libu*{} (and
- // also in search_modules()).
- //
- if (pt != nullptr && pt->is_a<bmix> ())
+ for (const target* pt: l.prerequisite_targets[a])
{
- // What we have is a binary module interface. What we need is
- // a module interface source it was built from. We assume it's
- // the first mxx{} target that we see.
- //
- const target* mt (nullptr);
- for (const target* t: pt->prerequisite_targets[a])
+ if (pt == nullptr)
+ continue;
+
+ if (modules && pt->is_a<bmix> ())
{
- if ((mt = t->is_a (*x_mod)))
- break;
- }
+ // What we have is a binary module interface. What we need is
+ // a module interface source it was built from. We assume it's
+ // the first mxx{} target that we see.
+ //
+ const target* mt (nullptr);
+ for (const target* t: pt->prerequisite_targets[a])
+ {
+ if (t != nullptr && (mt = t->is_a (*x_mod)))
+ break;
+ }
- // Can/should there be a bmi{} without mxx{}? Can't think of a
- // reason.
- //
- assert (mt != nullptr);
+ // Can/should there be a bmi{} without mxx{}? Can't think of a
+ // reason.
+ //
+ assert (mt != nullptr);
- path p (install::resolve_file (mt->as<file> ()));
+ path p (install::resolve_file (mt->as<file> ()));
- if (p.empty ()) // Not installed.
- continue;
+ if (p.empty ()) // Not installed.
+ continue;
+
+ string pp;
+ if (const string* v = cast_null<string> ((*mt)[x_preprocessed]))
+ pp = *v;
+
+ mods.push_back (
+ module {
+ cast<string> (pt->state[a].vars[c_module_name]),
+ move (p),
+ move (pp),
+ symexport});
+ }
+ else if (pt->is_a (**this->x_hdrs) || pt->is_a<h> ())
+ {
+ if (cast_false<bool> ((*pt)[c_importable]))
+ {
+ path p (install::resolve_file (pt->as<file> ()));
- string pp;
- if (const string* v = cast_null<string> ((*mt)[x_preprocessed]))
- pp = *v;
-
- modules.push_back (
- module {
- cast<string> (pt->state[a].vars[c_module_name]),
- move (p),
- move (pp),
- symexport
- });
+ if (p.empty ()) // Not installed.
+ continue;
+
+ (pt->is_a<h> () ? c_hdrs : x_hdrs).push_back (move (p));
+ }
+ }
+ // Note that in prerequisite targets we will have the libux{}
+ // members, not the group.
+ //
+ else if (pt->is_a<libux> ())
+ collect (*pt, collect);
}
- }
+ };
- if (!modules.empty ())
+ collect (l, collect);
+
+ if (size_t n = mods.size ())
{
os << endl
- << "cxx_modules =";
+ << "cxx.modules =";
// The partition separator (`:`) is not a valid character in the
// variable name. In fact, from the pkg-config source we can see
@@ -1676,7 +2395,7 @@ namespace build2
// for example hello.print..impl. While in the variable values we
// can use `:`, for consistency we use `..` there as well.
//
- for (module& m: modules)
+ for (module& m: mods)
{
size_t p (m.name.find (':'));
if (p != string::npos)
@@ -1684,25 +2403,51 @@ namespace build2
// Module names shouldn't require escaping.
//
- os << ' ' << m.name << '=' << escape (m.file.string ());
+ os << (n != 1 ? " \\\n" : " ")
+ << m.name << '='
+ << escape (reloc_path (m.file, "module interface"));
}
os << endl;
// Module-specific properties. The format is:
//
- // <lang>_module_<property>.<module> = <value>
+ // <lang>.module_<property>.<module> = <value>
//
- for (const module& m: modules)
+ for (const module& m: mods)
{
- if (!m.pp.empty ())
- os << "cxx_module_preprocessed." << m.name << " = " << m.pp
- << endl;
+ if (!m.preprocessed.empty ())
+ os << "cxx.module_preprocessed." << m.name << " = "
+ << m.preprocessed << endl;
if (m.symexport)
- os << "cxx_module_symexport." << m.name << " = true" << endl;
+ os << "cxx.module_symexport." << m.name << " = true" << endl;
}
}
+
+ if (size_t n = c_hdrs.size ())
+ {
+ os << endl
+ << "c.importable_headers =";
+
+ for (const path& h: c_hdrs)
+ os << (n != 1 ? " \\\n" : " ")
+ << escape (reloc_path (h, "header unit"));
+
+ os << endl;
+ }
+
+ if (size_t n = x_hdrs.size ())
+ {
+ os << endl
+ << x << ".importable_headers =";
+
+ for (const path& h: x_hdrs)
+ os << (n != 1 ? " \\\n" : " ")
+ << escape (reloc_path (h, "header unit"));
+
+ os << endl;
+ }
}
os.close ();