From f0edc0e2b67fa43c4e2410c7d3d8f1841d576749 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 26 Aug 2016 16:37:16 +0200 Subject: Add pkg-config support for import installed Redesign library importing/exporting while at it. --- build2/cc/pkgconfig.cxx | 446 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 build2/cc/pkgconfig.cxx (limited to 'build2/cc/pkgconfig.cxx') diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx new file mode 100644 index 0000000..2122ea5 --- /dev/null +++ b/build2/cc/pkgconfig.cxx @@ -0,0 +1,446 @@ +// file : build2/cc/msvc.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + using namespace bin; + + // Try to find a .pc file in the pkgconfig/ subdirectory of libd, trying + // several names derived from stem. If not found, return false. If found, + // extract poptions, loptions, and libs, set the corresponding *.export.* + // variables on lib, and return true. + // + // System library search paths (those extracted from the compiler) are + // passed in sys_sp and should already be extracted. + // + // Note that scope and link order should be "top-level" from the + // search_library() POV. + // + bool link:: + pkgconfig_extract (scope& s, + file& lt, + const string* proj, + const string& stem, + const dir_path& libd, + optional& sys_sp, + lorder lo) const + { + tracer trace (x, "link::pkgconfig_extract"); + + assert (sys_sp); + assert (pkgconfig != nullptr); + + // Check if we have the pkgconfig/ subdirectory in this library's + // directory. + // + dir_path pkgd (dir_path (libd) /= "pkgconfig"); + + if (!dir_exists (pkgd)) + return false; + + // Now see if there is a corresponding .pc file. About half of them + // called foo.pc and half libfoo.pc (and one of the pkg-config's authors + // suggests that some of you should call yours foolib.pc, just to keep + // things interesting, you know). + // + // Given the (general) import in the form %lib{}, we will + // first try .pc, then lib.pc. Maybe it also makes sense to + // try .pc, just in case. Though, according to pkg-config docs, + // the .pc file should correspond to a library, not project. But then + // you get something like zlib which calls it zlib.pc. So let's just do + // it. + // + path f; + f = pkgd; + f /= stem; + f += ".pc"; + + if (!file_exists (f)) + { + f = pkgd; + f /= "lib"; + f += stem; + f += ".pc"; + + if (!file_exists (f)) + { + if (proj != nullptr) + { + f = pkgd; + f /= *proj; + f += ".pc"; + + if (!file_exists (f)) + return false; + } + else + return false; + } + } + + // Ok, we are in business. Time to run pkg-config. To keep things + // simple, we run it twice, first time for --cflag, then for --libs. + // + bool la (lt.is_a ()); + + const char* args[] = { + pkgconfig->initial, + nullptr, // --cflags/--libs + (la ? "--static" : f.string ().c_str ()), + (la ? f.string ().c_str () : nullptr), + nullptr + }; + + auto retline = [] (string& s) -> string {return move (s);}; + + args[1] = "--cflags"; + string cstr (run (*pkgconfig, args, retline)); + + args[1] = "--libs"; + string lstr (run (*pkgconfig, args, retline)); + + // On Windows pkg-config (at least the MSYS2 one which we are using) + // will escape backslahses in paths. In fact, it may escape things + // even on non-Windows platforms, for example, spaces. So we use a + // slightly modified version of next_word(). + // + auto next = [] (const string& s, size_t& b, size_t& e) -> string + { + string r; + size_t n (s.size ()); + + if (b != e) + b = e; + + // Skip leading delimiters. + // + for (; b != n && s[b] == ' '; ++b) ; + + if (b == n) + { + e = n; + return r; + } + + // Find first trailing delimiter while taking care of escapes. + // + r = s[b]; + for (e = b + 1; e != n && s[e] != ' '; ++e) + { + if (s[e] == '\\') + { + if (++e == n) + fail << "dangling escape in pkg-config output '" << s << "'"; + } + + r += s[e]; + } + + return r; + }; + + // Parse --cflags into poptions. + // + { + strings pops; + + bool arg (false); + string o; + for (size_t b (0), e (0); !(o = next (cstr, b, e)).empty (); ) + { + if (arg) + { + pops.push_back (move (o)); + arg = false; + continue; + } + + size_t n (o.size ()); + + // We only keep -I, -D and -U. + // + if (n >= 2 && + o[0] == '-' && + (o[1] == 'I' || o[1] == 'D' || o[1] == 'U')) + { + pops.push_back (move (o)); + arg = (n == 2); + continue; + } + + l4 ([&]{trace << "ignoring " << f << " --cflags option " << o;}); + } + + if (arg) + fail << "argument expected after " << pops.back () << + info << "while parsing pkg-config --cflags output of " << f; + + if (!pops.empty ()) + { + auto p (lt.vars.insert (c_export_poptions)); + + // The only way we could already have this value is if this same + // library was also imported as a project (as opposed to installed). + // Unlikely but possible. In this case the values were set by the + // export stub and we shouldn't touch them. + // + if (p.second) + p.first.get () = move (pops); + } + } + + // Parse --libs into loptions/libs. + // + { + strings lops; + names libs; + + // Normally we will have zero or more -L's followed by one or more + // -l's, with the first one being the library itself. But sometimes + // we may have other linker options, for example, -Wl,... or + // -pthread. It's probably a bad idea to ignore them. Also, + // theoretically, we could have just the library name/path. + // + // The tricky part, of course, is to know whether what follows after + // an option we don't recognize is its argument or another option or + // library. What we do at the moment is stop recognizing just + // library names (without -l) after seeing an unknown option. + // + + bool arg (false), first (true), known (true), have_L; + string o; + for (size_t b (0), e (0); !(o = next (lstr, b, e)).empty (); ) + { + if (arg) + { + // Can only be an argument for an loption. + // + lops.push_back (move (o)); + arg = false; + continue; + } + + size_t n (o.size ()); + + // See if this is -L. + // + if (n >= 2 && o[0] == '-' && o[1] == 'L') + { + have_L = true; + lops.push_back (move (o)); + arg = (n == 2); + continue; + } + + // See if that's -l or just the library name/path. + // + if ((known && o[0] != '-') || + (n > 2 && o[0] == '-' && o[1] == 'l')) + { + // First one is the library itself, which we skip. Note that we + // don't verify this and theoretically it could be some other + // library, but we haven't encountered such a beast yet. + // + if (first) + { + first = false; + continue; + } + + libs.push_back (name (move (o), false)); + continue; + } + + // Otherwise we assume it is some other loption. + // + known = false; + lops.push_back (move (o)); + } + + if (arg) + fail << "argument expected after " << lops.back () << + info << "while parsing pkg-config --libs output of " << f; + + if (first) + fail << "library expected in '" << lstr << "'" << + info << "while parsing pkg-config --libs output of " << f; + + // Resolve -lfoo into the library file path using our import installed + // machinery (i.e., we are going to call search_library() that will + // probably call us again, and so on). + // + // The reason we do it is the link order. For general libraries it + // shouldn't matter if we imported them via an export stub, direct + // import installed, or via a .pc file (which we could have generated + // from the export stub). The exception is "system libraries" (which + // are really the extension of libc) such as -lm, -ldl, -lpthread, + // etc. Those we will detect and leave as -l*. + // + // If we managed to resolve all the -l's (sans system), then we can + // omit -L's for nice and tidy command line. + // + bool all (true); + optional sp; // Populate lazily. + + for (name& n: libs) + { + string& l (n.value); + + // These ones are common/standard/POSIX. + // + if (l[0] != '-' || // e.g., shell32.lib + l == "-lm" || + l == "-ldl" || + l == "-lrt" || + l == "-lpthread") + continue; + + // Note: these list are most likely incomplete. + // + if (tclass == "linux") + { + // Some extras from libc (see libc6-dev) and other places. + // + if (l == "-lanl" || + l == "-lcrypt" || + l == "-lnsl" || + l == "-lresolv" || + l == "-lgcc") + continue; + } + else if (tclass == "macosx") + { + if (l == "-lSystem") + continue; + } + + // Prepare the search paths. + // + if (have_L && !sp) + { + sp = dir_paths (); + + // First enter the -L paths from the .pc file so that they take + // precedence. + // + for (auto i (lops.begin ()); i != lops.end (); ++i) + { + const string& o (*i); + + if (o.size () >= 2 && o[0] == '-' && o[1] == 'L') + { + string p; + + if (o.size () == 2) + p = *++i; // We've verified it's there. + else + p = string (o, 2); + + dir_path d (move (p)); + + if (d.relative ()) + fail << "relative -L directory in '" << lstr << "'" << + info << "while parsing pkg-config --libs output of " << f; + + sp->push_back (move (d)); + } + } + + // Then append system paths. + // + sp->insert (sp->end (), sys_sp->begin (), sys_sp->end ()); + } + + // @@ OUT: for now we assume out is undetermined, just like in + // link::resolve_library(). + // + dir_path out; + string name (l, 2); // Sans -l. + const string* ext (nullptr); + + prerequisite_key pk { + nullptr, {&lib::static_type, &out, &out, &name, ext}, &s}; + + if (lib* lt = static_cast ( + search_library (have_L ? sp : sys_sp, pk, lo))) + { + file& f (static_cast (link_member (*lt, lo))); + l = f.path ().string (); + } + else + // If we couldn't find the library, then leave it as -l. + // + all = false; + } + + // If all the -l's resolved and no other options, then drop all the + // -L's. If we have unknown options, then leave them in to be safe. + // + if (all && known) + lops.clear (); + + if (!lops.empty ()) + { + if (cid == "msvc") + { + // Translate -L to /LIBPATH. + // + for (auto i (lops.begin ()); i != lops.end (); ) + { + string& o (*i); + size_t n (o.size ()); + + if (n >= 2 && o[0] == '-' && o[1] == 'L') + { + o.replace (0, 2, "/LIBPATH:"); + + if (n == 2) + { + o += *++i; // We've verified it's there. + i = lops.erase (i); + continue; + } + } + + ++i; + } + } + + auto p (lt.vars.insert (c_export_loptions)); + + if (p.second) + p.first.get () = move (lops); + } + + if (!libs.empty ()) + { + auto p (lt.vars.insert (c_export_libs)); + + if (p.second) + p.first.get () = move (libs); + } + } + + return true; + } + } +} -- cgit v1.1