diff options
Diffstat (limited to 'libbuild2/cc/pkgconfig.cxx')
-rw-r--r-- | libbuild2/cc/pkgconfig.cxx | 1675 |
1 files changed, 1079 insertions, 596 deletions
diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index 617834e..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_hdr_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_hdr_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_hdr_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) + { + // 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 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. @@ -892,11 +666,15 @@ namespace build2 if (l[0] != '-') // e.g., just shell32.lib continue; else if (cmp ("advapi32") || + cmp ("authz") || cmp ("bcrypt") || + cmp ("comdlg32") || cmp ("crypt32") || - cmp ("dbgeng") || cmp ("d2d1") || cmp ("d3d", 3) || // d3d* + cmp ("dbgeng") || + cmp ("dbghelp") || + cmp ("dnsapi") || cmp ("dwmapi") || cmp ("dwrite") || cmp ("dxgi") || @@ -907,7 +685,9 @@ namespace build2 cmp ("imm32") || cmp ("iphlpapi") || cmp ("kernel32") || + cmp ("mincore") || cmp ("mpr") || + cmp ("msimg32") || cmp ("mswsock") || cmp ("msxml", 5) || // msxml* cmp ("netapi32") || @@ -916,9 +696,11 @@ namespace build2 cmp ("ole32") || cmp ("oleaut32") || cmp ("opengl32") || + cmp ("powrprof") || cmp ("psapi") || cmp ("rpcrt4") || cmp ("secur32") || + cmp ("setupapi") || cmp ("shell32") || cmp ("shlwapi") || cmp ("synchronization") || @@ -926,6 +708,8 @@ namespace build2 cmp ("userenv") || cmp ("uuid") || cmp ("version") || + cmp ("windowscodecs") || + cmp ("winhttp") || cmp ("winmm") || cmp ("winspool") || cmp ("ws2") || @@ -942,6 +726,11 @@ namespace build2 } continue; } + else if (tsys == "mingw32") + { + if (l == "-pthread") + continue; + } } else { @@ -951,6 +740,7 @@ namespace build2 l == "-lm" || l == "-ldl" || l == "-lrt" || + l == "-pthread" || l == "-lpthread") continue; @@ -969,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") @@ -986,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 { @@ -1008,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) @@ -1038,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) @@ -1087,24 +877,16 @@ 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; } } @@ -1128,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; @@ -1163,17 +949,123 @@ namespace build2 return r; }; + // 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_metadata = [&next] (target& t, + pkgconfig& pc, + const string& md, + bool user) + { + 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, <] (const pkgconf& pc, + &next, &s, <] (const pkgconfig& pc, prerequisites& ps) { - string val (pc.variable ("cxx_modules")); + optional<string> val (pc.variable ("cxx.modules")); + + if (!val) + return; string m; - for (size_t b (0), e (0); !(m = next (val, 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). @@ -1182,18 +1074,26 @@ namespace build2 if (p == string::npos || p == 0 || // Empty name. p == m.size () - 1) // Empty path. - fail << "invalid module information in '" << val << "'" << - 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. // @@ -1212,7 +1112,7 @@ 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 @@ -1230,6 +1130,7 @@ 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 @@ -1238,11 +1139,12 @@ namespace build2 // { 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 (); @@ -1264,18 +1166,29 @@ namespace build2 // the prerequisites. // auto parse_headers = [&trace, this, - &next, &s, <] (const pkgconf& pc, + &next, &s, <] (const pkgconfig& pc, const target_type& tt, const char* lang, prerequisites& ps) { - string var (string (lang) + "_importable_headers"); - string val (pc.variable (var)); + 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 (); ) + 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 ( @@ -1288,7 +1201,7 @@ namespace build2 target_decl::implied, trace)); - target& ht (tl.first); + 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 @@ -1297,6 +1210,7 @@ namespace build2 // if (tl.second.owns_lock ()) { + ht.path (move (hp)); ht.vars.assign (c_importable) = true; tl.second.unlock (); } @@ -1313,19 +1227,10 @@ namespace build2 } }; - // For now we only populate prerequisites for lib{}. To do it for - // liba{} would require weeding out duplicates that are already in - // lib{}. + // Load the information from the pkg-config files. // - // 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; - - pkgconf apc; - pkgconf spc; + pkgconfig apc; + pkgconfig spc; // Create the .pc files search directory list. // @@ -1333,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; }; @@ -1345,18 +1257,115 @@ namespace build2 bool pa (at != nullptr && !ap.empty ()); if (pa || sp.empty ()) - apc = pkgconf (ap, pc_dirs, sys_lib_dirs, sys_hdr_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_hdr_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, @@ -1365,28 +1374,58 @@ 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/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_hdr */, x, prs); + parse_headers (ipc, h::static_type /* **x_hdrs */, x, prs); parse_headers (ipc, h::static_type, "c", prs); } @@ -1407,7 +1446,7 @@ namespace build2 } bool common:: - pkgconfig_load (action, + pkgconfig_load (optional<action>, const scope&, lib&, liba*, @@ -1416,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*, @@ -1430,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. } @@ -1444,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, @@ -1478,7 +1524,7 @@ namespace build2 // 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}. // @@ -1486,32 +1532,124 @@ namespace build2 // 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? + // 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 ldir (resolve_dir (g, - cast<dir_path> (g["install.lib"]), - false /* fail_unknown */)); - if (ldir.empty ()) + small_vector<dir_path, 1> ldirs; + + 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; } - dir_path idir (resolve_dir (g, cast<dir_path> (g["install.include"]))); + 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) - text << "pc " << *t; - else if (verb >= 2) + 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 @@ -1529,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; @@ -1645,13 +1797,11 @@ namespace build2 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; @@ -1670,7 +1820,8 @@ namespace build2 // 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()). We also reuse @@ -1686,7 +1837,8 @@ namespace build2 appended_libraries* pls; // Previous. appended_libraries* ls; // Current. strings& args; - } d {os, nullptr, &ls, args}; + bool common; + } d {os, nullptr, &ls, args, common}; auto imp = [&priv] (const target&, bool la) {return priv && la;}; @@ -1730,7 +1882,17 @@ namespace build2 if (l != nullptr) { if (l->is_a<libs> () || l->is_a<liba> ()) // See through libux. - d.args.push_back (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 { @@ -1752,7 +1914,7 @@ namespace build2 //@@ 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; @@ -1773,7 +1935,10 @@ namespace build2 library_cache lib_cache; process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. - imp, lib, opt, !binless /* self */, &lib_cache); + imp, lib, opt, + !binless /* self */, + false /* proc_opt_group */, // @@ !priv? + &lib_cache); for (const string& a: args) os << ' ' << a; @@ -1795,11 +1960,326 @@ namespace build2 process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. - imp, lib, opt, false /* self */, &lib_cache); + 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; + } + + // 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; } } @@ -1881,7 +2361,7 @@ namespace build2 move (pp), symexport}); } - else if (pt->is_a (**x_hdr) || pt->is_a<h> ()) + else if (pt->is_a (**this->x_hdrs) || pt->is_a<h> ()) { if (cast_false<bool> ((*pt)[c_importable])) { @@ -1906,7 +2386,7 @@ namespace build2 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 @@ -1924,33 +2404,35 @@ namespace build2 // Module names shouldn't require escaping. // os << (n != 1 ? " \\\n" : " ") - << m.name << '=' << escape (m.file.string ()); + << 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: mods) { if (!m.preprocessed.empty ()) - os << "cxx_module_preprocessed." << m.name << " = " + 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 ="; + << "c.importable_headers ="; for (const path& h: c_hdrs) - os << (n != 1 ? " \\\n" : " ") << escape (h.string ()); + os << (n != 1 ? " \\\n" : " ") + << escape (reloc_path (h, "header unit")); os << endl; } @@ -1958,10 +2440,11 @@ namespace build2 if (size_t n = x_hdrs.size ()) { os << endl - << x << "_importable_headers ="; + << x << ".importable_headers ="; for (const path& h: x_hdrs) - os << (n != 1 ? " \\\n" : " ") << escape (h.string ()); + os << (n != 1 ? " \\\n" : " ") + << escape (reloc_path (h, "header unit")); os << endl; } |