aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-09-12 01:51:17 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-09-12 01:51:17 +0300
commit53a76972f56688f4e0c7889e295d87b91ae85bc5 (patch)
tree479eae73397f1204bf0761a1012334f2343b42d9
parent4beec8f055fd3b0cc4ef618cce8b52c58dd0ee08 (diff)
Fix leaks and package not caching
-rw-r--r--libpkgconf/client.c6
-rw-r--r--libpkgconf/client.c.orig554
-rw-r--r--libpkgconf/pkg.c13
-rw-r--r--libpkgconf/pkg.c.orig1588
4 files changed, 2161 insertions, 0 deletions
diff --git a/libpkgconf/client.c b/libpkgconf/client.c
index 044a589..cda1c75 100644
--- a/libpkgconf/client.c
+++ b/libpkgconf/client.c
@@ -140,6 +140,12 @@ pkgconf_client_deinit(pkgconf_client_t *client)
pkgconf_tuple_free_global(client);
pkgconf_path_free(&client->dir_list);
pkgconf_cache_free(client);
+
+ // Added to the original leaking code (reported issue #130).
+ //
+ pkgconf_path_free(&client->filter_libdirs);
+ pkgconf_path_free(&client->filter_includedirs);
+
}
/*
diff --git a/libpkgconf/client.c.orig b/libpkgconf/client.c.orig
new file mode 100644
index 0000000..1203ffe
--- /dev/null
+++ b/libpkgconf/client.c.orig
@@ -0,0 +1,554 @@
+/*
+ * client.c
+ * libpkgconf consumer lifecycle management
+ *
+ * Copyright (c) 2016 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/config.h>
+/*
+ * !doc
+ *
+ * libpkgconf `client` module
+ * ==========================
+ *
+ * The libpkgconf `client` module implements the `pkgconf_client_t` "client" object.
+ * Client objects store all necessary state for libpkgconf allowing for multiple instances to run
+ * in parallel.
+ *
+ * Client objects are not thread safe, in other words, a client object should not be shared across
+ * thread boundaries.
+ */
+
+static void
+trace_path_list(const pkgconf_client_t *client, const char *desc, pkgconf_list_t *list)
+{
+ const pkgconf_node_t *n;
+
+ PKGCONF_TRACE(client, "%s:", desc);
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, n)
+ {
+ const pkgconf_path_t *p = n->data;
+
+ PKGCONF_TRACE(client, " - '%s'", p->path);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler)
+ *
+ * Initialise a pkgconf client object.
+ *
+ * :param pkgconf_client_t* client: The client to initialise.
+ * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors.
+ * :param void* error_handler_data: user data passed to optional error handler
+ * :return: nothing
+ */
+void
+pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data)
+{
+ client->error_handler_data = error_handler_data;
+ client->error_handler = error_handler;
+ client->auditf = NULL;
+
+ if (client->trace_handler == NULL)
+ pkgconf_client_set_trace_handler(client, NULL, NULL);
+
+ pkgconf_client_set_error_handler(client, error_handler, error_handler_data);
+ pkgconf_client_set_warn_handler(client, NULL, NULL);
+
+ pkgconf_client_set_sysroot_dir(client, NULL);
+ pkgconf_client_set_buildroot_dir(client, NULL);
+ pkgconf_client_set_prefix_varname(client, NULL);
+
+ pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_LIBRARY_PATH", SYSTEM_LIBDIR, &client->filter_libdirs, false);
+ pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_INCLUDE_PATH", SYSTEM_INCLUDEDIR, &client->filter_includedirs, false);
+
+ /* GCC uses these environment variables to define system include paths, so we should check them. */
+ pkgconf_path_build_from_environ("LIBRARY_PATH", NULL, &client->filter_libdirs, false);
+ pkgconf_path_build_from_environ("CPATH", NULL, &client->filter_includedirs, false);
+ pkgconf_path_build_from_environ("C_INCLUDE_PATH", NULL, &client->filter_includedirs, false);
+ pkgconf_path_build_from_environ("CPLUS_INCLUDE_PATH", NULL, &client->filter_includedirs, false);
+ pkgconf_path_build_from_environ("OBJC_INCLUDE_PATH", NULL, &client->filter_includedirs, false);
+
+#ifdef _WIN32
+ /* also use the path lists that MSVC uses on windows */
+ pkgconf_path_build_from_environ("INCLUDE", NULL, &client->filter_includedirs, false);
+#endif
+
+ PKGCONF_TRACE(client, "initialized client @%p", client);
+
+ trace_path_list(client, "filtered library paths", &client->filter_libdirs);
+ trace_path_list(client, "filtered include paths", &client->filter_includedirs);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_t* pkgconf_client_new(pkgconf_error_handler_func_t error_handler)
+ *
+ * Allocate and initialise a pkgconf client object.
+ *
+ * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors.
+ * :param void* error_handler_data: user data passed to optional error handler
+ * :return: A pkgconf client object.
+ * :rtype: pkgconf_client_t*
+ */
+pkgconf_client_t *
+pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data)
+{
+ pkgconf_client_t *out = calloc(sizeof(pkgconf_client_t), 1);
+ pkgconf_client_init(out, error_handler, error_handler_data);
+ return out;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_deinit(pkgconf_client_t *client)
+ *
+ * Release resources belonging to a pkgconf client object.
+ *
+ * :param pkgconf_client_t* client: The client to deinitialise.
+ * :return: nothing
+ */
+void
+pkgconf_client_deinit(pkgconf_client_t *client)
+{
+ PKGCONF_TRACE(client, "deinit @%p", client);
+
+ if (client->prefix_varname != NULL)
+ free(client->prefix_varname);
+
+ if (client->sysroot_dir != NULL)
+ free(client->sysroot_dir);
+
+ if (client->buildroot_dir != NULL)
+ free(client->buildroot_dir);
+
+ pkgconf_tuple_free_global(client);
+ pkgconf_path_free(&client->dir_list);
+ pkgconf_cache_free(client);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_free(pkgconf_client_t *client)
+ *
+ * Release resources belonging to a pkgconf client object and then free the client object itself.
+ *
+ * :param pkgconf_client_t* client: The client to deinitialise and free.
+ * :return: nothing
+ */
+void
+pkgconf_client_free(pkgconf_client_t *client)
+{
+ pkgconf_client_deinit(client);
+ free(client);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client)
+ *
+ * Retrieves the client's sysroot directory (if any).
+ *
+ * :param pkgconf_client_t* client: The client object being accessed.
+ * :return: A string containing the sysroot directory or NULL.
+ * :rtype: const char *
+ */
+const char *
+pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client)
+{
+ return client->sysroot_dir;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir)
+ *
+ * Sets or clears the sysroot directory on a client object. Any previous sysroot directory setting is
+ * automatically released if one was previously set.
+ *
+ * Additionally, the global tuple ``$(pc_sysrootdir)`` is set as appropriate based on the new setting.
+ *
+ * :param pkgconf_client_t* client: The client object being modified.
+ * :param char* sysroot_dir: The sysroot directory to set or NULL to unset.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir)
+{
+ if (client->sysroot_dir != NULL)
+ free(client->sysroot_dir);
+
+ client->sysroot_dir = sysroot_dir != NULL ? strdup(sysroot_dir) : NULL;
+
+ PKGCONF_TRACE(client, "set sysroot_dir to: %s", client->sysroot_dir != NULL ? client->sysroot_dir : "<default>");
+
+ pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : "/");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client)
+ *
+ * Retrieves the client's buildroot directory (if any).
+ *
+ * :param pkgconf_client_t* client: The client object being accessed.
+ * :return: A string containing the buildroot directory or NULL.
+ * :rtype: const char *
+ */
+const char *
+pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client)
+{
+ return client->buildroot_dir;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir)
+ *
+ * Sets or clears the buildroot directory on a client object. Any previous buildroot directory setting is
+ * automatically released if one was previously set.
+ *
+ * Additionally, the global tuple ``$(pc_top_builddir)`` is set as appropriate based on the new setting.
+ *
+ * :param pkgconf_client_t* client: The client object being modified.
+ * :param char* buildroot_dir: The buildroot directory to set or NULL to unset.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir)
+{
+ if (client->buildroot_dir != NULL)
+ free(client->buildroot_dir);
+
+ client->buildroot_dir = buildroot_dir != NULL ? strdup(buildroot_dir) : NULL;
+
+ PKGCONF_TRACE(client, "set buildroot_dir to: %s", client->buildroot_dir != NULL ? client->buildroot_dir : "<default>");
+
+ pkgconf_tuple_add_global(client, "pc_top_builddir", client->buildroot_dir != NULL ? client->buildroot_dir : "$(top_builddir)");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...)
+ *
+ * Report an error to a client-registered error handler.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to report the error to.
+ * :param char* format: A printf-style format string to use for formatting the error message.
+ * :return: true if the error handler processed the message, else false.
+ * :rtype: bool
+ */
+bool
+pkgconf_error(const pkgconf_client_t *client, const char *format, ...)
+{
+ char errbuf[PKGCONF_BUFSIZE];
+ va_list va;
+
+ va_start(va, format);
+ vsnprintf(errbuf, sizeof errbuf, format, va);
+ va_end(va);
+
+ return client->error_handler(errbuf, client, client->error_handler_data);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...)
+ *
+ * Report an error to a client-registered warn handler.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to report the error to.
+ * :param char* format: A printf-style format string to use for formatting the warning message.
+ * :return: true if the warn handler processed the message, else false.
+ * :rtype: bool
+ */
+bool
+pkgconf_warn(const pkgconf_client_t *client, const char *format, ...)
+{
+ char errbuf[PKGCONF_BUFSIZE];
+ va_list va;
+
+ va_start(va, format);
+ vsnprintf(errbuf, sizeof errbuf, format, va);
+ va_end(va);
+
+ return client->warn_handler(errbuf, client, client->warn_handler_data);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t len, const char *funcname, const char *format, ...)
+ *
+ * Report a message to a client-registered trace handler.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to report the trace message to.
+ * :param char* filename: The file the function is in.
+ * :param size_t lineno: The line number currently being executed.
+ * :param char* funcname: The function name to use.
+ * :param char* format: A printf-style format string to use for formatting the trace message.
+ * :return: true if the trace handler processed the message, else false.
+ * :rtype: bool
+ */
+bool
+pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...)
+{
+ char errbuf[PKGCONF_BUFSIZE];
+ size_t len;
+ va_list va;
+
+ len = snprintf(errbuf, sizeof errbuf, "%s:%zu [%s]: ", filename, lineno, funcname);
+
+ va_start(va, format);
+ vsnprintf(errbuf + len, sizeof(errbuf) - len, format, va);
+ va_end(va);
+
+ pkgconf_strlcat(errbuf, "\n", sizeof errbuf);
+
+ return client->trace_handler(errbuf, client, client->trace_handler_data);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, const void *data)
+ *
+ * The default pkgconf error handler.
+ *
+ * :param char* msg: The error message to handle.
+ * :param pkgconf_client_t* client: The client object the error originated from.
+ * :param void* data: An opaque pointer to extra data associated with the client for error handling.
+ * :return: true (the function does nothing to process the message)
+ * :rtype: bool
+ */
+bool
+pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, const void *data)
+{
+ (void) msg;
+ (void) client;
+ (void) data;
+
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: unsigned int pkgconf_client_get_flags(const pkgconf_client_t *client)
+ *
+ * Retrieves resolver-specific flags associated with a client object.
+ *
+ * :param pkgconf_client_t* client: The client object to retrieve the resolver-specific flags from.
+ * :return: a bitfield of resolver-specific flags
+ * :rtype: uint
+ */
+unsigned int
+pkgconf_client_get_flags(const pkgconf_client_t *client)
+{
+ return client->flags;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags)
+ *
+ * Sets resolver-specific flags associated with a client object.
+ *
+ * :param pkgconf_client_t* client: The client object to set the resolver-specific flags on.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags)
+{
+ client->flags = flags;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_client_get_prefix_varname(const pkgconf_client_t *client)
+ *
+ * Retrieves the name of the variable that should contain a module's prefix.
+ * In some cases, it is necessary to override this variable to allow proper path relocation.
+ *
+ * :param pkgconf_client_t* client: The client object to retrieve the prefix variable name from.
+ * :return: the prefix variable name as a string
+ * :rtype: const char *
+ */
+const char *
+pkgconf_client_get_prefix_varname(const pkgconf_client_t *client)
+{
+ return client->prefix_varname;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname)
+ *
+ * Sets the name of the variable that should contain a module's prefix.
+ * If the variable name is ``NULL``, then the default variable name (``prefix``) is used.
+ *
+ * :param pkgconf_client_t* client: The client object to set the prefix variable name on.
+ * :param char* prefix_varname: The prefix variable name to set.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname)
+{
+ if (prefix_varname == NULL)
+ prefix_varname = "prefix";
+
+ if (client->prefix_varname != NULL)
+ free(client->prefix_varname);
+
+ client->prefix_varname = strdup(prefix_varname);
+
+ PKGCONF_TRACE(client, "set prefix_varname to: %s", client->prefix_varname);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_get_warn_handler(const pkgconf_client_t *client)
+ *
+ * Returns the warning handler if one is set, else ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to get the warn handler from.
+ * :return: a function pointer to the warn handler or ``NULL``
+ */
+pkgconf_error_handler_func_t
+pkgconf_client_get_warn_handler(const pkgconf_client_t *client)
+{
+ return client->warn_handler;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data)
+ *
+ * Sets a warn handler on a client object or uninstalls one if set to ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to set the warn handler on.
+ * :param pkgconf_error_handler_func_t warn_handler: The warn handler to set.
+ * :param void* warn_handler_data: Optional data to associate with the warn handler.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data)
+{
+ client->warn_handler = warn_handler;
+ client->warn_handler_data = warn_handler_data;
+
+ if (client->warn_handler == NULL)
+ {
+ PKGCONF_TRACE(client, "installing default warn handler");
+ client->warn_handler = pkgconf_default_error_handler;
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_get_error_handler(const pkgconf_client_t *client)
+ *
+ * Returns the error handler if one is set, else ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to get the error handler from.
+ * :return: a function pointer to the error handler or ``NULL``
+ */
+pkgconf_error_handler_func_t
+pkgconf_client_get_error_handler(const pkgconf_client_t *client)
+{
+ return client->error_handler;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data)
+ *
+ * Sets a warn handler on a client object or uninstalls one if set to ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to set the error handler on.
+ * :param pkgconf_error_handler_func_t error_handler: The error handler to set.
+ * :param void* error_handler_data: Optional data to associate with the error handler.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data)
+{
+ client->error_handler = error_handler;
+ client->error_handler_data = error_handler_data;
+
+ if (client->error_handler == NULL)
+ {
+ PKGCONF_TRACE(client, "installing default error handler");
+ client->error_handler = pkgconf_default_error_handler;
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_get_trace_handler(const pkgconf_client_t *client)
+ *
+ * Returns the error handler if one is set, else ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to get the error handler from.
+ * :return: a function pointer to the error handler or ``NULL``
+ */
+pkgconf_error_handler_func_t
+pkgconf_client_get_trace_handler(const pkgconf_client_t *client)
+{
+ return client->trace_handler;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data)
+ *
+ * Sets a warn handler on a client object or uninstalls one if set to ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The client object to set the error handler on.
+ * :param pkgconf_error_handler_func_t trace_handler: The error handler to set.
+ * :param void* trace_handler_data: Optional data to associate with the error handler.
+ * :return: nothing
+ */
+void
+pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data)
+{
+ client->trace_handler = trace_handler;
+ client->trace_handler_data = trace_handler_data;
+
+ if (client->trace_handler == NULL)
+ {
+ client->trace_handler = pkgconf_default_error_handler;
+ PKGCONF_TRACE(client, "installing default trace handler");
+ }
+}
diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c
index bcfb486..59957ff 100644
--- a/libpkgconf/pkg.c
+++ b/libpkgconf/pkg.c
@@ -16,6 +16,8 @@
#include <libpkgconf/config.h>
#include <libpkgconf/libpkgconf.h>
+#include <assert.h>
+
/*
* !doc
*
@@ -407,6 +409,13 @@ pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *filename, FILE *
void
pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
{
+ // The function leaks (issue #132 is reported). The whole concept of "static"
+ // packages is quite murky, so it's better just not to use it, at least
+ // until fixed by the library owners. In particular don't use
+ // pkgconf_queue_* functions.
+ //
+ assert (pkg == NULL || (pkg->flags & PKGCONF_PKG_PROPF_STATIC) == 0);
+
if (pkg == NULL || pkg->flags & PKGCONF_PKG_PROPF_STATIC)
return;
@@ -672,6 +681,10 @@ pkgconf_pkg_find(pkgconf_client_t *client, const char *name)
if (pkg != NULL)
{
pkgconf_path_add(pkg_get_parent_dir(pkg), &client->dir_list, true);
+
+ // Add the package to the cache (issue #133 is reported).
+ //
+ pkgconf_cache_add(client, pkg);
return pkg;
}
}
diff --git a/libpkgconf/pkg.c.orig b/libpkgconf/pkg.c.orig
new file mode 100644
index 0000000..27bc3c7
--- /dev/null
+++ b/libpkgconf/pkg.c.orig
@@ -0,0 +1,1588 @@
+/*
+ * pkg.c
+ * higher-level dependency graph compilation, management and manipulation
+ *
+ * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS).
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * This software is provided 'as is' and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising
+ * from the use of this software.
+ */
+
+#include <libpkgconf/config.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `pkg` module
+ * =======================
+ *
+ * The `pkg` module provides dependency resolution services and the overall `.pc` file parsing
+ * routines.
+ */
+
+#ifdef _WIN32
+# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
+# undef PKG_DEFAULT_PATH
+# define PKG_DEFAULT_PATH "../lib/pkgconfig;../share/pkgconfig"
+# define strncasecmp _strnicmp
+# define strcasecmp _stricmp
+#endif
+
+#define PKG_CONFIG_EXT ".pc"
+#define PKG_CONFIG_PATH_SZ (65535)
+
+static inline bool
+str_has_suffix(const char *str, const char *suffix)
+{
+ size_t str_len = strlen(str);
+ size_t suf_len = strlen(suffix);
+
+ if (str_len < suf_len)
+ return false;
+
+ return !strncasecmp(str + str_len - suf_len, suffix, suf_len);
+}
+
+static inline const char *
+get_default_pkgconfig_path(void)
+{
+#ifdef _WIN32
+ static char outbuf[MAX_PATH];
+ char namebuf[MAX_PATH];
+ char *p;
+
+ int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf);
+ char * winslash;
+ namebuf[sizepath] = '\0';
+ while ((winslash = strchr (namebuf, '\\')) != NULL)
+ {
+ *winslash = '/';
+ }
+ p = strrchr(namebuf, '/');
+ if (p == NULL)
+ return PKG_DEFAULT_PATH;
+
+ *p = '\0';
+ pkgconf_strlcpy(outbuf, namebuf, sizeof outbuf);
+ pkgconf_strlcat(outbuf, "/", sizeof outbuf);
+ pkgconf_strlcat(outbuf, "../lib/pkgconfig", sizeof outbuf);
+ pkgconf_strlcat(outbuf, ";", sizeof outbuf);
+ pkgconf_strlcat(outbuf, namebuf, sizeof outbuf);
+ pkgconf_strlcat(outbuf, "/", sizeof outbuf);
+ pkgconf_strlcat(outbuf, "../share/pkgconfig", sizeof outbuf);
+
+ return outbuf;
+#endif
+
+ return PKG_DEFAULT_PATH;
+}
+
+static const char *
+pkg_get_parent_dir(pkgconf_pkg_t *pkg)
+{
+ static char buf[PKGCONF_BUFSIZE];
+ char *pathbuf;
+
+ pkgconf_strlcpy(buf, pkg->filename, sizeof buf);
+ pathbuf = strrchr(buf, PKG_DIR_SEP_S);
+ if (pathbuf == NULL)
+ pathbuf = strrchr(buf, '/');
+ if (pathbuf != NULL)
+ pathbuf[0] = '\0';
+
+ return buf;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_pkg_dir_list_build(pkgconf_client_t *client)
+ *
+ * Bootstraps the package search paths. If the ``PKGCONF_PKG_PKGF_ENV_ONLY`` `flag` is set on the client,
+ * then only the ``PKG_CONFIG_PATH`` environment variable will be used, otherwise both the
+ * ``PKG_CONFIG_PATH`` and ``PKG_CONFIG_LIBDIR`` environment variables will be used.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to bootstrap.
+ * :return: nothing
+ */
+void
+pkgconf_pkg_dir_list_build(pkgconf_client_t *client)
+{
+ pkgconf_path_build_from_environ("PKG_CONFIG_PATH", NULL, &client->dir_list, true);
+
+ if (!(client->flags & PKGCONF_PKG_PKGF_ENV_ONLY))
+ pkgconf_path_build_from_environ("PKG_CONFIG_LIBDIR", get_default_pkgconfig_path(), &client->dir_list, true);
+}
+
+typedef void (*pkgconf_pkg_parser_keyword_func_t)(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, const ptrdiff_t offset, char *value);
+typedef struct {
+ const char *keyword;
+ const pkgconf_pkg_parser_keyword_func_t func;
+ const ptrdiff_t offset;
+} pkgconf_pkg_parser_keyword_pair_t;
+
+static int pkgconf_pkg_parser_keyword_pair_cmp(const void *key, const void *ptr)
+{
+ const pkgconf_pkg_parser_keyword_pair_t *pair = ptr;
+ return strcasecmp(key, pair->keyword);
+}
+
+static void
+pkgconf_pkg_parser_tuple_func(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, const ptrdiff_t offset, char *value)
+{
+ char **dest = (char **)((char *) pkg + offset);
+ *dest = pkgconf_tuple_parse(client, &pkg->vars, value);
+}
+
+static void
+pkgconf_pkg_parser_fragment_func(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, const ptrdiff_t offset, char *value)
+{
+ pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+ pkgconf_fragment_parse(client, dest, &pkg->vars, value);
+}
+
+static void
+pkgconf_pkg_parser_dependency_func(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, const ptrdiff_t offset, char *value)
+{
+ pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+ pkgconf_dependency_parse(client, pkg, dest, value);
+}
+
+/* keep this in alphabetical order */
+static const pkgconf_pkg_parser_keyword_pair_t pkgconf_pkg_parser_keyword_funcs[] = {
+ {"CFLAGS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags)},
+ {"CFLAGS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags_private)},
+ {"Conflicts", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, conflicts)},
+ {"Description", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, description)},
+ {"LIBS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs)},
+ {"LIBS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs_private)},
+ {"Name", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, realname)},
+ {"Provides", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, provides)},
+ {"Requires", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, requires)},
+ {"Requires.private", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, requires_private)},
+ {"Version", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, version)},
+};
+
+static bool
+pkgconf_pkg_parser_keyword_set(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, char *value)
+{
+ const pkgconf_pkg_parser_keyword_pair_t *pair = bsearch(keyword,
+ pkgconf_pkg_parser_keyword_funcs, PKGCONF_ARRAY_SIZE(pkgconf_pkg_parser_keyword_funcs),
+ sizeof(pkgconf_pkg_parser_keyword_pair_t), pkgconf_pkg_parser_keyword_pair_cmp);
+
+ if (pair == NULL || pair->func == NULL)
+ return false;
+
+ pair->func(client, pkg, pair->offset, value);
+ return true;
+}
+
+static const char *
+determine_prefix(const pkgconf_pkg_t *pkg)
+{
+ static char buf[PKGCONF_BUFSIZE];
+ char *pathiter;
+
+ pkgconf_strlcpy(buf, pkg->filename, sizeof buf);
+ pkgconf_path_relocate(buf, sizeof buf);
+
+ pathiter = strrchr(buf, PKG_DIR_SEP_S);
+ if (pathiter == NULL)
+ pathiter = strrchr(buf, '/');
+ if (pathiter != NULL)
+ pathiter[0] = '\0';
+
+ pathiter = strrchr(buf, PKG_DIR_SEP_S);
+ if (pathiter == NULL)
+ pathiter = strrchr(buf, '/');
+ if (pathiter == NULL)
+ return NULL;
+
+ /* parent dir is not pkgconfig, can't relocate then */
+ if (strcmp(pathiter + 1, "pkgconfig"))
+ return NULL;
+
+ /* okay, work backwards and do it again. */
+ pathiter[0] = '\0';
+ pathiter = strrchr(buf, PKG_DIR_SEP_S);
+ if (pathiter == NULL)
+ pathiter = strrchr(buf, '/');
+ if (pathiter == NULL)
+ return NULL;
+
+ pathiter[0] = '\0';
+
+ return buf;
+}
+
+typedef struct {
+ const char *field;
+ const ptrdiff_t offset;
+} pkgconf_pkg_validity_check_t;
+
+static const pkgconf_pkg_validity_check_t pkgconf_pkg_validations[] = {
+ {"Name", offsetof(pkgconf_pkg_t, realname)},
+ {"Description", offsetof(pkgconf_pkg_t, description)},
+ {"Version", offsetof(pkgconf_pkg_t, version)},
+};
+
+static bool
+pkgconf_pkg_validate(const pkgconf_client_t *client, const pkgconf_pkg_t *pkg)
+{
+ size_t i;
+ bool valid = true;
+
+ for (i = 0; i < PKGCONF_ARRAY_SIZE(pkgconf_pkg_validations); i++)
+ {
+ char **p = (char **)((char *) pkg + pkgconf_pkg_validations[i].offset);
+
+ if (*p != NULL)
+ continue;
+
+ pkgconf_warn(client, "%s: warning: file does not declare a `%s' field\n", pkg->filename, pkgconf_pkg_validations[i].field);
+ valid = false;
+ }
+
+ return valid;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f)
+ *
+ * Parse a .pc file into a pkgconf_pkg_t object structure.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param char* filename: The filename of the package file (including full path).
+ * :param FILE* f: The file object to read from.
+ * :returns: A ``pkgconf_pkg_t`` object which contains the package data.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *filename, FILE *f)
+{
+ pkgconf_pkg_t *pkg;
+ char readbuf[PKGCONF_BUFSIZE];
+ char *idptr;
+ size_t lineno = 0;
+
+ pkg = calloc(sizeof(pkgconf_pkg_t), 1);
+ pkg->filename = strdup(filename);
+ pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pkg_get_parent_dir(pkg), true);
+
+ /* make module id */
+ if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL)
+ idptr++;
+#ifdef _WIN32
+ else if ((idptr = strrchr(pkg->filename, '/')) != NULL)
+ idptr++;
+#endif
+ else
+ idptr = pkg->filename;
+
+ pkg->id = strdup(idptr);
+ idptr = strrchr(pkg->id, '.');
+ if (idptr)
+ *idptr = '\0';
+
+ while (pkgconf_fgetline(readbuf, PKGCONF_BUFSIZE, f) != NULL)
+ {
+ char op, *p, *key, *value;
+ bool warned_key_whitespace = false, warned_value_whitespace = false;
+
+ lineno++;
+
+ PKGCONF_TRACE(client, "%s:%zu > [%s]", filename, lineno, readbuf);
+
+ p = readbuf;
+ while (*p && (isalpha((unsigned int)*p) || isdigit((unsigned int)*p) || *p == '_' || *p == '.'))
+ p++;
+
+ key = readbuf;
+ if (!isalpha((unsigned int)*key) && !isdigit((unsigned int)*p))
+ continue;
+
+ while (*p && isspace((unsigned int)*p))
+ {
+ if (!warned_key_whitespace)
+ {
+ pkgconf_warn(client, "%s:%zu: warning: whitespace encountered while parsing key section\n",
+ pkg->filename, lineno);
+ warned_key_whitespace = true;
+ }
+
+ /* set to null to avoid trailing spaces in key */
+ *p = '\0';
+ p++;
+ }
+
+ op = *p;
+ *p = '\0';
+ p++;
+
+ while (*p && isspace((unsigned int)*p))
+ p++;
+
+ value = p;
+ p = value + (strlen(value) - 1);
+ while (*p && isspace((unsigned int) *p) && p > value)
+ {
+ if (!warned_value_whitespace && op == '=')
+ {
+ pkgconf_warn(client, "%s:%zu: warning: trailing whitespace encountered while parsing value section\n",
+ pkg->filename, lineno);
+ warned_value_whitespace = true;
+ }
+
+ *p = '\0';
+ p--;
+ }
+
+ switch (op)
+ {
+ case ':':
+ pkgconf_pkg_parser_keyword_set(client, pkg, key, value);
+ break;
+ case '=':
+ if (strcmp(key, client->prefix_varname) || !(client->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX))
+ pkgconf_tuple_add(client, &pkg->vars, key, value, true);
+ else
+ {
+ const char *relvalue = determine_prefix(pkg);
+ if (relvalue != NULL)
+ {
+ pkgconf_tuple_add(client, &pkg->vars, "orig_prefix", value, true);
+ pkgconf_tuple_add(client, &pkg->vars, key, relvalue, false);
+ }
+ else
+ pkgconf_tuple_add(client, &pkg->vars, key, value, true);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ fclose(f);
+
+ if (!pkgconf_pkg_validate(client, pkg))
+ {
+ pkgconf_warn(client, "%s: warning: skipping invalid file\n", pkg->filename);
+ pkgconf_pkg_free(client, pkg);
+ return NULL;
+ }
+
+ pkgconf_dependency_add(client, &pkg->provides, pkg->id, pkg->version, PKGCONF_CMP_EQUAL);
+
+ return pkgconf_pkg_ref(client, pkg);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ * Releases all releases for a given ``pkgconf_pkg_t`` object.
+ *
+ * :param pkgconf_client_t* client: The client which owns the ``pkgconf_pkg_t`` object, `pkg`.
+ * :param pkgconf_pkg_t* pkg: The package to free.
+ * :return: nothing
+ */
+void
+pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+ if (pkg == NULL || pkg->flags & PKGCONF_PKG_PROPF_STATIC)
+ return;
+
+ pkgconf_cache_remove(client, pkg);
+
+ pkgconf_dependency_free(&pkg->requires);
+ pkgconf_dependency_free(&pkg->requires_private);
+ pkgconf_dependency_free(&pkg->conflicts);
+ pkgconf_dependency_free(&pkg->provides);
+
+ pkgconf_fragment_free(&pkg->cflags);
+ pkgconf_fragment_free(&pkg->cflags_private);
+ pkgconf_fragment_free(&pkg->libs);
+ pkgconf_fragment_free(&pkg->libs_private);
+
+ pkgconf_tuple_free(&pkg->vars);
+
+ if (pkg->id != NULL)
+ free(pkg->id);
+
+ if (pkg->filename != NULL)
+ free(pkg->filename);
+
+ if (pkg->realname != NULL)
+ free(pkg->realname);
+
+ if (pkg->version != NULL)
+ free(pkg->version);
+
+ if (pkg->description != NULL)
+ free(pkg->description);
+
+ if (pkg->url != NULL)
+ free(pkg->url);
+
+ if (pkg->pc_filedir != NULL)
+ free(pkg->pc_filedir);
+
+ free(pkg);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_ref(const pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ * Adds an additional reference to the package object.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object which owns the package being referenced.
+ * :param pkgconf_pkg_t* pkg: The package object being referenced.
+ * :return: The package itself with an incremented reference count.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_pkg_ref(const pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+ (void) client;
+
+ pkg->refcount++;
+ return pkg;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ * Releases a reference on the package object. If the reference count is 0, then also free the package.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object which owns the package being dereferenced.
+ * :param pkgconf_pkg_t* pkg: The package object being dereferenced.
+ * :return: nothing
+ */
+void
+pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+ pkg->refcount--;
+ if (pkg->refcount <= 0)
+ pkgconf_pkg_free(client, pkg);
+}
+
+static inline pkgconf_pkg_t *
+pkgconf_pkg_try_specific_path(pkgconf_client_t *client, const char *path, const char *name)
+{
+ pkgconf_pkg_t *pkg = NULL;
+ FILE *f;
+ char locbuf[PKG_CONFIG_PATH_SZ];
+ char uninst_locbuf[PKG_CONFIG_PATH_SZ];
+
+ PKGCONF_TRACE(client, "trying path: %s for %s", path, name);
+
+ snprintf(locbuf, sizeof locbuf, "%s/%s" PKG_CONFIG_EXT, path, name);
+ snprintf(uninst_locbuf, sizeof uninst_locbuf, "%s/%s-uninstalled" PKG_CONFIG_EXT, path, name);
+
+ if (!(client->flags & PKGCONF_PKG_PKGF_NO_UNINSTALLED) && (f = fopen(uninst_locbuf, "r")) != NULL)
+ {
+ PKGCONF_TRACE(client, "found (uninstalled): %s", uninst_locbuf);
+ pkg = pkgconf_pkg_new_from_file(client, uninst_locbuf, f);
+ pkg->flags |= PKGCONF_PKG_PROPF_UNINSTALLED;
+ }
+ else if ((f = fopen(locbuf, "r")) != NULL)
+ {
+ PKGCONF_TRACE(client, "found: %s", locbuf);
+ pkg = pkgconf_pkg_new_from_file(client, locbuf, f);
+ }
+
+ return pkg;
+}
+
+static pkgconf_pkg_t *
+pkgconf_pkg_scan_dir(pkgconf_client_t *client, const char *path, void *data, pkgconf_pkg_iteration_func_t func)
+{
+ DIR *dir;
+ struct dirent *dirent;
+ pkgconf_pkg_t *outpkg = NULL;
+
+ dir = opendir(path);
+ if (dir == NULL)
+ return NULL;
+
+ PKGCONF_TRACE(client, "scanning dir [%s]", path);
+
+ for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir))
+ {
+ static char filebuf[PKGCONF_BUFSIZE];
+ pkgconf_pkg_t *pkg;
+ FILE *f;
+
+ pkgconf_strlcpy(filebuf, path, sizeof filebuf);
+ pkgconf_strlcat(filebuf, "/", sizeof filebuf);
+ pkgconf_strlcat(filebuf, dirent->d_name, sizeof filebuf);
+
+ if (!str_has_suffix(filebuf, PKG_CONFIG_EXT))
+ continue;
+
+ PKGCONF_TRACE(client, "trying file [%s]", filebuf);
+
+ f = fopen(filebuf, "r");
+ if (f == NULL)
+ continue;
+
+ pkg = pkgconf_pkg_new_from_file(client, filebuf, f);
+ if (pkg != NULL)
+ {
+ if (func(pkg, data))
+ {
+ outpkg = pkg;
+ goto out;
+ }
+
+ pkgconf_pkg_unref(client, pkg);
+ }
+ }
+
+out:
+ closedir(dir);
+ return outpkg;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *data, pkgconf_pkg_iteration_func_t func)
+ *
+ * Iterates over all packages found in the `package directory list`, running ``func`` on them. If ``func`` returns true,
+ * then stop iteration and return the last iterated package.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param void* data: An opaque pointer to data to provide the iteration function with.
+ * :param pkgconf_pkg_iteration_func_t func: A function which is called for each package to determine if the package matches,
+ * always return ``false`` to iterate over all packages.
+ * :return: A package object reference if one is found by the scan function, else ``NULL``.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_scan_all(pkgconf_client_t *client, void *data, pkgconf_pkg_iteration_func_t func)
+{
+ pkgconf_node_t *n;
+ pkgconf_pkg_t *pkg;
+
+ PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ PKGCONF_TRACE(client, "scanning directory: %s", pnode->path);
+
+ if ((pkg = pkgconf_pkg_scan_dir(client, pnode->path, data, func)) != NULL)
+ return pkg;
+ }
+
+ return NULL;
+}
+
+#ifdef _WIN32
+static pkgconf_pkg_t *
+pkgconf_pkg_find_in_registry_key(pkgconf_client_t *client, HKEY hkey, const char *name)
+{
+ pkgconf_pkg_t *pkg = NULL;
+
+ HKEY key;
+ int i = 0;
+
+ char buf[16384]; /* per registry limits */
+ DWORD bufsize = sizeof buf;
+ if (RegOpenKeyEx(hkey, PKG_CONFIG_REG_KEY,
+ 0, KEY_READ, &key) != ERROR_SUCCESS)
+ return NULL;
+
+ while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL)
+ == ERROR_SUCCESS)
+ {
+ char pathbuf[PKG_CONFIG_PATH_SZ];
+ DWORD type;
+ DWORD pathbuflen = sizeof pathbuf;
+
+ if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen)
+ == ERROR_SUCCESS && type == REG_SZ)
+ {
+ pkg = pkgconf_pkg_try_specific_path(client, pathbuf, name);
+ if (pkg != NULL)
+ break;
+ }
+
+ bufsize = sizeof buf;
+ }
+
+ RegCloseKey(key);
+ return pkg;
+}
+#endif
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name)
+ *
+ * Search for a package.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param char* name: The name of the package `atom` to use for searching.
+ * :return: A package object reference if the package was found, else ``NULL``.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_pkg_find(pkgconf_client_t *client, const char *name)
+{
+ pkgconf_pkg_t *pkg = NULL;
+ pkgconf_node_t *n;
+ FILE *f;
+
+ PKGCONF_TRACE(client, "looking for: %s", name);
+
+ /* name might actually be a filename. */
+ if (str_has_suffix(name, PKG_CONFIG_EXT))
+ {
+ if ((f = fopen(name, "r")) != NULL)
+ {
+ pkgconf_pkg_t *pkg;
+
+ PKGCONF_TRACE(client, "%s is a file", name);
+
+ pkg = pkgconf_pkg_new_from_file(client, name, f);
+ if (pkg != NULL)
+ {
+ pkgconf_path_add(pkg_get_parent_dir(pkg), &client->dir_list, true);
+ return pkg;
+ }
+ }
+ }
+
+ /* check builtins */
+ if ((pkg = pkgconf_builtin_pkg_get(name)) != NULL)
+ {
+ PKGCONF_TRACE(client, "%s is a builtin", name);
+ return pkg;
+ }
+
+ /* check cache */
+ if (!(client->flags & PKGCONF_PKG_PKGF_NO_CACHE))
+ {
+ if ((pkg = pkgconf_cache_lookup(client, name)) != NULL)
+ {
+ PKGCONF_TRACE(client, "%s is cached", name);
+
+ pkg->flags |= PKGCONF_PKG_PROPF_CACHED;
+ return pkg;
+ }
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ pkg = pkgconf_pkg_try_specific_path(client, pnode->path, name);
+ if (pkg != NULL)
+ goto out;
+ }
+
+#ifdef _WIN32
+ /* support getting PKG_CONFIG_PATH from registry */
+ pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_CURRENT_USER, name);
+ if (!pkg)
+ pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_LOCAL_MACHINE, name);
+#endif
+
+out:
+ pkgconf_cache_add(client, pkg);
+
+ return pkg;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_compare_version(const char *a, const char *b)
+ *
+ * Compare versions using RPM version comparison rules as described in the LSB.
+ *
+ * :param char* a: The first version to compare in the pair.
+ * :param char* b: The second version to compare in the pair.
+ * :return: -1 if the first version is greater, 0 if both versions are equal, 1 if the second version is greater.
+ * :rtype: int
+ */
+int
+pkgconf_compare_version(const char *a, const char *b)
+{
+ char oldch1, oldch2;
+ char buf1[PKGCONF_BUFSIZE], buf2[PKGCONF_BUFSIZE];
+ char *str1, *str2;
+ char *one, *two;
+ int ret;
+ bool isnum;
+
+ /* optimization: if version matches then it's the same version. */
+ if (a == NULL)
+ return 1;
+
+ if (b == NULL)
+ return -1;
+
+ if (!strcasecmp(a, b))
+ return 0;
+
+ pkgconf_strlcpy(buf1, a, sizeof buf1);
+ pkgconf_strlcpy(buf2, b, sizeof buf2);
+
+ one = str1 = buf1;
+ two = str2 = buf2;
+
+ while (*one || *two)
+ {
+ while (*one && !isalnum((unsigned int)*one) && *one != '~')
+ one++;
+ while (*two && !isalnum((unsigned int)*two) && *two != '~')
+ two++;
+
+ if (*one == '~' || *two == '~')
+ {
+ if (*one != '~')
+ return -1;
+ if (*two != '~')
+ return 1;
+
+ one++;
+ two++;
+ continue;
+ }
+
+ if (!(*one && *two))
+ break;
+
+ str1 = one;
+ str2 = two;
+
+ if (isdigit((unsigned int)*str1))
+ {
+ while (*str1 && isdigit((unsigned int)*str1))
+ str1++;
+
+ while (*str2 && isdigit((unsigned int)*str2))
+ str2++;
+
+ isnum = true;
+ }
+ else
+ {
+ while (*str1 && isalpha((unsigned int)*str1))
+ str1++;
+
+ while (*str2 && isalpha((unsigned int)*str2))
+ str2++;
+
+ isnum = false;
+ }
+
+ oldch1 = *str1;
+ oldch2 = *str2;
+
+ *str1 = '\0';
+ *str2 = '\0';
+
+ if (one == str1)
+ return -1;
+
+ if (two == str2)
+ return (isnum ? 1 : -1);
+
+ if (isnum)
+ {
+ int onelen, twolen;
+
+ while (*one == '0')
+ one++;
+
+ while (*two == '0')
+ two++;
+
+ onelen = strlen(one);
+ twolen = strlen(two);
+
+ if (onelen > twolen)
+ return 1;
+ else if (twolen > onelen)
+ return -1;
+ }
+
+ ret = strcmp(one, two);
+ if (ret)
+ return ret;
+
+ *str1 = oldch1;
+ *str2 = oldch2;
+
+ one = str1;
+ two = str2;
+ }
+
+ if ((!*one) && (!*two))
+ return 0;
+
+ if (!*one)
+ return -1;
+
+ return 1;
+}
+
+static pkgconf_pkg_t pkg_config_virtual = {
+ .id = "pkg-config",
+ .realname = "pkg-config",
+ .description = "virtual package defining pkg-config API version supported",
+ .url = PACKAGE_BUGREPORT,
+ .version = PACKAGE_VERSION,
+ .flags = PKGCONF_PKG_PROPF_STATIC,
+ .vars = {
+ .head = &(pkgconf_node_t){
+ .prev = NULL,
+ .next = NULL,
+ .data = &(pkgconf_tuple_t){
+ .key = "pc_path",
+ .value = PKG_DEFAULT_PATH,
+ },
+ },
+ .tail = NULL,
+ },
+};
+
+static pkgconf_pkg_t pkgconf_virtual = {
+ .id = "pkgconf",
+ .realname = "pkgconf",
+ .description = "virtual package defining pkgconf API version supported",
+ .url = PACKAGE_BUGREPORT,
+ .version = PACKAGE_VERSION,
+ .flags = PKGCONF_PKG_PROPF_STATIC,
+ .vars = {
+ .head = &(pkgconf_node_t){
+ .prev = NULL,
+ .next = NULL,
+ .data = &(pkgconf_tuple_t){
+ .key = "pc_path",
+ .value = PKG_DEFAULT_PATH,
+ },
+ },
+ .tail = NULL,
+ },
+};
+
+typedef struct {
+ const char *name;
+ pkgconf_pkg_t *pkg;
+} pkgconf_builtin_pkg_pair_t;
+
+/* keep these in alphabetical order */
+static const pkgconf_builtin_pkg_pair_t pkgconf_builtin_pkg_pair_set[] = {
+ {"pkg-config", &pkg_config_virtual},
+ {"pkgconf", &pkgconf_virtual},
+};
+
+static int pkgconf_builtin_pkg_pair_cmp(const void *key, const void *ptr)
+{
+ const pkgconf_builtin_pkg_pair_t *pair = ptr;
+ return strcasecmp(key, pair->name);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name)
+ *
+ * Looks up a built-in package. The package should not be freed or dereferenced.
+ *
+ * :param char* name: An atom corresponding to a built-in package to search for.
+ * :return: the built-in package if present, else ``NULL``.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_builtin_pkg_get(const char *name)
+{
+ const pkgconf_builtin_pkg_pair_t *pair = bsearch(name, pkgconf_builtin_pkg_pair_set,
+ PKGCONF_ARRAY_SIZE(pkgconf_builtin_pkg_pair_set), sizeof(pkgconf_builtin_pkg_pair_t),
+ pkgconf_builtin_pkg_pair_cmp);
+
+ return (pair != NULL) ? pair->pkg : NULL;
+}
+
+typedef bool (*pkgconf_vercmp_res_func_t)(const char *a, const char *b);
+
+typedef struct {
+ const char *name;
+ pkgconf_pkg_comparator_t compare;
+} pkgconf_pkg_comparator_pair_t;
+
+static const pkgconf_pkg_comparator_pair_t pkgconf_pkg_comparator_names[] = {
+ {"!=", PKGCONF_CMP_NOT_EQUAL},
+ {"(any)", PKGCONF_CMP_ANY},
+ {"<", PKGCONF_CMP_LESS_THAN},
+ {"<=", PKGCONF_CMP_LESS_THAN_EQUAL},
+ {"=", PKGCONF_CMP_EQUAL},
+ {">", PKGCONF_CMP_GREATER_THAN},
+ {">=", PKGCONF_CMP_GREATER_THAN_EQUAL},
+};
+
+static int pkgconf_pkg_comparator_pair_namecmp(const void *key, const void *ptr)
+{
+ const pkgconf_pkg_comparator_pair_t *pair = ptr;
+ return strcmp(key, pair->name);
+}
+
+static bool pkgconf_pkg_comparator_lt(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) < 0);
+}
+
+static bool pkgconf_pkg_comparator_gt(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) > 0);
+}
+
+static bool pkgconf_pkg_comparator_lte(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) <= 0);
+}
+
+static bool pkgconf_pkg_comparator_gte(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) >= 0);
+}
+
+static bool pkgconf_pkg_comparator_eq(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) == 0);
+}
+
+static bool pkgconf_pkg_comparator_ne(const char *a, const char *b)
+{
+ return (pkgconf_compare_version(a, b) != 0);
+}
+
+static bool pkgconf_pkg_comparator_any(const char *a, const char *b)
+{
+ (void) a;
+ (void) b;
+
+ return true;
+}
+
+static bool pkgconf_pkg_comparator_none(const char *a, const char *b)
+{
+ (void) a;
+ (void) b;
+
+ return false;
+}
+
+static const pkgconf_vercmp_res_func_t pkgconf_pkg_comparator_impls[] = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_any,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_eq,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_ne,
+};
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep)
+ *
+ * Returns the comparator used in a depgraph dependency node as a string.
+ *
+ * :param pkgconf_dependency_t* pkgdep: The depgraph dependency node to return the comparator for.
+ * :return: A string matching the comparator or ``"???"``.
+ * :rtype: char *
+ */
+const char *
+pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep)
+{
+ if (pkgdep->compare >= PKGCONF_ARRAY_SIZE(pkgconf_pkg_comparator_names))
+ return "???";
+
+ return pkgconf_pkg_comparator_names[pkgdep->compare].name;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name)
+ *
+ * Look up the appropriate comparator bytecode in the comparator set (defined
+ * in ``pkg.c``, see ``pkgconf_pkg_comparator_names`` and ``pkgconf_pkg_comparator_impls``).
+ *
+ * :param char* name: The comparator to look up by `name`.
+ * :return: The comparator bytecode if found, else ``PKGCONF_CMP_ANY``.
+ * :rtype: pkgconf_pkg_comparator_t
+ */
+pkgconf_pkg_comparator_t
+pkgconf_pkg_comparator_lookup_by_name(const char *name)
+{
+ const pkgconf_pkg_comparator_pair_t *p = bsearch(name, pkgconf_pkg_comparator_names,
+ PKGCONF_ARRAY_SIZE(pkgconf_pkg_comparator_names), sizeof(pkgconf_pkg_comparator_pair_t),
+ pkgconf_pkg_comparator_pair_namecmp);
+
+ return (p != NULL) ? p->compare : PKGCONF_CMP_ANY;
+}
+
+typedef struct {
+ pkgconf_dependency_t *pkgdep;
+} pkgconf_pkg_scan_providers_ctx_t;
+
+typedef struct {
+ const pkgconf_vercmp_res_func_t rulecmp[PKGCONF_CMP_COUNT];
+ const pkgconf_vercmp_res_func_t depcmp[PKGCONF_CMP_COUNT];
+} pkgconf_pkg_provides_vermatch_rule_t;
+
+static const pkgconf_pkg_provides_vermatch_rule_t pkgconf_pkg_provides_vermatch_rules[] = {
+ [PKGCONF_CMP_ANY] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ },
+ .depcmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ },
+ },
+ [PKGCONF_CMP_LESS_THAN] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ },
+ .depcmp = {
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_gte,
+ },
+ },
+ [PKGCONF_CMP_GREATER_THAN] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ },
+ .depcmp = {
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_lte,
+ },
+ },
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ },
+ .depcmp = {
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_gt,
+ },
+ },
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ },
+ .depcmp = {
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_lt,
+ },
+ },
+ [PKGCONF_CMP_EQUAL] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_eq,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_ne
+ },
+ .depcmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ },
+ },
+ [PKGCONF_CMP_NOT_EQUAL] = {
+ .rulecmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gte,
+ [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lte,
+ [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gt,
+ [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lt,
+ [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_ne,
+ [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_eq
+ },
+ .depcmp = {
+ [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none,
+ },
+ },
+};
+
+/*
+ * pkgconf_pkg_scan_provides_vercmp(pkgdep, provider)
+ *
+ * compare a provides node against the requested dependency node.
+ *
+ * XXX: maybe handle PKGCONF_CMP_ANY in a versioned comparison
+ */
+static bool
+pkgconf_pkg_scan_provides_vercmp(const pkgconf_dependency_t *pkgdep, const pkgconf_dependency_t *provider)
+{
+ const pkgconf_pkg_provides_vermatch_rule_t *rule = &pkgconf_pkg_provides_vermatch_rules[pkgdep->compare];
+
+ if (rule->depcmp[provider->compare] != NULL &&
+ !rule->depcmp[provider->compare](provider->version, pkgdep->version))
+ return false;
+
+ if (rule->rulecmp[provider->compare] != NULL &&
+ !rule->rulecmp[provider->compare](pkgdep->version, provider->version))
+ return false;
+
+ return true;
+}
+
+/*
+ * pkgconf_pkg_scan_provides_entry(pkg, ctx)
+ *
+ * attempt to match a single package's Provides rules against the requested dependency node.
+ */
+static bool
+pkgconf_pkg_scan_provides_entry(const pkgconf_pkg_t *pkg, const pkgconf_pkg_scan_providers_ctx_t *ctx)
+{
+ const pkgconf_dependency_t *pkgdep = ctx->pkgdep;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node)
+ {
+ const pkgconf_dependency_t *provider = node->data;
+ if (!strcmp(provider->package, pkgdep->package))
+ return pkgconf_pkg_scan_provides_vercmp(pkgdep, provider);
+ }
+
+ return false;
+}
+
+/*
+ * pkgconf_pkg_scan_providers(client, pkgdep, eflags)
+ *
+ * scan all available packages to see if a Provides rule matches the pkgdep.
+ */
+static pkgconf_pkg_t *
+pkgconf_pkg_scan_providers(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags)
+{
+ pkgconf_pkg_t *pkg;
+ pkgconf_pkg_scan_providers_ctx_t ctx = {
+ .pkgdep = pkgdep,
+ };
+
+ pkg = pkgconf_scan_all(client, &ctx, (pkgconf_pkg_iteration_func_t) pkgconf_pkg_scan_provides_entry);
+ if (pkg != NULL)
+ return pkg;
+
+ if (eflags != NULL)
+ *eflags |= PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND;
+
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags)
+ *
+ * Verify a pkgconf_dependency_t node in the depgraph. If the dependency is solvable,
+ * return the appropriate ``pkgconf_pkg_t`` object, else ``NULL``.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_dependency_t* pkgdep: The dependency graph node to solve.
+ * :param uint* eflags: An optional pointer that, if set, will be populated with an error code from the resolver.
+ * :return: On success, the appropriate ``pkgconf_pkg_t`` object to solve the dependency, else ``NULL``.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags)
+{
+ pkgconf_pkg_t *pkg = NULL;
+
+ if (eflags != NULL)
+ *eflags = PKGCONF_PKG_ERRF_OK;
+
+ PKGCONF_TRACE(client, "trying to verify dependency: %s", pkgdep->package);
+
+ pkg = pkgconf_pkg_find(client, pkgdep->package);
+ if (pkg == NULL)
+ {
+ if (client->flags & PKGCONF_PKG_PKGF_SKIP_PROVIDES)
+ {
+ if (eflags != NULL)
+ *eflags |= PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND;
+
+ return NULL;
+ }
+
+ return pkgconf_pkg_scan_providers(client, pkgdep, eflags);
+ }
+
+ if (pkg->id == NULL)
+ pkg->id = strdup(pkgdep->package);
+
+ if (pkgconf_pkg_comparator_impls[pkgdep->compare](pkg->version, pkgdep->version) == true)
+ return pkg;
+
+ if (eflags != NULL)
+ *eflags |= PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH;
+
+ return pkg;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth)
+ *
+ * Verify the graph dependency nodes are satisfiable by walking the tree using
+ * ``pkgconf_pkg_traverse()``.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_pkg_t* root: The root entry in the package dependency graph which should contain the top-level dependencies to resolve.
+ * :param int depth: The maximum allowed depth for dependency resolution.
+ * :return: On success, ``PKGCONF_PKG_ERRF_OK`` (0), else an error code.
+ * :rtype: unsigned int
+ */
+unsigned int
+pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth)
+{
+ return pkgconf_pkg_traverse(client, root, NULL, NULL, depth);
+}
+
+static unsigned int
+pkgconf_pkg_report_graph_error(pkgconf_client_t *client, pkgconf_pkg_t *parent, pkgconf_pkg_t *pkg, pkgconf_dependency_t *node, unsigned int eflags)
+{
+ static bool already_sent_notice = false;
+
+ if (eflags & PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND)
+ {
+ if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) & !already_sent_notice)
+ {
+ pkgconf_error(client, "Package %s was not found in the pkg-config search path.\n", node->package);
+ pkgconf_error(client, "Perhaps you should add the directory containing `%s.pc'\n", node->package);
+ pkgconf_error(client, "to the PKG_CONFIG_PATH environment variable\n");
+ already_sent_notice = true;
+ }
+
+ pkgconf_error(client, "Package '%s', required by '%s', not found\n", node->package, parent->id);
+ pkgconf_audit_log(client, "%s NOT-FOUND\n", node->package);
+ }
+ else if (eflags & PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH)
+ {
+ pkgconf_error(client, "Package dependency requirement '%s %s %s' could not be satisfied.\n",
+ node->package, pkgconf_pkg_get_comparator(node), node->version);
+
+ if (pkg != NULL)
+ pkgconf_error(client, "Package '%s' has version '%s', required version is '%s %s'\n",
+ node->package, pkg->version, pkgconf_pkg_get_comparator(node), node->version);
+ }
+
+ if (pkg != NULL)
+ pkgconf_pkg_unref(client, pkg);
+
+ return eflags;
+}
+
+static inline unsigned int
+pkgconf_pkg_walk_list(pkgconf_client_t *client,
+ pkgconf_pkg_t *parent,
+ pkgconf_list_t *deplist,
+ pkgconf_pkg_traverse_func_t func,
+ void *data,
+ int depth)
+{
+ unsigned int eflags = PKGCONF_PKG_ERRF_OK;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(deplist->head, node)
+ {
+ unsigned int eflags_local = PKGCONF_PKG_ERRF_OK;
+ pkgconf_dependency_t *depnode = node->data;
+ pkgconf_pkg_t *pkgdep;
+
+ if (*depnode->package == '\0')
+ continue;
+
+ pkgdep = pkgconf_pkg_verify_dependency(client, depnode, &eflags_local);
+
+ eflags |= eflags_local;
+ if (eflags_local != PKGCONF_PKG_ERRF_OK && !(client->flags & PKGCONF_PKG_PKGF_SKIP_ERRORS))
+ {
+ pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local);
+ continue;
+ }
+ if (pkgdep == NULL)
+ continue;
+
+ if (pkgdep->flags & PKGCONF_PKG_PROPF_SEEN)
+ {
+ pkgconf_pkg_unref(client, pkgdep);
+ continue;
+ }
+
+ pkgconf_audit_log_dependency(client, pkgdep, depnode);
+
+ pkgdep->flags |= PKGCONF_PKG_PROPF_SEEN;
+ eflags |= pkgconf_pkg_traverse(client, pkgdep, func, data, depth - 1);
+ pkgdep->flags &= ~PKGCONF_PKG_PROPF_SEEN;
+ pkgconf_pkg_unref(client, pkgdep);
+ }
+
+ return eflags;
+}
+
+static inline unsigned int
+pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client,
+ pkgconf_pkg_t *root, pkgconf_list_t *deplist)
+{
+ unsigned int eflags;
+ pkgconf_node_t *node, *childnode;
+
+ PKGCONF_FOREACH_LIST_ENTRY(deplist->head, node)
+ {
+ pkgconf_dependency_t *parentnode = node->data;
+
+ if (*parentnode->package == '\0')
+ continue;
+
+ PKGCONF_FOREACH_LIST_ENTRY(root->requires.head, childnode)
+ {
+ pkgconf_pkg_t *pkgdep;
+ pkgconf_dependency_t *depnode = childnode->data;
+
+ if (*depnode->package == '\0' || strcmp(depnode->package, parentnode->package))
+ continue;
+
+ pkgdep = pkgconf_pkg_verify_dependency(client, parentnode, &eflags);
+ if (eflags == PKGCONF_PKG_ERRF_OK)
+ {
+ pkgconf_error(client, "Version '%s' of '%s' conflicts with '%s' due to satisfying conflict rule '%s %s%s%s'.\n",
+ pkgdep->version, pkgdep->realname, root->realname, parentnode->package, pkgconf_pkg_get_comparator(parentnode),
+ parentnode->version != NULL ? " " : "", parentnode->version != NULL ? parentnode->version : "");
+ pkgconf_error(client, "It may be possible to ignore this conflict and continue, try the\n");
+ pkgconf_error(client, "PKG_CONFIG_IGNORE_CONFLICTS environment variable.\n");
+
+ pkgconf_pkg_unref(client, pkgdep);
+
+ return PKGCONF_PKG_ERRF_PACKAGE_CONFLICT;
+ }
+
+ pkgconf_pkg_unref(client, pkgdep);
+ }
+ }
+
+ return PKGCONF_PKG_ERRF_OK;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth)
+ *
+ * Walk and resolve the dependency graph up to `maxdepth` levels.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_pkg_t* root: The root of the dependency graph.
+ * :param pkgconf_pkg_traverse_func_t func: A traversal function to call for each resolved node in the dependency graph.
+ * :param void* data: An opaque pointer to data to be passed to the traversal function.
+ * :param int maxdepth: The maximum depth to walk the dependency graph for. -1 means infinite recursion.
+ * :return: ``PKGCONF_PKG_ERRF_OK`` on success, else an error code.
+ * :rtype: unsigned int
+ */
+unsigned int
+pkgconf_pkg_traverse(pkgconf_client_t *client,
+ pkgconf_pkg_t *root,
+ pkgconf_pkg_traverse_func_t func,
+ void *data,
+ int maxdepth)
+{
+ unsigned int eflags = PKGCONF_PKG_ERRF_OK;
+
+ if (maxdepth == 0)
+ return eflags;
+
+ PKGCONF_TRACE(client, "%s: level %d", root->id, maxdepth);
+
+ if ((root->flags & PKGCONF_PKG_PROPF_VIRTUAL) != PKGCONF_PKG_PROPF_VIRTUAL || (client->flags & PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL) != PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL)
+ {
+ if (func != NULL)
+ func(client, root, data);
+ }
+
+ if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS))
+ {
+ eflags = pkgconf_pkg_walk_conflicts_list(client, root, &root->conflicts);
+ if (eflags != PKGCONF_PKG_ERRF_OK)
+ return eflags;
+ }
+
+ PKGCONF_TRACE(client, "%s: walking requires list", root->id);
+ eflags = pkgconf_pkg_walk_list(client, root, &root->requires, func, data, maxdepth);
+ if (eflags != PKGCONF_PKG_ERRF_OK)
+ return eflags;
+
+ if (client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE)
+ {
+ PKGCONF_TRACE(client, "%s: walking requires.private list", root->id);
+
+ /* XXX: ugly */
+ client->flags |= PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE;
+ eflags = pkgconf_pkg_walk_list(client, root, &root->requires_private, func, data, maxdepth);
+ client->flags &= ~PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE;
+
+ if (eflags != PKGCONF_PKG_ERRF_OK)
+ return eflags;
+ }
+
+ return eflags;
+}
+
+static void
+pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+{
+ pkgconf_list_t *list = data;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, false);
+ }
+}
+
+static void
+pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+{
+ pkgconf_list_t *list = data;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_private.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, true);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
+ *
+ * Walks a dependency graph and extracts relevant ``CFLAGS`` fragments.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_pkg_t* root: The root of the dependency graph.
+ * :param pkgconf_list_t* list: The fragment list to add the extracted ``CFLAGS`` fragments to.
+ * :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion.
+ * :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code.
+ * :rtype: unsigned int
+ */
+unsigned int
+pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
+{
+ unsigned int eflag;
+
+ eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, list, maxdepth);
+ if (eflag != PKGCONF_PKG_ERRF_OK)
+ pkgconf_fragment_free(list);
+
+ if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)
+ {
+ eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, list, maxdepth);
+ if (eflag != PKGCONF_PKG_ERRF_OK)
+ pkgconf_fragment_free(list);
+ }
+
+ return eflag;
+}
+
+static void
+pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+{
+ pkgconf_list_t *list = data;
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->libs.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE) != 0);
+ }
+
+ if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)
+ {
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_private.head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+ pkgconf_fragment_copy(client, list, frag, true);
+ }
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
+ *
+ * Walks a dependency graph and extracts relevant ``LIBS`` fragments.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_pkg_t* root: The root of the dependency graph.
+ * :param pkgconf_list_t* list: The fragment list to add the extracted ``LIBS`` fragments to.
+ * :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion.
+ * :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code.
+ * :rtype: unsigned int
+ */
+unsigned int
+pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
+{
+ unsigned int eflag;
+
+ eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, list, maxdepth);
+
+ if (eflag != PKGCONF_PKG_ERRF_OK)
+ {
+ pkgconf_fragment_free(list);
+ return eflag;
+ }
+
+ return eflag;
+}