// file : libbuild2/cc/pkgconfig-libpkgconf.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #ifndef BUILD2_BOOTSTRAP #include <libbuild2/cc/pkgconfig.hxx> #include <libbuild2/diagnostics.hxx> // Note that the libpkgconf library did not used to 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 ()); } namespace build2 { namespace cc { // The libpkgconf library is not thread-safe, even on the pkgconf_client_t // level (see issue #128 for details). While it seems that the obvious // thread-safety issues are fixed, the default personality initialization, // which is still not thread-safe. So let's keep the mutex for now not to // introduce potential issues. // 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 | PKGCONF_PKG_PKGF_SKIP_PROVIDES #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 (trailing dot). 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". // pkgconfig:: pkgconfig (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 (); } void pkgconfig:: free () { assert (pkg_ != nullptr); mlock l (pkgconf_mutex); pkgconf_pkg_unref (client_, pkg_); pkgconf_client_free (client_); } strings pkgconfig:: 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 pkgconfig:: 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); } optional<string> pkgconfig:: 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 ? optional<string> (r) : nullopt; } } } #endif // BUILD2_BOOTSTRAP