aboutsummaryrefslogtreecommitdiff
path: root/build2/cc/pkgconfig.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-08-26 16:37:16 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-08-26 16:37:16 +0200
commitf0edc0e2b67fa43c4e2410c7d3d8f1841d576749 (patch)
tree25ca1dd8c20649a1a9a35355670a1f5c8b99695c /build2/cc/pkgconfig.cxx
parente347540d400c552917ca7067c4571f1028f6e1af (diff)
Add pkg-config support for import installed
Redesign library importing/exporting while at it.
Diffstat (limited to 'build2/cc/pkgconfig.cxx')
-rw-r--r--build2/cc/pkgconfig.cxx446
1 files changed, 446 insertions, 0 deletions
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 <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/variable>
+#include <build2/filesystem>
+#include <build2/diagnostics>
+
+#include <build2/bin/target>
+
+#include <build2/cc/link>
+#include <build2/cc/types>
+#include <build2/cc/utility>
+
+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<dir_paths>& 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 <proj>%lib{<stem>}, we will
+ // first try <stem>.pc, then lib<stem>.pc. Maybe it also makes sense to
+ // try <proj>.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<liba> ());
+
+ 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<string> (*pkgconfig, args, retline));
+
+ args[1] = "--libs";
+ string lstr (run<string> (*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<dir_paths> 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<lib*> (
+ search_library (have_L ? sp : sys_sp, pk, lo)))
+ {
+ file& f (static_cast<file&> (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;
+ }
+ }
+}