aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS4
-rw-r--r--COPYING10
-rw-r--r--INSTALL7
-rw-r--r--README20
-rw-r--r--TODO18
-rw-r--r--build/.gitignore1
-rw-r--r--build/bootstrap.build25
-rw-r--r--build/export.build10
-rw-r--r--build/root.build10
-rw-r--r--buildfile18
-rw-r--r--libpkgconf/.gitignore3
-rw-r--r--libpkgconf/argvsplit.c156
-rw-r--r--libpkgconf/audit.c98
-rw-r--r--libpkgconf/bsdstubs.c139
-rw-r--r--libpkgconf/bsdstubs.h25
-rw-r--r--libpkgconf/buildfile97
-rw-r--r--libpkgconf/cache.c136
-rw-r--r--libpkgconf/client.c570
-rw-r--r--libpkgconf/config.h37
-rw-r--r--libpkgconf/dependency.c320
-rw-r--r--libpkgconf/fileio.c120
-rw-r--r--libpkgconf/fragment.c626
-rw-r--r--libpkgconf/iter.h97
-rw-r--r--libpkgconf/libpkgconf-api.h20
-rw-r--r--libpkgconf/libpkgconf.h319
-rw-r--r--libpkgconf/path.c326
-rw-r--r--libpkgconf/pkg.c1597
-rw-r--r--libpkgconf/queue.c194
-rw-r--r--libpkgconf/stdinc.h54
-rw-r--r--libpkgconf/tuple.c347
-rw-r--r--libpkgconf/version.h.in10
-rw-r--r--libpkgconf/win-dirent.h929
-rw-r--r--manifest16
-rw-r--r--tests/.gitignore3
-rw-r--r--tests/basic/buildfile7
-rw-r--r--tests/basic/driver.c166
-rw-r--r--tests/basic/testscript74
-rw-r--r--tests/build/.gitignore1
-rw-r--r--tests/build/bootstrap.build9
-rw-r--r--tests/build/root.build18
-rw-r--r--tests/buildfile5
41 files changed, 6642 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..92e4b23
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,4 @@
+Baptiste Daroussin <bapt@FreeBSD.org>
+Jeff Horelick <jdhore@gentoo.org>
+Michał Górny <mgorny@gentoo.org
+William Pitcock <nenolod@atheme.org>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..81a5221
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,10 @@
+Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017
+ pkgconf authors (see AUTHORS file in source directory).
+
+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.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..d44b974
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,7 @@
+The aim of this package is to make reading the INSTALL file unnecessary. So
+next time try running:
+
+$ bpkg build libpkgconf
+
+But if you don't want to use the package manager, then you can also build this
+package manually using the standard build2 build system.
diff --git a/README b/README
new file mode 100644
index 0000000..bf253ff
--- /dev/null
+++ b/README
@@ -0,0 +1,20 @@
+libpkgconf is a C library which helps to configure compiler and linker flags
+for development frameworks. It provids most of the pkgconf's functionality,
+which itself is similar to pkg-config. For more information see:
+
+https://github.com/pkgconf/pkgconf
+
+This package contains the original libpkgconf library source code overlaid with
+the build2-based build system and packaged for the build2 package manager
+(bpkg).
+
+See the INSTALL file for the prerequisites and installation instructions.
+
+Post questions, bug reports, or any other feedback about the library itself at
+https://github.com/pkgconf/pkgconf/issues. Send build system and
+packaging-related feedback to the packaging@build2.org mailing list (see
+https://lists.build2.org for posting guidelines, etc).
+
+The packaging of libpkgconf for build2 is tracked in a Git repository at:
+
+https://git.build2.org/cgit/packaging/pkgconf/
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a7f564e
--- /dev/null
+++ b/TODO
@@ -0,0 +1,18 @@
+@@ What is a project email (update the manifest file)? Using personal email of
+ one of the developers for now.
+
+@@ Get MinGW/msvcrt issue fixed (see client.c and pkg.c for details) in the
+ original code base. Issue report is at
+ https://github.com/pkgconf/pkgconf/issues/125.
+
+@@ Get thread safety issue fixed. Relates to wide using static char buffers
+ (just grep the sources for 'static char'). Issue report is at
+ https://github.com/pkgconf/pkgconf/issues/128.
+
+@@ Consider redefining PKGCONF_BUFSIZE from the current 65535 bytes to
+ something smaller, given it is used to allocate buffers on the stack
+ (sometimes several of them).
+
+@@ For VC including libpkgconf.h leads to including win-dirent.h, that injects
+ a bunch of static functions into the translation unit. So need to take care
+ of not including libpkgconf.h in too many places.
diff --git a/build/.gitignore b/build/.gitignore
new file mode 100644
index 0000000..225c27f
--- /dev/null
+++ b/build/.gitignore
@@ -0,0 +1 @@
+config.build
diff --git a/build/bootstrap.build b/build/bootstrap.build
new file mode 100644
index 0000000..3034b13
--- /dev/null
+++ b/build/bootstrap.build
@@ -0,0 +1,25 @@
+# file : build/bootstrap.build
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+project = libpkgconf
+
+using version
+using config
+using dist
+using test
+using install
+
+# The versioning scheme (after 0.9.12) assumes that each [major?] release has
+# it's own number (starting with 2). In any case, as of 1.3.8, version 2 is
+# still in the library file name (libpkgconf.so.2.0.0). This probably means
+# that the release number is equal to major version + 1 (though in Makefile.am
+# they use -version-info 2:0:0 which doesn't mean what they think it means).
+# So we just need to watch their Makefile.am for any changes.
+#
+# See also: http://kaniini.dereferenced.org/2015/07/20/pkgconf-0-9-12-and-future.html
+#
+if ($version.major == 1)
+ release_num = 2
+else
+ fail "increment the release number?"
diff --git a/build/export.build b/build/export.build
new file mode 100644
index 0000000..ef0b5b5
--- /dev/null
+++ b/build/export.build
@@ -0,0 +1,10 @@
+# file : build/export.build
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+$out_root/:
+{
+ include libpkgconf/
+}
+
+export $out_root/libpkgconf/lib{pkgconf}
diff --git a/build/root.build b/build/root.build
new file mode 100644
index 0000000..ba11186
--- /dev/null
+++ b/build/root.build
@@ -0,0 +1,10 @@
+# file : build/root.build
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+c.std = 99
+
+using c
+
+h{*}: extension = h
+c{*}: extension = c
diff --git a/buildfile b/buildfile
new file mode 100644
index 0000000..f4e7523
--- /dev/null
+++ b/buildfile
@@ -0,0 +1,18 @@
+# file : buildfile
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+./: {*/ -build/} doc{AUTHORS COPYING INSTALL README version} file{manifest}
+
+# The version file is auto-generated (by the version module) from manifest.
+# Include it in distribution and don't remove when cleaning in src (so that
+# clean results in a state identical to distributed).
+#
+doc{version}: file{manifest}
+doc{version}: dist = true
+doc{version}: clean = ($src_root != $out_root)
+
+# Don't install tests or the INSTALL file.
+#
+dir{tests/}: install = false
+doc{INSTALL}@./: install = false
diff --git a/libpkgconf/.gitignore b/libpkgconf/.gitignore
new file mode 100644
index 0000000..620b4c8
--- /dev/null
+++ b/libpkgconf/.gitignore
@@ -0,0 +1,3 @@
+# Generated version.h.
+#
+version.h
diff --git a/libpkgconf/argvsplit.c b/libpkgconf/argvsplit.c
new file mode 100644
index 0000000..85f7f57
--- /dev/null
+++ b/libpkgconf/argvsplit.c
@@ -0,0 +1,156 @@
+/*
+ * argvsplit.c
+ * argv_split() routine
+ *
+ * Copyright (c) 2012, 2017 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>
+
+/*
+ * !doc
+ *
+ * libpkgconf `argvsplit` module
+ * =============================
+ *
+ * This is a lowlevel module which provides parsing of strings into argument vectors,
+ * similar to what a shell would do.
+ */
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_argv_free(char **argv)
+ *
+ * Frees an argument vector.
+ *
+ * :param char** argv: The argument vector to free.
+ * :return: nothing
+ */
+void
+pkgconf_argv_free(char **argv)
+{
+ free(argv[0]);
+ free(argv);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv)
+ *
+ * Splits a string into an argument vector.
+ *
+ * :param char* src: The string to split.
+ * :param int* argc: A pointer to an integer to store the argument count.
+ * :param char*** argv: A pointer to a pointer for an argument vector.
+ * :return: 0 on success, -1 on error.
+ * :rtype: int
+ */
+int
+pkgconf_argv_split(const char *src, int *argc, char ***argv)
+{
+ char *buf = malloc(strlen(src) + 1);
+ const char *src_iter;
+ char *dst_iter;
+ int argc_count = 0;
+ int argv_size = 5;
+ char quote = 0;
+ bool escaped = false;
+
+ src_iter = src;
+ dst_iter = buf;
+
+ memset(buf, 0, strlen(src) + 1);
+
+ *argv = calloc(sizeof (void *), argv_size);
+ (*argv)[argc_count] = dst_iter;
+
+ while (*src_iter)
+ {
+ if (escaped)
+ {
+ /* POSIX: only \CHAR is special inside a double quote if CHAR is {$, `, ", \, newline}. */
+ if (quote == '\"')
+ {
+ if (!(*src_iter == '$' || *src_iter == '`' || *src_iter == '"' || *src_iter == '\\'))
+ *dst_iter++ = '\\';
+
+ *dst_iter++ = *src_iter;
+ }
+ else
+ {
+ if (isspace((unsigned int) *src_iter))
+ *dst_iter++ = '\\';
+
+ *dst_iter++ = *src_iter;
+ }
+
+ escaped = false;
+ }
+ else if (quote)
+ {
+ if (*src_iter == quote)
+ quote = 0;
+ else if (*src_iter == '\\')
+ escaped = true;
+ else
+ *dst_iter++ = *src_iter;
+ }
+ else if (isspace((unsigned int)*src_iter))
+ {
+ if ((*argv)[argc_count] != NULL)
+ {
+ argc_count++, dst_iter++;
+
+ if (argc_count == argv_size)
+ {
+ argv_size += 5;
+ *argv = realloc(*argv, sizeof(void *) * argv_size);
+ }
+
+ (*argv)[argc_count] = dst_iter;
+ }
+ }
+ else switch(*src_iter)
+ {
+ case '\\':
+ escaped = true;
+ break;
+
+ case '\"':
+ case '\'':
+ quote = *src_iter;
+ break;
+
+ default:
+ *dst_iter++ = *src_iter;
+ break;
+ }
+
+ src_iter++;
+ }
+
+ if (escaped || quote)
+ {
+ free(*argv);
+ free(buf);
+ return -1;
+ }
+
+ if (strlen((*argv)[argc_count]))
+ {
+ argc_count++;
+ }
+
+ *argc = argc_count;
+ return 0;
+}
diff --git a/libpkgconf/audit.c b/libpkgconf/audit.c
new file mode 100644
index 0000000..a06eb24
--- /dev/null
+++ b/libpkgconf/audit.c
@@ -0,0 +1,98 @@
+/*
+ * audit.c
+ * package audit log functions
+ *
+ * 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>
+
+/*
+ * !doc
+ *
+ * libpkgconf `audit` module
+ * =========================
+ *
+ * The libpkgconf `audit` module contains the functions related to attaching an audit log file
+ * to a ``pkgconf_client_t`` object.
+ *
+ * The audit log format is the same as the output generated by the ``PKG_CONFIG_LOG`` environment
+ * variable.
+ */
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf)
+ *
+ * Sets the audit log file pointer on `client` to `auditf`.
+ * The callee is responsible for closing any previous log files.
+ *
+ * :param pkgconf_client_t* client: The client object to modify.
+ * :param FILE* auditf: The file pointer for the already open log file.
+ * :return: nothing
+ */
+void
+pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf)
+{
+ client->auditf = auditf;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...)
+ *
+ * Logs a message to the opened audit log (if any).
+ *
+ * :param pkgconf_client_t* client: The client object the log message is for.
+ * :param char* format: The format string to use for the log messages.
+ * :return: nothing
+ */
+void
+pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...)
+{
+ va_list va;
+
+ if (client->auditf == NULL)
+ return;
+
+ va_start(va, format);
+ vfprintf(client->auditf, format, va);
+ va_end(va);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode)
+ *
+ * Convenience function which logs a dependency node to the opened audit log (if any).
+ *
+ * :param pkgconf_client_t* client: The client object the log message is for.
+ * :param pkgconf_pkg_t* dep: The dependency package object being logged.
+ * :param pkgconf_dependency_t* depnode: The dependency object itself being logged.
+ * :return: nothing
+ */
+void
+pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode)
+{
+ if (client->auditf == NULL)
+ return;
+
+ fprintf(client->auditf, "%s ", dep->id);
+ if (depnode->version != NULL && depnode->compare != PKGCONF_CMP_ANY)
+ {
+ fprintf(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version);
+ }
+
+ fprintf(client->auditf, "[%s]\n", dep->version);
+}
diff --git a/libpkgconf/bsdstubs.c b/libpkgconf/bsdstubs.c
new file mode 100644
index 0000000..2c000ac
--- /dev/null
+++ b/libpkgconf/bsdstubs.c
@@ -0,0 +1,139 @@
+/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */
+/* $OpenBSD: strlcat.c,v 1.12 2005/03/30 20:13:52 otto Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+
+#include <libpkgconf/bsdstubs.h>
+#include <libpkgconf/config.h>
+
+#ifndef HAVE_STRLCPY
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+static inline size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+#endif
+
+#ifndef HAVE_STRLCAT
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+static inline size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+#endif
+
+/*
+ * Copyright (c) 2012 William Pitcock <nenolod@dereferenced.org>.
+ *
+ * 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.
+ */
+
+#ifndef HAVE_STRNDUP
+/*
+ * Creates a memory buffer and copies at most 'len' characters to it.
+ * If 'len' is less than the length of the source string, truncation occured.
+ */
+static inline char *
+strndup(const char *src, size_t len)
+{
+ char *out = malloc(len + 1);
+ pkgconf_strlcpy(out, src, len + 1);
+ return out;
+}
+#endif
+
+size_t
+pkgconf_strlcpy(char *dst, const char *src, size_t siz)
+{
+ return strlcpy(dst, src, siz);
+}
+
+size_t
+pkgconf_strlcat(char *dst, const char *src, size_t siz)
+{
+ return strlcat(dst, src, siz);
+}
+
+char *
+pkgconf_strndup(const char *src, size_t len)
+{
+ return strndup(src, len);
+}
diff --git a/libpkgconf/bsdstubs.h b/libpkgconf/bsdstubs.h
new file mode 100644
index 0000000..2e0fb5c
--- /dev/null
+++ b/libpkgconf/bsdstubs.h
@@ -0,0 +1,25 @@
+/*
+ * bsdstubs.h
+ * Header for stub BSD function prototypes if unavailable on a specific platform.
+ *
+ * Copyright (c) 2012 William Pitcock <nenolod@dereferenced.org>.
+ *
+ * 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.
+ */
+
+#ifndef __BSDSTUBS_H__
+#define __BSDSTUBS_H__
+
+#include <libpkgconf/libpkgconf-api.h>
+
+PKGCONF_API extern size_t pkgconf_strlcpy(char *dst, const char *src, size_t siz);
+PKGCONF_API extern size_t pkgconf_strlcat(char *dst, const char *src, size_t siz);
+PKGCONF_API extern char *pkgconf_strndup(const char *src, size_t len);
+
+#endif
diff --git a/libpkgconf/buildfile b/libpkgconf/buildfile
new file mode 100644
index 0000000..285ff01
--- /dev/null
+++ b/libpkgconf/buildfile
@@ -0,0 +1,97 @@
+# file : libpkgconf/buildfile
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+lib{pkgconf}: {h c}{* -version} {h}{version}
+
+# See bootstrap.build for details.
+#
+if $version.pre_release
+ lib{pkgconf}: bin.lib.version = @"-$version.project_id"
+else
+ lib{pkgconf}: bin.lib.version = @"-$release_num"
+
+# The version file is an internal one (is only included from config.h) so we
+# don't distribute nor install (see below).
+#
+h{version}: in{version} $src_root/file{manifest}
+
+# Define the PKG_DEFAULT_PATH, SYSTEM_INCLUDEDIR and SYSTEM_LIBDIR macros.
+# The whole idea feels utterly broken (hello cross-compilation) so we will
+# just do bare minimum and wait and see.
+#
+# @@ We should probably allow to configure these macros via configuration
+# variables config.pkgconfig.pkg_default_path and alike.
+#
+if ($c.target.class == "windows")
+{
+ inc_dirs = ""
+ lib_dirs = ""
+
+ # Note that on Windows PKG_DEFAULT_PATH macros is internally redefined as
+ # "../lib/pkgconfig;../share/pkgconfig" and is used as a fallback. Normally
+ # the default path is relative to the program's (that link the library)
+ # directory and has the following format:
+ #
+ # <dir>/../lib/pkgconfig;<dir>/../share/pkgconfig
+ #
+ # So we keep it empty.
+ #
+ def_paths = ""
+}
+else
+{
+ inc_dirs = "/usr/include"
+ lib_dirs = "/usr/lib"
+
+ def_dirs = ($install.root != [null] \
+ ? "$install.resolve($install.pkgconfig)" \
+ : "")
+}
+
+c.poptions += -DPKG_DEFAULT_PATH=\"$def_dirs\" \
+ -DSYSTEM_INCLUDEDIR=\"$inc_dirs\" \
+ -DSYSTEM_LIBDIR=\"$lib_dirs\"
+
+# Disable warnings.
+#
+if ($c.target.system == "win32-msvc")
+ c.coptions += /wd4996 /wd4267
+
+if ($c.target.class == "windows")
+{
+ # See libpkgconf/libpkgconf-api.h for details.
+ #
+ objs{*}: c.poptions += -DLIBPKGCONF_EXPORT
+ obja{*}: c.poptions += -DPKGCONFIG_IS_STATIC
+ liba{pkgconf}: cc.export.poptions += -DPKGCONFIG_IS_STATIC
+
+ if ($c.target.system == "mingw32")
+ c.libs += -ladvapi32
+ else
+ c.libs += advapi32.lib
+}
+
+c.poptions =+ "-I$out_root" "-I$src_root"
+
+lib{pkgconf}: cc.export.poptions = "-I$src_root"
+
+# Install into the pkgconf/libpkgconf/ subdirectory of, say, /usr/include/.
+# Also make sure Cflags is properly set in .pc files to pkgconfig/.
+#
+{h}{*}: install = include/$project/
+install.include = $install.include/pkgconf/
+
+# For the original package config.h is generated during the configuration
+# phase. We distribute a custom one with a minimal set of macro definitions
+# required to build the project. Note that the file is an internal one (is
+# only included from C files) and so is not installed.
+#
+h{config}@./: install = false
+
+# Internal one (see above).
+#
+h{version}: install = false
+
+if ($c.target.class != "windows")
+ h{win-dirent}@./: install = false
diff --git a/libpkgconf/cache.c b/libpkgconf/cache.c
new file mode 100644
index 0000000..986eba5
--- /dev/null
+++ b/libpkgconf/cache.c
@@ -0,0 +1,136 @@
+/*
+ * cache.c
+ * package object cache
+ *
+ * Copyright (c) 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/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `cache` module
+ * =========================
+ *
+ * The libpkgconf `cache` module manages a package/module object cache, allowing it to
+ * avoid loading duplicate copies of a package/module.
+ *
+ * A cache is tied to a specific pkgconf client object, so package objects should not
+ * be shared across threads.
+ */
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_pkg_t *pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id)
+ *
+ * Looks up a package in the cache given an `id` atom,
+ * such as ``gtk+-3.0`` and returns the already loaded version
+ * if present.
+ *
+ * :param pkgconf_client_t* client: The client object to access.
+ * :param char* id: The package atom to look up in the client object's cache.
+ * :return: A package object if present, else ``NULL``.
+ * :rtype: pkgconf_pkg_t *
+ */
+pkgconf_pkg_t *
+pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id)
+{
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(client->pkg_cache.head, node)
+ {
+ pkgconf_pkg_t *pkg = node->data;
+
+ if (!strcmp(pkg->id, id))
+ {
+ PKGCONF_TRACE(client, "found: %s @%p", id, pkg);
+ return pkgconf_pkg_ref(client, pkg);
+ }
+ }
+
+ PKGCONF_TRACE(client, "miss: %s", id);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ * Adds an entry for the package to the package cache.
+ * The cache entry must be removed if the package is freed.
+ *
+ * :param pkgconf_client_t* client: The client object to modify.
+ * :param pkgconf_pkg_t* pkg: The package object to add to the client object's cache.
+ * :return: nothing
+ */
+void
+pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+ if (pkg == NULL)
+ return;
+
+ pkgconf_pkg_ref(client, pkg);
+ pkgconf_node_insert(&pkg->cache_iter, pkg, &client->pkg_cache);
+
+ PKGCONF_TRACE(client, "added @%p to cache", pkg);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ * Deletes a package from the client object's package cache.
+ *
+ * :param pkgconf_client_t* client: The client object to modify.
+ * :param pkgconf_pkg_t* pkg: The package object to remove from the client object's cache.
+ * :return: nothing
+ */
+void
+pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+ if (pkg == NULL)
+ return;
+
+ PKGCONF_TRACE(client, "removed @%p from cache", pkg);
+
+ pkgconf_node_delete(&pkg->cache_iter, &client->pkg_cache);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_cache_free(pkgconf_client_t *client)
+ *
+ * Releases all resources related to a client object's package cache.
+ * This function should only be called to clear a client object's package cache,
+ * as it may release any package in the cache.
+ *
+ * :param pkgconf_client_t* client: The client object to modify.
+ */
+void
+pkgconf_cache_free(pkgconf_client_t *client)
+{
+ pkgconf_node_t *iter, *iter2;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(client->pkg_cache.head, iter2, iter)
+ {
+ pkgconf_pkg_t *pkg = iter->data;
+ pkgconf_pkg_free(client, pkg);
+ }
+
+ memset(&client->pkg_cache, 0, sizeof client->pkg_cache);
+
+ PKGCONF_TRACE(client, "cleared package cache");
+}
diff --git a/libpkgconf/client.c b/libpkgconf/client.c
new file mode 100644
index 0000000..044a589
--- /dev/null
+++ b/libpkgconf/client.c
@@ -0,0 +1,570 @@
+/*
+ * 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;
+
+ // Workaround MinGW bug which doesn't recognize 'z' length modifier
+ // (despite the presence of -std=C99 option) which leads to crash.
+ //
+ // This indeed is an issue with an outdated Windows run-time
+ // (msvcrt.dll) where MinGW delegates the function call. The modifier
+ // isn't mentioned in the documentation for Visual Studio 2015, but is
+ // for the later one.
+ //
+ // Note that the issue is already reported to pkgconf developers
+ // (issue #125) and hopefully will be workarounded.
+ //
+ // The original line:
+ //
+ // len = snprintf(errbuf, sizeof errbuf, "%s:%zu [%s]: ", filename, lineno, funcname);
+ //
+ len = snprintf(errbuf, sizeof errbuf, "%s:%lu [%s]: ",
+ filename, (unsigned long)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/config.h b/libpkgconf/config.h
new file mode 100644
index 0000000..00ff8cb
--- /dev/null
+++ b/libpkgconf/config.h
@@ -0,0 +1,37 @@
+/* file : libpkgconf/config.h -*- C -*-
+ * copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+ * license : ISC; see accompanying COPYING file
+ */
+
+/*
+ * Defines PACKAGE_VERSION.
+ */
+#include <libpkgconf/version.h>
+
+/*
+ * strndup() is not present on Windows, for gcc and clang if compile with
+ * -std=C99, except for MacOS.
+ */
+#if defined (__apple_build_version__)
+# define HAVE_STRNDUP 1
+#endif
+
+#if defined(__FreeBSD__) || defined(__apple_build_version__)
+# define HAVE_STRLCPY 1
+# define HAVE_STRLCAT 1
+#endif
+
+/*
+ * Let's assume cygwin_conv_path() is always available if compile with MSYS
+ * gcc.
+ */
+#if defined(__MSYS__)
+# define HAVE_CYGWIN_CONV_PATH 1
+#endif
+
+/*
+ * We don't consider such an outdated environments.
+ */
+#define HAVE_SYS_STAT_H 1
+
+#define PACKAGE_BUGREPORT "http://github.com/pkgconf/pkgconf/issues"
diff --git a/libpkgconf/dependency.c b/libpkgconf/dependency.c
new file mode 100644
index 0000000..3b1503c
--- /dev/null
+++ b/libpkgconf/dependency.c
@@ -0,0 +1,320 @@
+/*
+ * dependency.c
+ * dependency parsing and management
+ *
+ * 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/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `dependency` module
+ * ==============================
+ *
+ * The `dependency` module provides support for building `dependency lists` (the basic component of the overall `dependency graph`) and
+ * `dependency nodes` which store dependency information.
+ */
+
+typedef enum {
+ OUTSIDE_MODULE = 0,
+ INSIDE_MODULE_NAME = 1,
+ BEFORE_OPERATOR = 2,
+ INSIDE_OPERATOR = 3,
+ AFTER_OPERATOR = 4,
+ INSIDE_VERSION = 5
+} parse_state_t;
+
+#define DEBUG_PARSE 0
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_dependency_to_str(const pkgconf_dependency_t *dep)
+ *
+ * Renders a dependency to a string.
+ *
+ * :param pkgconf_dependency_t* dep: The dependency to render.
+ * :return: The dependency rendered as a string.
+ * :rtype: const char *
+ */
+const char *
+pkgconf_dependency_to_str(const pkgconf_dependency_t *dep)
+{
+ static char outbuf[PKGCONF_BUFSIZE];
+
+ pkgconf_strlcpy(outbuf, dep->package, sizeof outbuf);
+ if (dep->version != NULL)
+ {
+ pkgconf_strlcat(outbuf, " ", sizeof outbuf);
+ pkgconf_strlcat(outbuf, pkgconf_pkg_get_comparator(dep), sizeof outbuf);
+ pkgconf_strlcat(outbuf, " ", sizeof outbuf);
+ pkgconf_strlcat(outbuf, dep->version, sizeof outbuf);
+ }
+
+ return outbuf;
+}
+
+static inline pkgconf_dependency_t *
+pkgconf_dependency_addraw(const pkgconf_client_t *client, pkgconf_list_t *list, const char *package, size_t package_sz, const char *version, size_t version_sz, pkgconf_pkg_comparator_t compare)
+{
+ pkgconf_dependency_t *dep;
+
+ dep = calloc(sizeof(pkgconf_dependency_t), 1);
+ dep->package = pkgconf_strndup(package, package_sz);
+
+ if (version_sz != 0)
+ dep->version = pkgconf_strndup(version, version_sz);
+
+ dep->compare = compare;
+
+ PKGCONF_TRACE(client, "added dependency [%s] to list @%p", pkgconf_dependency_to_str(dep), list);
+ pkgconf_node_insert_tail(&dep->iter, dep, list);
+
+ return dep;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare)
+ *
+ * Adds a parsed dependency to a dependency list as a dependency node.
+ *
+ * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
+ * :param pkgconf_list_t* list: The dependency list to add a dependency node to.
+ * :param char* package: The package `atom` to set on the dependency node.
+ * :param char* version: The package `version` to set on the dependency node.
+ * :param pkgconf_pkg_comparator_t compare: The comparison operator to set on the dependency node.
+ * :return: A dependency node.
+ * :rtype: pkgconf_dependency_t *
+ */
+pkgconf_dependency_t *
+pkgconf_dependency_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare)
+{
+ if (version != NULL)
+ return pkgconf_dependency_addraw(client, list, package, strlen(package), version, strlen(version), compare);
+
+ return pkgconf_dependency_addraw(client, list, package, strlen(package), NULL, 0, compare);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail)
+ *
+ * Adds a dependency node to a pre-existing dependency list.
+ *
+ * :param pkgconf_list_t* list: The dependency list to add a dependency node to.
+ * :param pkgconf_dependency_t* tail: The dependency node to add to the tail of the dependency list.
+ * :return: nothing
+ */
+void
+pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail)
+{
+ pkgconf_node_insert_tail(&tail->iter, tail, list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_dependency_free(pkgconf_list_t *list)
+ *
+ * Release a dependency list and it's child dependency nodes.
+ *
+ * :param pkgconf_list_t* list: The dependency list to release.
+ * :return: nothing
+ */
+void
+pkgconf_dependency_free(pkgconf_list_t *list)
+{
+ pkgconf_node_t *node, *next;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
+ {
+ pkgconf_dependency_t *dep = node->data;
+
+ if (dep->package != NULL)
+ free(dep->package);
+
+ if (dep->version != NULL)
+ free(dep->version);
+
+ free(dep);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends)
+ *
+ * Parse a dependency declaration into a dependency list.
+ * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed
+ * into ``, zlib``.
+ *
+ * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
+ * :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes.
+ * :param char* depends: The dependency data to parse.
+ * :return: nothing
+ */
+void
+pkgconf_dependency_parse_str(const pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends)
+{
+ parse_state_t state = OUTSIDE_MODULE;
+ pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY;
+ char cmpname[PKGCONF_BUFSIZE];
+ char buf[PKGCONF_BUFSIZE];
+ size_t package_sz = 0, version_sz = 0;
+ char *start = buf;
+ char *ptr = buf;
+ char *vstart = NULL;
+ char *package = NULL, *version = NULL;
+ char *cnameptr = cmpname;
+
+ memset(cmpname, '\0', sizeof cmpname);
+
+ pkgconf_strlcpy(buf, depends, sizeof buf);
+ pkgconf_strlcat(buf, " ", sizeof buf);
+
+ while (*ptr)
+ {
+ switch (state)
+ {
+ case OUTSIDE_MODULE:
+ if (!PKGCONF_IS_MODULE_SEPARATOR(*ptr))
+ state = INSIDE_MODULE_NAME;
+
+ break;
+
+ case INSIDE_MODULE_NAME:
+ if (isspace((unsigned int)*ptr))
+ {
+ const char *sptr = ptr;
+
+ while (*sptr && isspace((unsigned int)*sptr))
+ sptr++;
+
+ if (*sptr == '\0')
+ state = OUTSIDE_MODULE;
+ else if (PKGCONF_IS_MODULE_SEPARATOR(*sptr))
+ state = OUTSIDE_MODULE;
+ else if (PKGCONF_IS_OPERATOR_CHAR(*sptr))
+ state = BEFORE_OPERATOR;
+ else
+ state = OUTSIDE_MODULE;
+ }
+ else if (PKGCONF_IS_MODULE_SEPARATOR(*ptr))
+ state = OUTSIDE_MODULE;
+ else if (*(ptr + 1) == '\0')
+ {
+ ptr++;
+ state = OUTSIDE_MODULE;
+ }
+
+ if (state != INSIDE_MODULE_NAME && start != ptr)
+ {
+ char *iter = start;
+
+ while (PKGCONF_IS_MODULE_SEPARATOR(*iter))
+ iter++;
+
+ package = iter;
+ package_sz = ptr - iter;
+ start = ptr;
+ }
+
+ if (state == OUTSIDE_MODULE)
+ {
+ pkgconf_dependency_addraw(client, deplist_head, package, package_sz, NULL, 0, compare);
+
+ compare = PKGCONF_CMP_ANY;
+ package_sz = 0;
+ }
+
+ break;
+
+ case BEFORE_OPERATOR:
+ if (PKGCONF_IS_OPERATOR_CHAR(*ptr))
+ {
+ state = INSIDE_OPERATOR;
+ *cnameptr++ = *ptr;
+ }
+
+ break;
+
+ case INSIDE_OPERATOR:
+ if (!PKGCONF_IS_OPERATOR_CHAR(*ptr))
+ {
+ state = AFTER_OPERATOR;
+ compare = pkgconf_pkg_comparator_lookup_by_name(cmpname);
+ }
+ else
+ *cnameptr++ = *ptr;
+
+ break;
+
+ case AFTER_OPERATOR:
+ if (!isspace((unsigned int)*ptr))
+ {
+ vstart = ptr;
+ state = INSIDE_VERSION;
+ }
+ break;
+
+ case INSIDE_VERSION:
+ if (PKGCONF_IS_MODULE_SEPARATOR(*ptr) || *(ptr + 1) == '\0')
+ {
+ version = vstart;
+ version_sz = ptr - vstart;
+ state = OUTSIDE_MODULE;
+
+ pkgconf_dependency_addraw(client, deplist_head, package, package_sz, version, version_sz, compare);
+
+ compare = PKGCONF_CMP_ANY;
+ cnameptr = cmpname;
+ memset(cmpname, 0, sizeof cmpname);
+ package_sz = 0;
+ }
+
+ if (state == OUTSIDE_MODULE)
+ start = ptr;
+ break;
+ }
+
+ ptr++;
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends)
+ *
+ * Preprocess dependency data and then process that dependency declaration into a dependency list.
+ * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed
+ * into ``, zlib``.
+ *
+ * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
+ * :param pkgconf_pkg_t* pkg: The package object that owns this dependency list.
+ * :param pkgconf_list_t* deplist: The dependency list to populate with dependency nodes.
+ * :param char* depends: The dependency data to parse.
+ * :return: nothing
+ */
+void
+pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends)
+{
+ char *kvdepends = pkgconf_tuple_parse(client, &pkg->vars, depends);
+
+ pkgconf_dependency_parse_str(client, deplist, kvdepends);
+ free(kvdepends);
+}
diff --git a/libpkgconf/fileio.c b/libpkgconf/fileio.c
new file mode 100644
index 0000000..d1c848f
--- /dev/null
+++ b/libpkgconf/fileio.c
@@ -0,0 +1,120 @@
+/*
+ * fileio.c
+ * File reading utilities
+ *
+ * Copyright (c) 2012 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>
+
+char *
+pkgconf_fgetline(char *line, size_t size, FILE *stream)
+{
+ char *s = line;
+ char *end = line + size - 1;
+ bool quoted = false;
+ int c = '\0', c2;
+
+ if (s == NULL)
+ return NULL;
+
+ while (s < end && (c = getc(stream)) != EOF)
+ {
+ if (c == '\\')
+ {
+ quoted = true;
+ continue;
+ }
+ else if (c == '#')
+ {
+ if (!quoted) {
+ /* Skip the rest of the line */
+ do {
+ c = getc(stream);
+ } while (c != '\n' && c != EOF);
+ *s++ = c;
+ break;
+ }
+ quoted = false;
+ continue;
+ }
+ else if (c == '\n')
+ {
+ if (quoted)
+ {
+ /* Trim spaces */
+ do {
+ c2 = getc(stream);
+ } while (c2 == '\t' || c2 == ' ');
+
+ ungetc(c2, stream);
+
+ quoted = false;
+ continue;
+ }
+ else
+ {
+ *s++ = c;
+ }
+
+ break;
+ }
+ else if (c == '\r')
+ {
+ *s++ = '\n';
+
+ if ((c2 = getc(stream)) == '\n')
+ {
+ if (quoted)
+ {
+ quoted = false;
+ continue;
+ }
+
+ break;
+ }
+
+ ungetc(c2, stream);
+
+ if (quoted)
+ {
+ quoted = false;
+ continue;
+ }
+
+ break;
+ }
+ else
+ {
+ if (quoted) {
+ *s++ = '\\';
+ quoted = false;
+ }
+ *s++ = c;
+ }
+
+ }
+
+ if (c == EOF && (s == line || ferror(stream)))
+ return NULL;
+
+ *s = '\0';
+
+ /* Remove newline character. */
+ if (s > line && *(--s) == '\n') {
+ *s = '\0';
+
+ if (s > line && *(--s) == '\r')
+ *s = '\0';
+ }
+
+ return line;
+}
diff --git a/libpkgconf/fragment.c b/libpkgconf/fragment.c
new file mode 100644
index 0000000..9a4bc31
--- /dev/null
+++ b/libpkgconf/fragment.c
@@ -0,0 +1,626 @@
+/*
+ * fragment.c
+ * Management of fragment lists.
+ *
+ * Copyright (c) 2012, 2013, 2014 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>
+
+/*
+ * !doc
+ *
+ * libpkgconf `fragment` module
+ * ============================
+ *
+ * The `fragment` module provides low-level management and rendering of fragment lists. A
+ * `fragment list` contains various `fragments` of text (such as ``-I /usr/include``) in a matter
+ * which is composable, mergeable and reorderable.
+ */
+
+struct pkgconf_fragment_check {
+ char *token;
+ size_t len;
+};
+
+static inline bool
+pkgconf_fragment_is_unmergeable(const char *string)
+{
+ static struct pkgconf_fragment_check check_fragments[] = {
+ {"-framework", 10},
+ {"-isystem", 8},
+ {"-idirafter", 10},
+ {"-pthread", 8},
+ {"-Wa,", 4},
+ {"-Wl,", 4},
+ {"-Wp,", 4},
+ {"-trigraphs", 10},
+ {"-pedantic", 9},
+ {"-ansi", 5},
+ {"-std=", 5},
+ {"-stdlib=", 8},
+ {"-include", 8},
+ {"-nostdinc", 9},
+ {"-nostdlibinc", 12},
+ {"-nobuiltininc", 13},
+ };
+
+ if (*string != '-')
+ return true;
+
+ for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
+ if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
+ return true;
+
+ /* only one pair of {-flag, arg} may be merged together */
+ if (strchr(string, ' ') != NULL)
+ return false;
+
+ return false;
+}
+
+static inline bool
+pkgconf_fragment_should_munge(const char *string, const char *sysroot_dir)
+{
+ static struct pkgconf_fragment_check check_fragments[] = {
+ {"-isystem", 8},
+ {"-idirafter", 10},
+ {"-include", 8},
+ };
+
+ if (*string != '/')
+ return false;
+
+ if (sysroot_dir != NULL && strncmp(sysroot_dir, string, strlen(sysroot_dir)))
+ return true;
+
+ for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
+ if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
+ return true;
+
+ return false;
+}
+
+static inline bool
+pkgconf_fragment_is_special(const char *string)
+{
+ if (*string != '-')
+ return true;
+
+ if (!strncmp(string, "-lib:", 5))
+ return true;
+
+ return pkgconf_fragment_is_unmergeable(string);
+}
+
+static inline void
+pkgconf_fragment_munge(const pkgconf_client_t *client, char *buf, size_t buflen, const char *source, const char *sysroot_dir)
+{
+ *buf = '\0';
+
+ if (sysroot_dir == NULL)
+ sysroot_dir = pkgconf_tuple_find_global(client, "pc_sysrootdir");
+
+ if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir))
+ pkgconf_strlcat(buf, sysroot_dir, buflen);
+
+ pkgconf_strlcat(buf, source, buflen);
+
+ if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS))
+ pkgconf_path_relocate(buf, buflen);
+}
+
+static inline char *
+pkgconf_fragment_copy_munged(const pkgconf_client_t *client, const char *source)
+{
+ char mungebuf[PKGCONF_BUFSIZE];
+ pkgconf_fragment_munge(client, mungebuf, sizeof mungebuf, source, client->sysroot_dir);
+ return strdup(mungebuf);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string)
+ *
+ * Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param pkgconf_list_t* list: The fragment list.
+ * :param char* string: The string of text to add as a fragment to the fragment list.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string)
+{
+ pkgconf_fragment_t *frag;
+
+ if (*string == '\0')
+ return;
+
+ if (!pkgconf_fragment_is_special(string))
+ {
+ frag = calloc(sizeof(pkgconf_fragment_t), 1);
+
+ frag->type = *(string + 1);
+ frag->data = pkgconf_fragment_copy_munged(client, string + 2);
+
+ PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list);
+ }
+ else
+ {
+ char mungebuf[PKGCONF_BUFSIZE];
+
+ if (list->tail != NULL && list->tail->data != NULL)
+ {
+ pkgconf_fragment_t *parent = list->tail->data;
+
+ /* only attempt to merge 'special' fragments together */
+ if (!parent->type && pkgconf_fragment_is_unmergeable(parent->data))
+ {
+ size_t len;
+ char *newdata;
+
+ pkgconf_fragment_munge(client, mungebuf, sizeof mungebuf, string, NULL);
+
+ len = strlen(parent->data) + strlen(mungebuf) + 2;
+ newdata = malloc(len);
+
+ pkgconf_strlcpy(newdata, parent->data, len);
+ pkgconf_strlcat(newdata, " ", len);
+ pkgconf_strlcat(newdata, mungebuf, len);
+
+ PKGCONF_TRACE(client, "merging '%s' to '%s' to form fragment {'%s'} in list @%p", mungebuf, parent->data, newdata, list);
+
+ free(parent->data);
+ parent->data = newdata;
+
+ /* use a copy operation to force a dedup */
+ pkgconf_node_delete(&parent->iter, list);
+ pkgconf_fragment_copy(client, list, parent, false);
+
+ /* the fragment list now (maybe) has the copied node, so free the original */
+ free(parent->data);
+ free(parent);
+
+ return;
+ }
+ }
+
+ frag = calloc(sizeof(pkgconf_fragment_t), 1);
+
+ frag->type = 0;
+ frag->data = strdup(string);
+
+ PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, list);
+ }
+
+ pkgconf_node_insert_tail(&frag->iter, frag, list);
+}
+
+static inline pkgconf_fragment_t *
+pkgconf_fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_t *base)
+{
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY_REVERSE(list->tail, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+
+ if (base->type != frag->type)
+ continue;
+
+ if (!strcmp(base->data, frag->data))
+ return frag;
+ }
+
+ return NULL;
+}
+
+static inline bool
+pkgconf_fragment_can_merge_back(const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
+{
+ (void) flags;
+
+ if (base->type == 'l')
+ {
+ if (is_private)
+ return false;
+
+ return true;
+ }
+
+ if (base->type == 'F')
+ return false;
+ if (base->type == 'L')
+ return false;
+ if (base->type == 'I')
+ return false;
+
+ return true;
+}
+
+static inline bool
+pkgconf_fragment_can_merge(const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
+{
+ (void) flags;
+
+ if (is_private)
+ return false;
+
+ return pkgconf_fragment_is_unmergeable(base->data);
+}
+
+static inline pkgconf_fragment_t *
+pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
+{
+ if (!pkgconf_fragment_can_merge_back(base, flags, is_private))
+ return NULL;
+
+ if (!pkgconf_fragment_can_merge(base, flags, is_private))
+ return NULL;
+
+ return pkgconf_fragment_lookup(list, base);
+}
+
+static inline bool
+pkgconf_fragment_should_merge(const pkgconf_fragment_t *base)
+{
+ const pkgconf_fragment_t *parent;
+
+ /* if we are the first fragment, that means the next fragment is the same, so it's always safe. */
+ if (base->iter.prev == NULL)
+ return true;
+
+ /* this really shouldn't ever happen, but handle it */
+ parent = base->iter.prev->data;
+ if (parent == NULL)
+ return true;
+
+ switch (parent->type)
+ {
+ case 'l':
+ case 'L':
+ case 'I':
+ return true;
+ default:
+ return !base->type || parent->type == base->type;
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag)
+ *
+ * Checks if a `fragment` contains a `system path`. System paths are detected at compile time and optionally overridden by
+ * the ``PKG_CONFIG_SYSTEM_INCLUDE_PATH`` and ``PKG_CONFIG_SYSTEM_LIBRARY_PATH`` environment variables.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object the fragment belongs to.
+ * :param pkgconf_fragment_t* frag: The fragment being checked.
+ * :return: true if the fragment contains a system path, else false
+ * :rtype: bool
+ */
+bool
+pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag)
+{
+ const pkgconf_list_t *check_paths = NULL;
+
+ switch (frag->type)
+ {
+ case 'L':
+ check_paths = &client->filter_libdirs;
+ break;
+ case 'I':
+ check_paths = &client->filter_includedirs;
+ break;
+ default:
+ return false;
+ }
+
+ return pkgconf_path_match_list(frag->data, check_paths);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private)
+ *
+ * Copies a `fragment` to another `fragment list`, possibly removing a previous copy of the `fragment`
+ * in a process known as `mergeback`.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param pkgconf_list_t* list: The list the fragment is being added to.
+ * :param pkgconf_fragment_t* base: The fragment being copied.
+ * :param bool is_private: Whether the fragment list is a `private` fragment list (static linking).
+ * :return: nothing
+ */
+void
+pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private)
+{
+ pkgconf_fragment_t *frag;
+
+ if ((frag = pkgconf_fragment_exists(list, base, client->flags, is_private)) != NULL)
+ {
+ if (pkgconf_fragment_should_merge(frag))
+ pkgconf_fragment_delete(list, frag);
+ }
+ else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (pkgconf_fragment_lookup(list, base) != NULL))
+ return;
+
+ frag = calloc(sizeof(pkgconf_fragment_t), 1);
+
+ frag->type = base->type;
+ frag->data = strdup(base->data);
+
+ pkgconf_node_insert_tail(&frag->iter, frag, list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func)
+ *
+ * Copies a `fragment list` to another `fragment list` which match a user-specified filtering function.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param pkgconf_list_t* dest: The destination list.
+ * :param pkgconf_list_t* src: The source list.
+ * :param pkgconf_fragment_filter_func_t filter_func: The filter function to use.
+ * :param void* data: Optional data to pass to the filter function.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data)
+{
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(src->head, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+
+ if (filter_func(client, frag, data))
+ pkgconf_fragment_copy(client, dest, frag, true);
+ }
+}
+
+static inline char *
+fragment_escape(const char *src)
+{
+ ssize_t outlen = strlen(src) + 10;
+ char *out = calloc(outlen, 1);
+ char *dst = out;
+
+ while (*src)
+ {
+ if (((*src < ' ') ||
+ (*src > ' ' && *src < '$') ||
+ (*src > '$' && *src < '(') ||
+ (*src > ')' && *src < '+') ||
+ (*src > ':' && *src < '=') ||
+ (*src > '=' && *src < '@') ||
+ (*src > 'Z' && *src < '^') ||
+ (*src == '`') ||
+ (*src > 'z' && *src < '~') ||
+ (*src > '~')) && *src != '\\')
+ *dst++ = '\\';
+
+ *dst++ = *src++;
+
+ if ((ptrdiff_t)(dst - out) + 2 > outlen)
+ {
+ outlen *= 2;
+ out = realloc(out, outlen);
+ }
+ }
+
+ *dst = 0;
+ return out;
+}
+
+static inline size_t
+pkgconf_fragment_len(const pkgconf_fragment_t *frag, bool escape)
+{
+ size_t len = 1;
+
+ if (frag->type)
+ len += 2;
+
+ if (frag->data != NULL)
+ {
+ if (!escape)
+ len += strlen(frag->data);
+ else
+ {
+ char *tmp = fragment_escape(frag->data);
+ len += strlen(tmp);
+ free(tmp);
+ }
+ }
+
+ return len;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: size_t pkgconf_fragment_render_len(const pkgconf_list_t *list)
+ *
+ * Calculates the required memory to store a `fragment list` when rendered as a string.
+ *
+ * :param pkgconf_list_t* list: The `fragment list` being rendered.
+ * :param bool escape: Whether or not to escape special shell characters.
+ * :return: the amount of bytes required to represent the `fragment list` when rendered
+ * :rtype: size_t
+ */
+size_t
+pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape)
+{
+ size_t out = 1; /* trailing nul */
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+ {
+ const pkgconf_fragment_t *frag = node->data;
+ out += pkgconf_fragment_len(frag, escape);
+ }
+
+ return out;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen)
+ *
+ * Renders a `fragment list` into a buffer.
+ *
+ * :param pkgconf_list_t* list: The `fragment list` being rendered.
+ * :param char* buf: The buffer to render the fragment list into.
+ * :param size_t buflen: The length of the buffer.
+ * :param bool escape: Whether or not to escape special shell characters.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape)
+{
+ pkgconf_node_t *node;
+ char *bptr = buf;
+
+ memset(buf, 0, buflen);
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+ {
+ const pkgconf_fragment_t *frag = node->data;
+ size_t buf_remaining = buflen - (bptr - buf);
+
+ if (pkgconf_fragment_len(frag, escape) > buf_remaining)
+ break;
+
+ if (frag->type)
+ {
+ *bptr++ = '-';
+ *bptr++ = frag->type;
+ }
+
+ if (frag->data)
+ {
+ if (!escape)
+ bptr += pkgconf_strlcpy(bptr, frag->data, buf_remaining);
+ else
+ {
+ char *tmp = fragment_escape(frag->data);
+ bptr += pkgconf_strlcpy(bptr, tmp, buf_remaining);
+ free(tmp);
+ }
+ }
+
+ *bptr++ = ' ';
+ }
+
+ *bptr = '\0';
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *pkgconf_fragment_render(const pkgconf_list_t *list)
+ *
+ * Allocate memory and render a `fragment list` into it.
+ *
+ * :param pkgconf_list_t* list: The `fragment list` being rendered.
+ * :param bool escape: Whether or not to escape special shell characters.
+ * :return: An allocated string containing the rendered `fragment list`.
+ * :rtype: char *
+ */
+char *
+pkgconf_fragment_render(const pkgconf_list_t *list, bool escape)
+{
+ size_t buflen = pkgconf_fragment_render_len(list, escape);
+ char *buf = calloc(1, buflen);
+
+ pkgconf_fragment_render_buf(list, buf, buflen, escape);
+
+ return buf;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node)
+ *
+ * Delete a `fragment node` from a `fragment list`.
+ *
+ * :param pkgconf_list_t* list: The `fragment list` to delete from.
+ * :param pkgconf_fragment_t* node: The `fragment node` to delete.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node)
+{
+ pkgconf_node_delete(&node->iter, list);
+
+ free(node->data);
+ free(node);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_free(pkgconf_list_t *list)
+ *
+ * Delete an entire `fragment list`.
+ *
+ * :param pkgconf_list_t* list: The `fragment list` to delete.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_free(pkgconf_list_t *list)
+{
+ pkgconf_node_t *node, *next;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
+ {
+ pkgconf_fragment_t *frag = node->data;
+
+ free(frag->data);
+ free(frag);
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value)
+ *
+ * Parse a string into a `fragment list`.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param pkgconf_list_t* list: The `fragment list` to add the fragment entries to.
+ * :param pkgconf_list_t* vars: A list of variables to use for variable substitution.
+ * :param char* value: The string to parse into fragments.
+ * :return: nothing
+ */
+void
+pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value)
+{
+ int i, argc;
+ char **argv;
+ char *repstr = pkgconf_tuple_parse(client, vars, value);
+
+ pkgconf_argv_split(repstr, &argc, &argv);
+
+ for (i = 0; i < argc; i++)
+ pkgconf_fragment_add(client, list, argv[i]);
+
+ pkgconf_argv_free(argv);
+ free(repstr);
+}
diff --git a/libpkgconf/iter.h b/libpkgconf/iter.h
new file mode 100644
index 0000000..59cfac3
--- /dev/null
+++ b/libpkgconf/iter.h
@@ -0,0 +1,97 @@
+/*
+ * iter.h
+ * Linked lists and iterators.
+ *
+ * Copyright (c) 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.
+ */
+
+#ifndef PKGCONF__ITER_H
+#define PKGCONF__ITER_H
+
+typedef struct pkgconf_node_ pkgconf_node_t;
+
+struct pkgconf_node_ {
+ pkgconf_node_t *prev, *next;
+ void *data;
+};
+
+typedef struct {
+ pkgconf_node_t *head, *tail;
+ size_t length;
+} pkgconf_list_t;
+
+#define PKGCONF_LIST_INITIALIZER { NULL, NULL, 0 }
+
+static inline void
+pkgconf_node_insert(pkgconf_node_t *node, void *data, pkgconf_list_t *list)
+{
+ pkgconf_node_t *tnode;
+
+ node->data = data;
+
+ if (list->head == NULL)
+ {
+ list->head = node;
+ list->tail = node;
+ list->length = 1;
+ return;
+ }
+
+ tnode = list->head;
+
+ node->next = tnode;
+ tnode->prev = node;
+
+ list->head = node;
+ list->length++;
+}
+
+static inline void
+pkgconf_node_insert_tail(pkgconf_node_t *node, void *data, pkgconf_list_t *list)
+{
+ pkgconf_node_t *tnode;
+
+ node->data = data;
+
+ if (list->tail == NULL)
+ {
+ list->head = node;
+ list->tail = node;
+ list->length = 1;
+ return;
+ }
+
+ tnode = list->tail;
+
+ node->prev = tnode;
+ tnode->next = node;
+
+ list->tail = node;
+ list->length++;
+}
+
+static inline void
+pkgconf_node_delete(pkgconf_node_t *node, pkgconf_list_t *list)
+{
+ list->length--;
+
+ if (node->prev == NULL)
+ list->head = node->next;
+ else
+ node->prev->next = node->next;
+
+ if (node->next == NULL)
+ list->tail = node->prev;
+ else
+ node->next->prev = node->prev;
+}
+
+#endif
diff --git a/libpkgconf/libpkgconf-api.h b/libpkgconf/libpkgconf-api.h
new file mode 100644
index 0000000..1c4fb73
--- /dev/null
+++ b/libpkgconf/libpkgconf-api.h
@@ -0,0 +1,20 @@
+#ifndef PKGCONFG_API
+#define PKGCONFG_API
+
+/* Makefile.am specifies visibility using the libtool option -export-symbols-regex '^pkgconf_'
+ * Unfortunately, that is not available when building with cmake, so use attributes instead,
+ * in a way that doesn't depend on any cmake magic.
+ */
+#if defined(PKGCONFIG_IS_STATIC)
+# define PKGCONF_API
+#elif defined(_WIN32) || defined(_WIN64)
+# ifdef LIBPKGCONF_EXPORT
+# define PKGCONF_API __declspec(dllexport)
+# else
+# define PKGCONF_API __declspec(dllimport)
+# endif
+#else
+# define PKGCONF_API __attribute__((visibility("default")))
+#endif
+
+#endif
diff --git a/libpkgconf/libpkgconf.h b/libpkgconf/libpkgconf.h
new file mode 100644
index 0000000..8a0031e
--- /dev/null
+++ b/libpkgconf/libpkgconf.h
@@ -0,0 +1,319 @@
+/*
+ * libpkgconf.h
+ * Global include file for everything in libpkgconf.
+ *
+ * Copyright (c) 2011, 2015 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.
+ */
+
+#ifndef LIBPKGCONF__LIBPKGCONF_H
+#define LIBPKGCONF__LIBPKGCONF_H
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/iter.h>
+#include <libpkgconf/bsdstubs.h>
+#include <libpkgconf/libpkgconf-api.h>
+
+/* pkg-config uses ';' on win32 as ':' is part of path */
+#ifdef _WIN32
+#define PKG_CONFIG_PATH_SEP_S ";"
+#else
+#define PKG_CONFIG_PATH_SEP_S ":"
+#endif
+
+#ifdef _WIN32
+#define PKG_DIR_SEP_S '\\'
+#else
+#define PKG_DIR_SEP_S '/'
+#endif
+
+#define PKGCONF_BUFSIZE (65535)
+
+typedef enum {
+ PKGCONF_CMP_NOT_EQUAL,
+ PKGCONF_CMP_ANY,
+ PKGCONF_CMP_LESS_THAN,
+ PKGCONF_CMP_LESS_THAN_EQUAL,
+ PKGCONF_CMP_EQUAL,
+ PKGCONF_CMP_GREATER_THAN,
+ PKGCONF_CMP_GREATER_THAN_EQUAL
+} pkgconf_pkg_comparator_t;
+
+#define PKGCONF_CMP_COUNT 7
+
+typedef struct pkgconf_pkg_ pkgconf_pkg_t;
+typedef struct pkgconf_dependency_ pkgconf_dependency_t;
+typedef struct pkgconf_tuple_ pkgconf_tuple_t;
+typedef struct pkgconf_fragment_ pkgconf_fragment_t;
+typedef struct pkgconf_path_ pkgconf_path_t;
+typedef struct pkgconf_client_ pkgconf_client_t;
+
+#define PKGCONF_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define PKGCONF_FOREACH_LIST_ENTRY(head, value) \
+ for ((value) = (head); (value) != NULL; (value) = (value)->next)
+
+#define PKGCONF_FOREACH_LIST_ENTRY_SAFE(head, nextiter, value) \
+ for ((value) = (head), (nextiter) = (head) != NULL ? (head)->next : NULL; (value) != NULL; (value) = (nextiter), (nextiter) = (nextiter) != NULL ? (nextiter)->next : NULL)
+
+#define PKGCONF_FOREACH_LIST_ENTRY_REVERSE(tail, value) \
+ for ((value) = (tail); (value) != NULL; (value) = (value)->prev)
+
+struct pkgconf_fragment_ {
+ pkgconf_node_t iter;
+
+ char type;
+ char *data;
+};
+
+struct pkgconf_dependency_ {
+ pkgconf_node_t iter;
+
+ char *package;
+ pkgconf_pkg_comparator_t compare;
+ char *version;
+ pkgconf_pkg_t *parent;
+};
+
+struct pkgconf_tuple_ {
+ pkgconf_node_t iter;
+
+ char *key;
+ char *value;
+};
+
+struct pkgconf_path_ {
+ pkgconf_node_t lnode;
+
+ char *path;
+ void *handle_path;
+ void *handle_device;
+};
+
+#define PKGCONF_PKG_PROPF_NONE 0x00
+#define PKGCONF_PKG_PROPF_STATIC 0x01
+#define PKGCONF_PKG_PROPF_CACHED 0x02
+#define PKGCONF_PKG_PROPF_SEEN 0x04
+#define PKGCONF_PKG_PROPF_UNINSTALLED 0x08
+#define PKGCONF_PKG_PROPF_VIRTUAL 0x10
+
+struct pkgconf_pkg_ {
+ pkgconf_node_t cache_iter;
+
+ int refcount;
+ char *id;
+ char *filename;
+ char *realname;
+ char *version;
+ char *description;
+ char *url;
+ char *pc_filedir;
+
+ pkgconf_list_t libs;
+ pkgconf_list_t libs_private;
+ pkgconf_list_t cflags;
+ pkgconf_list_t cflags_private;
+
+ pkgconf_list_t requires;
+ pkgconf_list_t requires_private;
+ pkgconf_list_t conflicts;
+ pkgconf_list_t provides;
+
+ pkgconf_list_t vars;
+
+ unsigned int flags;
+};
+
+typedef bool (*pkgconf_pkg_iteration_func_t)(const pkgconf_pkg_t *pkg, void *data);
+typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data);
+typedef bool (*pkgconf_queue_apply_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth);
+typedef bool (*pkgconf_error_handler_func_t)(const char *msg, const pkgconf_client_t *client, const void *data);
+
+struct pkgconf_client_ {
+ pkgconf_list_t dir_list;
+ pkgconf_list_t pkg_cache;
+
+ pkgconf_list_t filter_libdirs;
+ pkgconf_list_t filter_includedirs;
+
+ pkgconf_list_t global_vars;
+
+ void *error_handler_data;
+ void *warn_handler_data;
+ void *trace_handler_data;
+
+ pkgconf_error_handler_func_t error_handler;
+ pkgconf_error_handler_func_t warn_handler;
+ pkgconf_error_handler_func_t trace_handler;
+
+ FILE *auditf;
+
+ char *sysroot_dir;
+ char *buildroot_dir;
+
+ unsigned int flags;
+
+ char *prefix_varname;
+};
+
+/* client.c */
+PKGCONF_API void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data);
+PKGCONF_API pkgconf_client_t * pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data);
+PKGCONF_API void pkgconf_client_deinit(pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_free(pkgconf_client_t *client);
+PKGCONF_API const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir);
+PKGCONF_API const char *pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir);
+PKGCONF_API unsigned int pkgconf_client_get_flags(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags);
+PKGCONF_API const char *pkgconf_client_get_prefix_varname(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname);
+PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_warn_handler(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data);
+PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_error_handler(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data);
+PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_trace_handler(const pkgconf_client_t *client);
+PKGCONF_API void pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data);
+
+#define PKGCONF_IS_MODULE_SEPARATOR(c) ((c) == ',' || isspace ((unsigned int)(c)))
+#define PKGCONF_IS_OPERATOR_CHAR(c) ((c) == '<' || (c) == '>' || (c) == '!' || (c) == '=')
+
+#define PKGCONF_PKG_PKGF_NONE 0x0000
+#define PKGCONF_PKG_PKGF_SEARCH_PRIVATE 0x0001
+#define PKGCONF_PKG_PKGF_ENV_ONLY 0x0002
+#define PKGCONF_PKG_PKGF_NO_UNINSTALLED 0x0004
+#define PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL 0x0008
+#define PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS 0x0010
+#define PKGCONF_PKG_PKGF_SKIP_CONFLICTS 0x0020
+#define PKGCONF_PKG_PKGF_NO_CACHE 0x0040
+#define PKGCONF_PKG_PKGF_SKIP_ERRORS 0x0080
+#define PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE 0x0100
+#define PKGCONF_PKG_PKGF_SKIP_PROVIDES 0x0200
+#define PKGCONF_PKG_PKGF_REDEFINE_PREFIX 0x0400
+#define PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS 0x0800
+#define PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS 0x1000
+
+#define PKGCONF_PKG_ERRF_OK 0x0
+#define PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND 0x1
+#define PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH 0x2
+#define PKGCONF_PKG_ERRF_PACKAGE_CONFLICT 0x4
+#define PKGCONF_PKG_ERRF_DEPGRAPH_BREAK 0x8
+
+/* pkg.c */
+#if defined(__GNUC__) || defined(__INTEL_COMPILER)
+#define PRINTFLIKE(fmtarg, firstvararg) \
+ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#define DEPRECATED \
+ __attribute__((deprecated))
+#else
+#define PRINTFLIKE(fmtarg, firstvararg)
+#define DEPRECATED
+#endif /* defined(__INTEL_COMPILER) || defined(__GNUC__) */
+
+PKGCONF_API bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
+PKGCONF_API bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
+PKGCONF_API bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...) PRINTFLIKE(5, 6);
+PKGCONF_API bool pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, const void *data);
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER)
+#define PKGCONF_TRACE(client, ...) do { \
+ pkgconf_trace(client, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__); \
+ } while (0);
+#else
+#define PKGCONF_TRACE(client, ...) do { \
+ pkgconf_trace(client, __FILE__, __LINE__, __func__, __VA_ARGS__); \
+ } while (0);
+#endif
+
+PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_ref(const pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+PKGCONF_API void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+PKGCONF_API void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name);
+PKGCONF_API unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth);
+PKGCONF_API unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth);
+PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags);
+PKGCONF_API const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep);
+PKGCONF_API unsigned int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth);
+PKGCONF_API unsigned int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth);
+PKGCONF_API pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name);
+PKGCONF_API pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name);
+
+PKGCONF_API int pkgconf_compare_version(const char *a, const char *b);
+PKGCONF_API pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *ptr, pkgconf_pkg_iteration_func_t func);
+PKGCONF_API void pkgconf_pkg_dir_list_build(pkgconf_client_t *client);
+
+/* parse.c */
+PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *path, FILE *f);
+PKGCONF_API void pkgconf_dependency_parse_str(const pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends);
+PKGCONF_API void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist_head, const char *depends);
+PKGCONF_API void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail);
+PKGCONF_API void pkgconf_dependency_free(pkgconf_list_t *list);
+PKGCONF_API const char *pkgconf_dependency_to_str(const pkgconf_dependency_t *dep);
+PKGCONF_API pkgconf_dependency_t *pkgconf_dependency_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare);
+
+/* argvsplit.c */
+PKGCONF_API int pkgconf_argv_split(const char *src, int *argc, char ***argv);
+PKGCONF_API void pkgconf_argv_free(char **argv);
+
+/* fragment.c */
+typedef bool (*pkgconf_fragment_filter_func_t)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data);
+PKGCONF_API void pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value);
+PKGCONF_API void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string);
+PKGCONF_API void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private);
+PKGCONF_API void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node);
+PKGCONF_API void pkgconf_fragment_free(pkgconf_list_t *list);
+PKGCONF_API void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data);
+PKGCONF_API size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape);
+PKGCONF_API void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t len, bool escape);
+PKGCONF_API char *pkgconf_fragment_render(const pkgconf_list_t *list, bool escape);
+PKGCONF_API bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag);
+
+/* fileio.c */
+PKGCONF_API char *pkgconf_fgetline(char *line, size_t size, FILE *stream);
+
+/* tuple.c */
+PKGCONF_API pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *parent, const char *key, const char *value, bool parse);
+PKGCONF_API char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key);
+PKGCONF_API char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *list, const char *value);
+PKGCONF_API void pkgconf_tuple_free(pkgconf_list_t *list);
+PKGCONF_API void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list);
+PKGCONF_API void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value);
+PKGCONF_API char *pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key);
+PKGCONF_API void pkgconf_tuple_free_global(pkgconf_client_t *client);
+PKGCONF_API void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv);
+
+/* queue.c */
+PKGCONF_API void pkgconf_queue_push(pkgconf_list_t *list, const char *package);
+PKGCONF_API bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list);
+PKGCONF_API void pkgconf_queue_free(pkgconf_list_t *list);
+PKGCONF_API bool pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data);
+PKGCONF_API bool pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, int maxdepth);
+
+/* cache.c */
+PKGCONF_API pkgconf_pkg_t *pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id);
+PKGCONF_API void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+PKGCONF_API void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+PKGCONF_API void pkgconf_cache_free(pkgconf_client_t *client);
+
+/* audit.c */
+PKGCONF_API void pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf);
+PKGCONF_API void pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
+PKGCONF_API void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode);
+
+/* path.c */
+PKGCONF_API void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter);
+PKGCONF_API size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter);
+PKGCONF_API size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter);
+PKGCONF_API bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist);
+PKGCONF_API void pkgconf_path_free(pkgconf_list_t *dirlist);
+PKGCONF_API bool pkgconf_path_relocate(char *buf, size_t buflen);
+
+#endif
diff --git a/libpkgconf/path.c b/libpkgconf/path.c
new file mode 100644
index 0000000..59e003e
--- /dev/null
+++ b/libpkgconf/path.c
@@ -0,0 +1,326 @@
+/*
+ * path.c
+ * filesystem path 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>
+
+#if defined(HAVE_CYGWIN_CONV_PATH) && defined(__MSYS__)
+# include <sys/cygwin.h>
+#endif
+
+#if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32)
+# include <sys/stat.h>
+# define PKGCONF_CACHE_INODES
+#endif
+
+static bool
+#ifdef PKGCONF_CACHE_INODES
+path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st)
+#else
+path_list_contains_entry(const char *text, pkgconf_list_t *dirlist)
+#endif
+{
+ pkgconf_node_t *n;
+
+ PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
+ {
+ pkgconf_path_t *pn = n->data;
+
+#ifdef PKGCONF_CACHE_INODES
+ if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino)
+ return true;
+#endif
+
+ if (!strcmp(text, pn->path))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * !doc
+ *
+ * libpkgconf `path` module
+ * ========================
+ *
+ * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably,
+ * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment
+ * variables.
+ */
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist)
+ *
+ * Adds a path node to a path list. If the path is already in the list, do nothing.
+ *
+ * :param char* text: The path text to add as a path node.
+ * :param pkgconf_list_t* dirlist: The path list to add the path node to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: nothing
+ */
+void
+pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ pkgconf_path_t *node;
+ char path[PKGCONF_BUFSIZE];
+
+#ifdef PKGCONF_CACHE_INODES
+ struct stat st;
+
+ if (filter)
+ {
+ if (lstat(text, &st) == -1)
+ return;
+ if (S_ISLNK(st.st_mode))
+ {
+ char linkdest[PKGCONF_BUFSIZE];
+ ssize_t len = readlink(text, linkdest, sizeof(linkdest));
+
+ if (len != -1 && (size_t)len < sizeof(linkdest) &&
+ stat(linkdest, &st) == -1)
+ return;
+ }
+ if (path_list_contains_entry(text, dirlist, &st))
+ return;
+ }
+#else
+ if (filter && path_list_contains_entry(text, dirlist))
+ return;
+#endif
+
+ pkgconf_strlcpy(path, text, sizeof path);
+ pkgconf_path_relocate(path, sizeof path);
+
+ node = calloc(sizeof(pkgconf_path_t), 1);
+ node->path = strdup(path);
+
+#ifdef PKGCONF_CACHE_INODES
+ if (filter) {
+ node->handle_path = (void *)(intptr_t) st.st_ino;
+ node->handle_device = (void *)(intptr_t) st.st_dev;
+ }
+#endif
+
+ pkgconf_node_insert_tail(&node->lnode, node, dirlist);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist)
+ *
+ * Splits a given text input and inserts paths into a path list.
+ *
+ * :param char* text: The path text to split and add as path nodes.
+ * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: number of path nodes added to the path list
+ * :rtype: size_t
+ */
+size_t
+pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter)
+{
+ size_t count = 0;
+ char *workbuf, *p, *iter;
+
+ if (text == NULL)
+ return 0;
+
+ iter = workbuf = strdup(text);
+ while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL)
+ {
+ pkgconf_path_add(p, dirlist, filter);
+
+ count++, iter = NULL;
+ }
+ free(workbuf);
+
+ return count;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist)
+ *
+ * Adds the paths specified in an environment variable to a path list. If the environment variable is not set,
+ * an optional default set of paths is added.
+ *
+ * :param char* envvarname: The environment variable to look up.
+ * :param char* fallback: The fallback paths to use if the environment variable is not set.
+ * :param pkgconf_list_t* dirlist: The path list to add the path nodes to.
+ * :param bool filter: Whether to perform duplicate filtering.
+ * :return: number of path nodes added to the path list
+ * :rtype: size_t
+ */
+size_t
+pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter)
+{
+ const char *data;
+
+ data = getenv(envvarname);
+ if (data != NULL)
+ return pkgconf_path_split(data, dirlist, filter);
+
+ if (fallback != NULL)
+ return pkgconf_path_split(fallback, dirlist, filter);
+
+ /* no fallback and no environment variable, thusly no nodes added */
+ return 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
+ *
+ * Checks whether a path has a matching prefix in a path list.
+ *
+ * :param char* path: The path to check against a path list.
+ * :param pkgconf_list_t* dirlist: The path list to check the path against.
+ * :return: true if the path list has a matching prefix, otherwise false
+ * :rtype: bool
+ */
+bool
+pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
+{
+ pkgconf_node_t *n = NULL;
+ char relocated[PKGCONF_BUFSIZE];
+ const char *cpath = path;
+
+ pkgconf_strlcpy(relocated, path, sizeof relocated);
+ if (pkgconf_path_relocate(relocated, sizeof relocated))
+ cpath = path;
+
+ PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ if (!strcmp(pnode->path, cpath))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist)
+ *
+ * Releases any path nodes attached to the given path list.
+ *
+ * :param pkgconf_list_t* dirlist: The path list to clean up.
+ * :return: nothing
+ */
+void
+pkgconf_path_free(pkgconf_list_t *dirlist)
+{
+ pkgconf_node_t *n, *tn;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n)
+ {
+ pkgconf_path_t *pnode = n->data;
+
+ free(pnode->path);
+ free(pnode);
+ }
+}
+
+static char *
+normpath(const char *path)
+{
+ if (!path)
+ return NULL;
+
+ char *copy = strdup(path);
+ if (NULL == copy)
+ return NULL;
+ char *ptr = copy;
+
+ for (int ii = 0; copy[ii]; ii++)
+ {
+ *ptr++ = path[ii];
+ if ('/' == path[ii])
+ {
+ ii++;
+ while ('/' == path[ii])
+ ii++;
+ ii--;
+ }
+ }
+ *ptr = '\0';
+
+ return copy;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen)
+ *
+ * Relocates a path, possibly calling normpath() or cygwin_conv_path() on it.
+ *
+ * :param char* buf: The path to relocate.
+ * :param size_t buflen: The buffer length the path is contained in.
+ * :return: true on success, false on error
+ * :rtype: bool
+ */
+bool
+pkgconf_path_relocate(char *buf, size_t buflen)
+{
+#if defined(HAVE_CYGWIN_CONV_PATH) && defined(__MSYS__)
+ ssize_t size;
+ char *tmpbuf, *ti;
+
+ size = cygwin_conv_path(CCP_POSIX_TO_WIN_A, buf, NULL, 0);
+ if (size < 0 || (size_t) size > buflen)
+ return false;
+
+ tmpbuf = malloc(size);
+ if (cygwin_conv_path(CCP_POSIX_TO_WIN_A, buf, tmpbuf, size))
+ return false;
+
+ pkgconf_strlcpy(buf, tmpbuf, buflen);
+ free(tmpbuf);
+
+ /* rewrite any backslash arguments for best compatibility */
+ for (ti = buf; *ti != '\0'; ti++)
+ {
+ if (*ti == '\\')
+ *ti = '/';
+ }
+#else
+ char *tmpbuf;
+
+ if ((tmpbuf = normpath(buf)) != NULL)
+ {
+ size_t tmpbuflen = strlen(tmpbuf);
+ if (tmpbuflen > buflen)
+ {
+ free(tmpbuf);
+ return false;
+ }
+
+ pkgconf_strlcpy(buf, tmpbuf, buflen);
+ free(tmpbuf);
+ }
+#endif
+
+ return true;
+}
diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c
new file mode 100644
index 0000000..bcfb486
--- /dev/null
+++ b/libpkgconf/pkg.c
@@ -0,0 +1,1597 @@
+/*
+ * 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++;
+
+ // Workaround MinGW/msvcrt issue (see the comment in client.c
+ // for details).
+ //
+ PKGCONF_TRACE(client, "%s:%lu > [%s]", filename, (unsigned long)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)
+ {
+ // Workaround MinGW/msvcrt issue (see the comment in
+ // client.c for details).
+ //
+ pkgconf_warn(client, "%s:%lu: warning: whitespace encountered while parsing key section\n",
+ pkg->filename, (unsigned long)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 == '=')
+ {
+ // Workaround MinGW/msvcrt issue (see the comment in
+ // client.c for details).
+ //
+ pkgconf_warn(client, "%s:%lu: warning: trailing whitespace encountered while parsing value section\n",
+ pkg->filename, (unsigned long)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;
+}
diff --git a/libpkgconf/queue.c b/libpkgconf/queue.c
new file mode 100644
index 0000000..227b160
--- /dev/null
+++ b/libpkgconf/queue.c
@@ -0,0 +1,194 @@
+/*
+ * queue.c
+ * compilation of a list of packages into a world dependency set
+ *
+ * Copyright (c) 2012 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>
+
+/*
+ * !doc
+ *
+ * libpkgconf `queue` module
+ * =========================
+ *
+ * The `queue` module provides an interface that allows easily building a dependency graph from an
+ * arbitrary set of dependencies. It also provides support for doing "preflight" checks on the entire
+ * dependency graph prior to working with it.
+ *
+ * Using the `queue` module functions is the recommended way of working with dependency graphs.
+ */
+
+typedef struct {
+ pkgconf_node_t iter;
+ char *package;
+} pkgconf_queue_t;
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_queue_push(pkgconf_list_t *list, const char *package)
+ *
+ * Pushes a requested dependency onto the dependency resolver's queue.
+ *
+ * :param pkgconf_list_t* list: the dependency resolution queue to add the package request to.
+ * :param char* package: the dependency atom requested
+ * :return: nothing
+ */
+void
+pkgconf_queue_push(pkgconf_list_t *list, const char *package)
+{
+ pkgconf_queue_t *pkgq = calloc(sizeof(pkgconf_queue_t), 1);
+
+ pkgq->package = strdup(package);
+ pkgconf_node_insert_tail(&pkgq->iter, pkgq, list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list)
+ *
+ * Compile a dependency resolution queue into a dependency resolution problem if possible, otherwise report an error.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_pkg_t* world: The designated root of the dependency graph.
+ * :param pkgconf_list_t* list: The list of dependency requests to consider.
+ * :return: true if the built dependency resolution problem is consistent, else false
+ * :rtype: bool
+ */
+bool
+pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list)
+{
+ pkgconf_node_t *iter;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+ {
+ pkgconf_queue_t *pkgq;
+
+ pkgq = iter->data;
+ pkgconf_dependency_parse(client, world, &world->requires, pkgq->package);
+ }
+
+ return (world->requires.head != NULL);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_queue_free(pkgconf_list_t *list)
+ *
+ * Release any memory related to a dependency resolution queue.
+ *
+ * :param pkgconf_list_t* list: The dependency resolution queue to release.
+ * :return: nothing
+ */
+void
+pkgconf_queue_free(pkgconf_list_t *list)
+{
+ pkgconf_node_t *node, *tnode;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, tnode, node)
+ {
+ pkgconf_queue_t *pkgq = node->data;
+
+ free(pkgq->package);
+ free(pkgq);
+ }
+}
+
+static inline unsigned int
+pkgconf_queue_verify(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth)
+{
+ if (!pkgconf_queue_compile(client, world, list))
+ return PKGCONF_PKG_ERRF_DEPGRAPH_BREAK;
+
+ return pkgconf_pkg_verify_graph(client, world, maxdepth);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data)
+ *
+ * Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem and
+ * feed the solution to a callback function if a complete dependency graph is found.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_list_t* list: The list of dependency requests to consider.
+ * :param pkgconf_queue_apply_func_t func: The callback function to call if a solution is found by the dependency resolver.
+ * :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited.
+ * :param void* data: An opaque pointer which is passed to the callback function.
+ * :returns: true if the dependency resolver found a solution, otherwise false.
+ * :rtype: bool
+ */
+bool
+pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data)
+{
+ pkgconf_pkg_t world = {
+ .id = "virtual:world",
+ .realname = "virtual world package",
+ .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
+ };
+
+ /* if maxdepth is one, then we will not traverse deeper than our virtual package. */
+ if (!maxdepth)
+ maxdepth = -1;
+
+ if (pkgconf_queue_verify(client, &world, list, maxdepth) != PKGCONF_PKG_ERRF_OK)
+ return false;
+
+ if (!func(client, &world, data, maxdepth))
+ {
+ pkgconf_pkg_free(client, &world);
+ return false;
+ }
+
+ pkgconf_pkg_free(client, &world);
+
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data)
+ *
+ * Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution.
+ * :param pkgconf_list_t* list: The list of dependency requests to consider.
+ * :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited.
+ * :returns: true if the dependency resolver found a solution, otherwise false.
+ * :rtype: bool
+ */
+bool
+pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, int maxdepth)
+{
+ bool retval = true;
+ pkgconf_pkg_t world = {
+ .id = "virtual:world",
+ .realname = "virtual world package",
+ .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
+ };
+
+ /* if maxdepth is one, then we will not traverse deeper than our virtual package. */
+ if (!maxdepth)
+ maxdepth = -1;
+
+ if (pkgconf_queue_verify(client, &world, list, maxdepth) != PKGCONF_PKG_ERRF_OK)
+ retval = false;
+
+ pkgconf_pkg_free(client, &world);
+
+ return retval;
+}
diff --git a/libpkgconf/stdinc.h b/libpkgconf/stdinc.h
new file mode 100644
index 0000000..d8efcf5
--- /dev/null
+++ b/libpkgconf/stdinc.h
@@ -0,0 +1,54 @@
+/*
+ * stdinc.h
+ * pull in standard headers (including portability hacks)
+ *
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef __STDINC_H
+#define __STDINC_H
+
+#define _GNU_SOURCE
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <malloc.h>
+# define PATH_DEV_NULL "nul"
+# ifndef ssize_t
+# ifndef __MINGW32__
+# include <BaseTsd.h>
+# else
+# include <basetsd.h>
+# endif
+# define ssize_t SSIZE_T
+# endif
+# ifndef __MINGW32__
+# include "win-dirent.h"
+# else
+# include <dirent.h>
+# endif
+#else
+# define PATH_DEV_NULL "/dev/null"
+# include <dirent.h>
+# include <unistd.h>
+#endif
+
+#endif
diff --git a/libpkgconf/tuple.c b/libpkgconf/tuple.c
new file mode 100644
index 0000000..e105adc
--- /dev/null
+++ b/libpkgconf/tuple.c
@@ -0,0 +1,347 @@
+/*
+ * tuple.c
+ * management of key->value tuples
+ *
+ * Copyright (c) 2011, 2012 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>
+
+/*
+ * !doc
+ *
+ * libpkgconf `tuple` module
+ * =========================
+ *
+ * The `tuple` module provides key-value mappings backed by a linked list. The key-value
+ * mapping is mainly used for variable substitution when parsing .pc files.
+ *
+ * There are two sets of mappings: a ``pkgconf_pkg_t`` specific mapping, and a `global` mapping.
+ * The `tuple` module provides convenience wrappers for managing the `global` mapping, which is
+ * attached to a given client object.
+ */
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value)
+ *
+ * Defines a global variable, replacing the previous declaration if one was set.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to modify.
+ * :param char* key: The key for the mapping (variable name).
+ * :param char* value: The value for the mapped entry.
+ * :return: nothing
+ */
+void
+pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value)
+{
+ pkgconf_tuple_add(client, &client->global_vars, key, value, false);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
+ *
+ * Looks up a global variable.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to access.
+ * :param char* key: The key or variable name to look up.
+ * :return: the contents of the variable or ``NULL``
+ * :rtype: char *
+ */
+char *
+pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
+{
+ pkgconf_node_t *node;
+
+ PKGCONF_FOREACH_LIST_ENTRY(client->global_vars.head, node)
+ {
+ pkgconf_tuple_t *tuple = node->data;
+
+ if (!strcmp(tuple->key, key))
+ return tuple->value;
+ }
+
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client)
+ *
+ * Delete all global variables associated with a pkgconf client object.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to modify.
+ * :return: nothing
+ */
+void
+pkgconf_tuple_free_global(pkgconf_client_t *client)
+{
+ pkgconf_tuple_free(&client->global_vars);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
+ *
+ * Parse and define a global variable.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to modify.
+ * :param char* kv: The variable in the form of ``key=value``.
+ * :return: nothing
+ */
+void
+pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
+{
+ char *workbuf = strdup(kv);
+ char *value;
+
+ value = strchr(workbuf, '=');
+ if (value == NULL)
+ goto out;
+
+ *value++ = '\0';
+ pkgconf_tuple_add_global(client, workbuf, value);
+out:
+ free(workbuf);
+}
+
+static void
+pkgconf_tuple_find_delete(pkgconf_list_t *list, const char *key)
+{
+ pkgconf_node_t *node, *next;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
+ {
+ pkgconf_tuple_t *tuple = node->data;
+
+ if (!strcmp(tuple->key, key))
+ {
+ pkgconf_tuple_free_entry(tuple, list);
+ return;
+ }
+ }
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse)
+ *
+ * Optionally parse and then define a variable.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to access.
+ * :param pkgconf_list_t* list: The variable list to add the new variable to.
+ * :param char* key: The name of the variable being added.
+ * :param char* value: The value of the variable being added.
+ * :param bool parse: Whether or not to parse the value for variable substitution.
+ * :return: a variable object
+ * :rtype: pkgconf_tuple_t *
+ */
+pkgconf_tuple_t *
+pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse)
+{
+ pkgconf_tuple_t *tuple = calloc(sizeof(pkgconf_tuple_t), 1);
+
+ pkgconf_tuple_find_delete(list, key);
+
+ tuple->key = strdup(key);
+ if (parse)
+ tuple->value = pkgconf_tuple_parse(client, list, value);
+ else
+ tuple->value = strdup(value);
+
+ pkgconf_node_insert(&tuple->iter, tuple, list);
+
+ return tuple;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
+ *
+ * Look up a variable in a variable list.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to access.
+ * :param pkgconf_list_t* list: The variable list to search.
+ * :param char* key: The variable name to search for.
+ * :return: the value of the variable or ``NULL``
+ * :rtype: char *
+ */
+char *
+pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
+{
+ pkgconf_node_t *node;
+ char *res;
+
+ if ((res = pkgconf_tuple_find_global(client, key)) != NULL)
+ return res;
+
+ PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+ {
+ pkgconf_tuple_t *tuple = node->data;
+
+ if (!strcmp(tuple->key, key))
+ return tuple->value;
+ }
+
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value)
+ *
+ * Parse an expression for variable substitution.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client object to access.
+ * :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list).
+ * :param char* value: The ``key=value`` string to parse.
+ * :return: the variable data with any variables substituted
+ * :rtype: char *
+ */
+char *
+pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value)
+{
+ char buf[PKGCONF_BUFSIZE];
+ const char *ptr;
+ char *bptr = buf;
+
+ if (*value == '/' && client->sysroot_dir != NULL && strncmp(value, client->sysroot_dir, strlen(client->sysroot_dir)))
+ bptr += pkgconf_strlcpy(buf, client->sysroot_dir, sizeof buf);
+
+ for (ptr = value; *ptr != '\0' && bptr - buf < PKGCONF_BUFSIZE; ptr++)
+ {
+ if (*ptr != '$' || (*ptr == '$' && *(ptr + 1) != '{'))
+ *bptr++ = *ptr;
+ else if (*(ptr + 1) == '{')
+ {
+ static char varname[PKGCONF_BUFSIZE];
+ char *vptr = varname;
+ const char *pptr;
+ char *kv, *parsekv;
+
+ *vptr = '\0';
+
+ for (pptr = ptr + 2; *pptr != '\0'; pptr++)
+ {
+ if (*pptr != '}')
+ *vptr++ = *pptr;
+ else
+ {
+ *vptr = '\0';
+ break;
+ }
+ }
+
+ ptr += (pptr - ptr);
+ kv = pkgconf_tuple_find_global(client, varname);
+ if (kv != NULL)
+ {
+ strncpy(bptr, kv, PKGCONF_BUFSIZE - (bptr - buf));
+ bptr += strlen(kv);
+ }
+ else
+ {
+ kv = pkgconf_tuple_find(client, vars, varname);
+
+ if (kv != NULL)
+ {
+ parsekv = pkgconf_tuple_parse(client, vars, kv);
+
+ strncpy(bptr, parsekv, PKGCONF_BUFSIZE - (bptr - buf));
+ bptr += strlen(parsekv);
+
+ free(parsekv);
+ }
+ }
+ }
+ }
+
+ *bptr = '\0';
+
+ /*
+ * Sigh. Somebody actually attempted to use freedesktop.org pkg-config's broken sysroot support,
+ * which was written by somebody who did not understand how sysroots are supposed to work. This
+ * results in an incorrect path being built as the sysroot will be prepended twice, once explicitly,
+ * and once by variable expansion (the pkgconf approach). We could simply make ${pc_sysrootdir} blank,
+ * but sometimes it is necessary to know the explicit sysroot path for other reasons, so we can't really
+ * do that.
+ *
+ * As a result, we check to see if ${pc_sysrootdir} is prepended as a duplicate, and if so, remove the
+ * prepend. This allows us to handle both our approach and the broken freedesktop.org implementation's
+ * approach. Because a path can be shorter than ${pc_sysrootdir}, we do some checks first to ensure it's
+ * safe to skip ahead in the string to scan for our sysroot dir.
+ *
+ * Finally, we call pkgconf_path_relocate() to clean the path of spurious elements.
+ */
+ if (*buf == '/' &&
+ client->sysroot_dir != NULL &&
+ strlen(buf) > strlen(client->sysroot_dir) &&
+ strstr(buf + strlen(client->sysroot_dir), client->sysroot_dir) != NULL)
+ {
+ char cleanpath[PKGCONF_BUFSIZE];
+
+ pkgconf_strlcpy(cleanpath, buf + strlen(client->sysroot_dir), sizeof cleanpath);
+ pkgconf_path_relocate(cleanpath, sizeof cleanpath);
+
+ return strdup(cleanpath);
+ }
+
+ return strdup(buf);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
+ *
+ * Deletes a variable object, removing it from any variable lists and releasing any memory associated
+ * with it.
+ *
+ * :param pkgconf_tuple_t* tuple: The variable object to release.
+ * :param pkgconf_list_t* list: The variable list the variable object is attached to.
+ * :return: nothing
+ */
+void
+pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
+{
+ pkgconf_node_delete(&tuple->iter, list);
+
+ free(tuple->key);
+ free(tuple->value);
+ free(tuple);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list)
+ *
+ * Deletes a variable list and any variables attached to it.
+ *
+ * :param pkgconf_list_t* list: The variable list to delete.
+ * :return: nothing
+ */
+void
+pkgconf_tuple_free(pkgconf_list_t *list)
+{
+ pkgconf_node_t *node, *next;
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
+ pkgconf_tuple_free_entry(node->data, list);
+}
diff --git a/libpkgconf/version.h.in b/libpkgconf/version.h.in
new file mode 100644
index 0000000..1ca0111
--- /dev/null
+++ b/libpkgconf/version.h.in
@@ -0,0 +1,10 @@
+/* file : libpkgconf/version.h.in -*- C -*-
+ * copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+ * license : ISC; see accompanying COPYING file
+ */
+
+#ifndef PACKAGE_VERSION // Note: using the version macro itself.
+
+#define PACKAGE_VERSION "$libpkgconf.version.project_id$"
+
+#endif // PACKAGE_VERSION
diff --git a/libpkgconf/win-dirent.h b/libpkgconf/win-dirent.h
new file mode 100644
index 0000000..ad9d2b3
--- /dev/null
+++ b/libpkgconf/win-dirent.h
@@ -0,0 +1,929 @@
+/*
+ * Dirent interface for Microsoft Visual Studio
+ * Version 1.21
+ *
+ * Copyright (C) 2006-2012 Toni Ronkko
+ * This file is part of dirent. Dirent may be freely distributed
+ * under the MIT license. For all details and documentation, see
+ * https://github.com/tronkko/dirent
+ */
+#ifndef DIRENT_H
+#define DIRENT_H
+
+/*
+ * Include windows.h without Windows Sockets 1.1 to prevent conflicts with
+ * Windows Sockets 2.0.
+ */
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <string.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+/* Indicates that d_type field is available in dirent structure */
+#define _DIRENT_HAVE_D_TYPE
+
+/* Indicates that d_namlen field is available in dirent structure */
+#define _DIRENT_HAVE_D_NAMLEN
+
+/* Entries missing from MSVC 6.0 */
+#if !defined(FILE_ATTRIBUTE_DEVICE)
+# define FILE_ATTRIBUTE_DEVICE 0x40
+#endif
+
+/* File type and permission flags for stat(), general mask */
+#if !defined(S_IFMT)
+# define S_IFMT _S_IFMT
+#endif
+
+/* Directory bit */
+#if !defined(S_IFDIR)
+# define S_IFDIR _S_IFDIR
+#endif
+
+/* Character device bit */
+#if !defined(S_IFCHR)
+# define S_IFCHR _S_IFCHR
+#endif
+
+/* Pipe bit */
+#if !defined(S_IFFIFO)
+# define S_IFFIFO _S_IFFIFO
+#endif
+
+/* Regular file bit */
+#if !defined(S_IFREG)
+# define S_IFREG _S_IFREG
+#endif
+
+/* Read permission */
+#if !defined(S_IREAD)
+# define S_IREAD _S_IREAD
+#endif
+
+/* Write permission */
+#if !defined(S_IWRITE)
+# define S_IWRITE _S_IWRITE
+#endif
+
+/* Execute permission */
+#if !defined(S_IEXEC)
+# define S_IEXEC _S_IEXEC
+#endif
+
+/* Pipe */
+#if !defined(S_IFIFO)
+# define S_IFIFO _S_IFIFO
+#endif
+
+/* Block device */
+#if !defined(S_IFBLK)
+# define S_IFBLK 0
+#endif
+
+/* Link */
+#if !defined(S_IFLNK)
+# define S_IFLNK 0
+#endif
+
+/* Socket */
+#if !defined(S_IFSOCK)
+# define S_IFSOCK 0
+#endif
+
+/* Read user permission */
+#if !defined(S_IRUSR)
+# define S_IRUSR S_IREAD
+#endif
+
+/* Write user permission */
+#if !defined(S_IWUSR)
+# define S_IWUSR S_IWRITE
+#endif
+
+/* Execute user permission */
+#if !defined(S_IXUSR)
+# define S_IXUSR 0
+#endif
+
+/* Read group permission */
+#if !defined(S_IRGRP)
+# define S_IRGRP 0
+#endif
+
+/* Write group permission */
+#if !defined(S_IWGRP)
+# define S_IWGRP 0
+#endif
+
+/* Execute group permission */
+#if !defined(S_IXGRP)
+# define S_IXGRP 0
+#endif
+
+/* Read others permission */
+#if !defined(S_IROTH)
+# define S_IROTH 0
+#endif
+
+/* Write others permission */
+#if !defined(S_IWOTH)
+# define S_IWOTH 0
+#endif
+
+/* Execute others permission */
+#if !defined(S_IXOTH)
+# define S_IXOTH 0
+#endif
+
+/* Maximum length of file name */
+#if !defined(PATH_MAX)
+# define PATH_MAX MAX_PATH
+#endif
+#if !defined(FILENAME_MAX)
+# define FILENAME_MAX MAX_PATH
+#endif
+#if !defined(NAME_MAX)
+# define NAME_MAX FILENAME_MAX
+#endif
+
+/* File type flags for d_type */
+#define DT_UNKNOWN 0
+#define DT_REG S_IFREG
+#define DT_DIR S_IFDIR
+#define DT_FIFO S_IFIFO
+#define DT_SOCK S_IFSOCK
+#define DT_CHR S_IFCHR
+#define DT_BLK S_IFBLK
+#define DT_LNK S_IFLNK
+
+/* Macros for converting between st_mode and d_type */
+#define IFTODT(mode) ((mode) & S_IFMT)
+#define DTTOIF(type) (type)
+
+/*
+ * File type macros. Note that block devices, sockets and links cannot be
+ * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are
+ * only defined for compatibility. These macros should always return false
+ * on Windows.
+ */
+#if !defined(S_ISFIFO)
+# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)
+#endif
+#if !defined(S_ISDIR)
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+#endif
+#if !defined(S_ISREG)
+# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
+#endif
+#if !defined(S_ISLNK)
+# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
+#endif
+#if !defined(S_ISSOCK)
+# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
+#endif
+#if !defined(S_ISCHR)
+# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR)
+#endif
+#if !defined(S_ISBLK)
+# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK)
+#endif
+
+/* Return the exact length of d_namlen without zero terminator */
+#define _D_EXACT_NAMLEN(p) ((p)->d_namlen)
+
+/* Return number of bytes needed to store d_namlen */
+#define _D_ALLOC_NAMLEN(p) (PATH_MAX)
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Wide-character version */
+struct _wdirent {
+ /* Always zero */
+ long d_ino;
+
+ /* Structure size */
+ unsigned short d_reclen;
+
+ /* Length of name without \0 */
+ size_t d_namlen;
+
+ /* File type */
+ int d_type;
+
+ /* File name */
+ wchar_t d_name[PATH_MAX];
+};
+typedef struct _wdirent _wdirent;
+
+struct _WDIR {
+ /* Current directory entry */
+ struct _wdirent ent;
+
+ /* Private file data */
+ WIN32_FIND_DATAW data;
+
+ /* True if data is valid */
+ int cached;
+
+ /* Win32 search handle */
+ HANDLE handle;
+
+ /* Initial directory name */
+ wchar_t *patt;
+};
+typedef struct _WDIR _WDIR;
+
+static _WDIR *_wopendir (const wchar_t *dirname);
+static struct _wdirent *_wreaddir (_WDIR *dirp);
+static int _wclosedir (_WDIR *dirp);
+static void _wrewinddir (_WDIR* dirp);
+
+
+/* For compatibility with Symbian */
+#define wdirent _wdirent
+#define WDIR _WDIR
+#define wopendir _wopendir
+#define wreaddir _wreaddir
+#define wclosedir _wclosedir
+#define wrewinddir _wrewinddir
+
+
+/* Multi-byte character versions */
+struct dirent {
+ /* Always zero */
+ long d_ino;
+
+ /* Structure size */
+ unsigned short d_reclen;
+
+ /* Length of name without \0 */
+ size_t d_namlen;
+
+ /* File type */
+ int d_type;
+
+ /* File name */
+ char d_name[PATH_MAX];
+};
+typedef struct dirent dirent;
+
+struct DIR {
+ struct dirent ent;
+ struct _WDIR *wdirp;
+};
+typedef struct DIR DIR;
+
+static DIR *opendir (const char *dirname);
+static struct dirent *readdir (DIR *dirp);
+static int closedir (DIR *dirp);
+static void rewinddir (DIR* dirp);
+
+
+/* Internal utility functions */
+static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp);
+static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp);
+
+static int dirent_mbstowcs_s(
+ size_t *pReturnValue,
+ wchar_t *wcstr,
+ size_t sizeInWords,
+ const char *mbstr,
+ size_t count);
+
+static int dirent_wcstombs_s(
+ size_t *pReturnValue,
+ char *mbstr,
+ size_t sizeInBytes,
+ const wchar_t *wcstr,
+ size_t count);
+
+static void dirent_set_errno (int error);
+
+/*
+ * Open directory stream DIRNAME for read and return a pointer to the
+ * internal working area that is used to retrieve individual directory
+ * entries.
+ */
+static _WDIR*
+_wopendir(
+ const wchar_t *dirname)
+{
+ _WDIR *dirp = NULL;
+ int error;
+
+ /* Must have directory name */
+ if (dirname == NULL || dirname[0] == '\0') {
+ dirent_set_errno (ENOENT);
+ return NULL;
+ }
+
+ /* Allocate new _WDIR structure */
+ dirp = (_WDIR*) malloc (sizeof (struct _WDIR));
+ if (dirp != NULL) {
+ DWORD n;
+
+ /* Reset _WDIR structure */
+ dirp->handle = INVALID_HANDLE_VALUE;
+ dirp->patt = NULL;
+ dirp->cached = 0;
+
+ /* Compute the length of full path plus zero terminator
+ *
+ * Note that on WinRT there's no way to convert relative paths
+ * into absolute paths, so just assume its an absolute path.
+ */
+# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
+ n = wcslen(dirname);
+# else
+ n = GetFullPathNameW (dirname, 0, NULL, NULL);
+# endif
+
+ /* Allocate room for absolute directory name and search pattern */
+ dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16);
+ if (dirp->patt) {
+
+ /*
+ * Convert relative directory name to an absolute one. This
+ * allows rewinddir() to function correctly even when current
+ * working directory is changed between opendir() and rewinddir().
+ *
+ * Note that on WinRT there's no way to convert relative paths
+ * into absolute paths, so just assume its an absolute path.
+ */
+# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
+ wcsncpy_s(dirp->patt, n+1, dirname, n);
+# else
+ n = GetFullPathNameW (dirname, n, dirp->patt, NULL);
+# endif
+ if (n > 0) {
+ wchar_t *p;
+
+ /* Append search pattern \* to the directory name */
+ p = dirp->patt + n;
+ if (dirp->patt < p) {
+ switch (p[-1]) {
+ case '\\':
+ case '/':
+ case ':':
+ /* Directory ends in path separator, e.g. c:\temp\ */
+ /*NOP*/;
+ break;
+
+ default:
+ /* Directory name doesn't end in path separator */
+ *p++ = '\\';
+ }
+ }
+ *p++ = '*';
+ *p = '\0';
+
+ /* Open directory stream and retrieve the first entry */
+ if (dirent_first (dirp)) {
+ /* Directory stream opened successfully */
+ error = 0;
+ } else {
+ /* Cannot retrieve first entry */
+ error = 1;
+ dirent_set_errno (ENOENT);
+ }
+
+ } else {
+ /* Cannot retrieve full path name */
+ dirent_set_errno (ENOENT);
+ error = 1;
+ }
+
+ } else {
+ /* Cannot allocate memory for search pattern */
+ error = 1;
+ }
+
+ } else {
+ /* Cannot allocate _WDIR structure */
+ error = 1;
+ }
+
+ /* Clean up in case of error */
+ if (error && dirp) {
+ _wclosedir (dirp);
+ dirp = NULL;
+ }
+
+ return dirp;
+}
+
+/*
+ * Read next directory entry. The directory entry is returned in dirent
+ * structure in the d_name field. Individual directory entries returned by
+ * this function include regular files, sub-directories, pseudo-directories
+ * "." and ".." as well as volume labels, hidden files and system files.
+ */
+static struct _wdirent*
+_wreaddir(
+ _WDIR *dirp)
+{
+ WIN32_FIND_DATAW *datap;
+ struct _wdirent *entp;
+
+ /* Read next directory entry */
+ datap = dirent_next (dirp);
+ if (datap) {
+ size_t n;
+ DWORD attr;
+
+ /* Pointer to directory entry to return */
+ entp = &dirp->ent;
+
+ /*
+ * Copy file name as wide-character string. If the file name is too
+ * long to fit in to the destination buffer, then truncate file name
+ * to PATH_MAX characters and zero-terminate the buffer.
+ */
+ n = 0;
+ while (n + 1 < PATH_MAX && datap->cFileName[n] != 0) {
+ entp->d_name[n] = datap->cFileName[n];
+ n++;
+ }
+ dirp->ent.d_name[n] = 0;
+
+ /* Length of file name excluding zero terminator */
+ entp->d_namlen = n;
+
+ /* File type */
+ attr = datap->dwFileAttributes;
+ if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
+ entp->d_type = DT_CHR;
+ } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ entp->d_type = DT_DIR;
+ } else {
+ entp->d_type = DT_REG;
+ }
+
+ /* Reset dummy fields */
+ entp->d_ino = 0;
+ entp->d_reclen = sizeof (struct _wdirent);
+
+ } else {
+
+ /* Last directory entry read */
+ entp = NULL;
+
+ }
+
+ return entp;
+}
+
+/*
+ * Close directory stream opened by opendir() function. This invalidates the
+ * DIR structure as well as any directory entry read previously by
+ * _wreaddir().
+ */
+static int
+_wclosedir(
+ _WDIR *dirp)
+{
+ int ok;
+ if (dirp) {
+
+ /* Release search handle */
+ if (dirp->handle != INVALID_HANDLE_VALUE) {
+ FindClose (dirp->handle);
+ dirp->handle = INVALID_HANDLE_VALUE;
+ }
+
+ /* Release search pattern */
+ if (dirp->patt) {
+ free (dirp->patt);
+ dirp->patt = NULL;
+ }
+
+ /* Release directory structure */
+ free (dirp);
+ ok = /*success*/0;
+
+ } else {
+ /* Invalid directory stream */
+ dirent_set_errno (EBADF);
+ ok = /*failure*/-1;
+ }
+ return ok;
+}
+
+/*
+ * Rewind directory stream such that _wreaddir() returns the very first
+ * file name again.
+ */
+static void
+_wrewinddir(
+ _WDIR* dirp)
+{
+ if (dirp) {
+ /* Release existing search handle */
+ if (dirp->handle != INVALID_HANDLE_VALUE) {
+ FindClose (dirp->handle);
+ }
+
+ /* Open new search handle */
+ dirent_first (dirp);
+ }
+}
+
+/* Get first directory entry (internal) */
+static WIN32_FIND_DATAW*
+dirent_first(
+ _WDIR *dirp)
+{
+ WIN32_FIND_DATAW *datap;
+
+ /* Open directory and retrieve the first entry */
+ dirp->handle = FindFirstFileExW(
+ dirp->patt, FindExInfoStandard, &dirp->data,
+ FindExSearchNameMatch, NULL, 0);
+ if (dirp->handle != INVALID_HANDLE_VALUE) {
+
+ /* a directory entry is now waiting in memory */
+ datap = &dirp->data;
+ dirp->cached = 1;
+
+ } else {
+
+ /* Failed to re-open directory: no directory entry in memory */
+ dirp->cached = 0;
+ datap = NULL;
+
+ }
+ return datap;
+}
+
+/* Get next directory entry (internal) */
+static WIN32_FIND_DATAW*
+dirent_next(
+ _WDIR *dirp)
+{
+ WIN32_FIND_DATAW *p;
+
+ /* Get next directory entry */
+ if (dirp->cached != 0) {
+
+ /* A valid directory entry already in memory */
+ p = &dirp->data;
+ dirp->cached = 0;
+
+ } else if (dirp->handle != INVALID_HANDLE_VALUE) {
+
+ /* Get the next directory entry from stream */
+ if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) {
+ /* Got a file */
+ p = &dirp->data;
+ } else {
+ /* The very last entry has been processed or an error occured */
+ FindClose (dirp->handle);
+ dirp->handle = INVALID_HANDLE_VALUE;
+ p = NULL;
+ }
+
+ } else {
+
+ /* End of directory stream reached */
+ p = NULL;
+
+ }
+
+ return p;
+}
+
+/*
+ * Open directory stream using plain old C-string.
+ */
+static DIR*
+opendir(
+ const char *dirname)
+{
+ struct DIR *dirp;
+ int error;
+
+ /* Must have directory name */
+ if (dirname == NULL || dirname[0] == '\0') {
+ dirent_set_errno (ENOENT);
+ return NULL;
+ }
+
+ /* Allocate memory for DIR structure */
+ dirp = (DIR*) malloc (sizeof (struct DIR));
+ if (dirp) {
+ wchar_t wname[PATH_MAX];
+ size_t n;
+
+ /* Convert directory name to wide-character string */
+ error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX);
+ if (!error) {
+
+ /* Open directory stream using wide-character name */
+ dirp->wdirp = _wopendir (wname);
+ if (dirp->wdirp) {
+ /* Directory stream opened */
+ error = 0;
+ } else {
+ /* Failed to open directory stream */
+ error = 1;
+ }
+
+ } else {
+ /*
+ * Cannot convert file name to wide-character string. This
+ * occurs if the string contains invalid multi-byte sequences or
+ * the output buffer is too small to contain the resulting
+ * string.
+ */
+ error = 1;
+ }
+
+ } else {
+ /* Cannot allocate DIR structure */
+ error = 1;
+ }
+
+ /* Clean up in case of error */
+ if (error && dirp) {
+ free (dirp);
+ dirp = NULL;
+ }
+
+ return dirp;
+}
+
+/*
+ * Read next directory entry.
+ *
+ * When working with text consoles, please note that file names returned by
+ * readdir() are represented in the default ANSI code page while any output to
+ * console is typically formatted on another code page. Thus, non-ASCII
+ * characters in file names will not usually display correctly on console. The
+ * problem can be fixed in two ways: (1) change the character set of console
+ * to 1252 using chcp utility and use Lucida Console font, or (2) use
+ * _cprintf function when writing to console. The _cprinf() will re-encode
+ * ANSI strings to the console code page so many non-ASCII characters will
+ * display correcly.
+ */
+static struct dirent*
+readdir(
+ DIR *dirp)
+{
+ WIN32_FIND_DATAW *datap;
+ struct dirent *entp;
+
+ /* Read next directory entry */
+ datap = dirent_next (dirp->wdirp);
+ if (datap) {
+ size_t n;
+ int error;
+
+ /* Attempt to convert file name to multi-byte string */
+ error = dirent_wcstombs_s(
+ &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX);
+
+ /*
+ * If the file name cannot be represented by a multi-byte string,
+ * then attempt to use old 8+3 file name. This allows traditional
+ * Unix-code to access some file names despite of unicode
+ * characters, although file names may seem unfamiliar to the user.
+ *
+ * Be ware that the code below cannot come up with a short file
+ * name unless the file system provides one. At least
+ * VirtualBox shared folders fail to do this.
+ */
+ if (error && datap->cAlternateFileName[0] != '\0') {
+ error = dirent_wcstombs_s(
+ &n, dirp->ent.d_name, PATH_MAX,
+ datap->cAlternateFileName, PATH_MAX);
+ }
+
+ if (!error) {
+ DWORD attr;
+
+ /* Initialize directory entry for return */
+ entp = &dirp->ent;
+
+ /* Length of file name excluding zero terminator */
+ entp->d_namlen = n - 1;
+
+ /* File attributes */
+ attr = datap->dwFileAttributes;
+ if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
+ entp->d_type = DT_CHR;
+ } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ entp->d_type = DT_DIR;
+ } else {
+ entp->d_type = DT_REG;
+ }
+
+ /* Reset dummy fields */
+ entp->d_ino = 0;
+ entp->d_reclen = sizeof (struct dirent);
+
+ } else {
+ /*
+ * Cannot convert file name to multi-byte string so construct
+ * an errornous directory entry and return that. Note that
+ * we cannot return NULL as that would stop the processing
+ * of directory entries completely.
+ */
+ entp = &dirp->ent;
+ entp->d_name[0] = '?';
+ entp->d_name[1] = '\0';
+ entp->d_namlen = 1;
+ entp->d_type = DT_UNKNOWN;
+ entp->d_ino = 0;
+ entp->d_reclen = 0;
+ }
+
+ } else {
+ /* No more directory entries */
+ entp = NULL;
+ }
+
+ return entp;
+}
+
+/*
+ * Close directory stream.
+ */
+static int
+closedir(
+ DIR *dirp)
+{
+ int ok;
+ if (dirp) {
+
+ /* Close wide-character directory stream */
+ ok = _wclosedir (dirp->wdirp);
+ dirp->wdirp = NULL;
+
+ /* Release multi-byte character version */
+ free (dirp);
+
+ } else {
+
+ /* Invalid directory stream */
+ dirent_set_errno (EBADF);
+ ok = /*failure*/-1;
+
+ }
+ return ok;
+}
+
+/*
+ * Rewind directory stream to beginning.
+ */
+static void
+rewinddir(
+ DIR* dirp)
+{
+ /* Rewind wide-character string directory stream */
+ _wrewinddir (dirp->wdirp);
+}
+
+/* Convert multi-byte string to wide character string */
+static int
+dirent_mbstowcs_s(
+ size_t *pReturnValue,
+ wchar_t *wcstr,
+ size_t sizeInWords,
+ const char *mbstr,
+ size_t count)
+{
+ int error;
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+
+ /* Microsoft Visual Studio 2005 or later */
+ error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count);
+
+#else
+
+ /* Older Visual Studio or non-Microsoft compiler */
+ size_t n;
+
+ /* Convert to wide-character string (or count characters) */
+ n = mbstowcs (wcstr, mbstr, sizeInWords);
+ if (!wcstr || n < count) {
+
+ /* Zero-terminate output buffer */
+ if (wcstr && sizeInWords) {
+ if (n >= sizeInWords) {
+ n = sizeInWords - 1;
+ }
+ wcstr[n] = 0;
+ }
+
+ /* Length of resuting multi-byte string WITH zero terminator */
+ if (pReturnValue) {
+ *pReturnValue = n + 1;
+ }
+
+ /* Success */
+ error = 0;
+
+ } else {
+
+ /* Could not convert string */
+ error = 1;
+
+ }
+
+#endif
+
+ return error;
+}
+
+/* Convert wide-character string to multi-byte string */
+static int
+dirent_wcstombs_s(
+ size_t *pReturnValue,
+ char *mbstr,
+ size_t sizeInBytes, /* max size of mbstr */
+ const wchar_t *wcstr,
+ size_t count)
+{
+ int error;
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+
+ /* Microsoft Visual Studio 2005 or later */
+ error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count);
+
+#else
+
+ /* Older Visual Studio or non-Microsoft compiler */
+ size_t n;
+
+ /* Convert to multi-byte string (or count the number of bytes needed) */
+ n = wcstombs (mbstr, wcstr, sizeInBytes);
+ if (!mbstr || n < count) {
+
+ /* Zero-terminate output buffer */
+ if (mbstr && sizeInBytes) {
+ if (n >= sizeInBytes) {
+ n = sizeInBytes - 1;
+ }
+ mbstr[n] = '\0';
+ }
+
+ /* Length of resulting multi-bytes string WITH zero-terminator */
+ if (pReturnValue) {
+ *pReturnValue = n + 1;
+ }
+
+ /* Success */
+ error = 0;
+
+ } else {
+
+ /* Cannot convert string */
+ error = 1;
+
+ }
+
+#endif
+
+ return error;
+}
+
+/* Set errno variable */
+static void
+dirent_set_errno(
+ int error)
+{
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+
+ /* Microsoft Visual Studio 2005 and later */
+ _set_errno (error);
+
+#else
+
+ /* Non-Microsoft compiler or older Microsoft compiler */
+ errno = error;
+
+#endif
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /*DIRENT_H*/
+
diff --git a/manifest b/manifest
new file mode 100644
index 0000000..c241935
--- /dev/null
+++ b/manifest
@@ -0,0 +1,16 @@
+: 1
+name: libpkgconf
+version: 1.3.9-a.0.z
+summary: C library for retriving pkg-config compiler and linker flags
+license: ISC, MIT; ISC for the most of original files.
+tags: pkg-config, pkgconf, cflags, libs
+description-file: README
+url: https://github.com/pkgconf/pkgconf
+doc-url: http://pkgconf.readthedocs.io/en/latest/?badge=latest
+src-url: https://git.build2.org/cgit/packaging/pkgconf/libpkgconf/tree/
+package-url: https://git.build2.org/cgit/packaging/pkgconf/
+email: packaging@build2.org; Report issues at https://github.com/pkgconf/pkgconf/issues
+package-email: packaging@build2.org; Mailing list.
+build-email: builds@build2.org
+depends: * build2 >= 0.6.0-
+depends: * bpkg >= 0.6.0-
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..2e508a9
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,3 @@
+driver
+test/
+test-*/
diff --git a/tests/basic/buildfile b/tests/basic/buildfile
new file mode 100644
index 0000000..8abc795
--- /dev/null
+++ b/tests/basic/buildfile
@@ -0,0 +1,7 @@
+# file : tests/basic/buildfile
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+import libs = libpkgconf%lib{pkgconf}
+
+exe{driver}: {h c}{*} $libs test{testscript}
diff --git a/tests/basic/driver.c b/tests/basic/driver.c
new file mode 100644
index 0000000..1f861de
--- /dev/null
+++ b/tests/basic/driver.c
@@ -0,0 +1,166 @@
+/* file : tests/basic/driver.c
+ * copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+ * license : ISC; see accompanying COPYING file
+ */
+
+/*
+ * Enable assertions.
+ */
+#ifdef NDEBUG
+# undef NDEBUG
+#endif
+
+#include <libpkgconf/libpkgconf.h>
+
+#include <stdio.h> /* printf(), fprintf(), stderr */
+#include <stddef.h> /* NULL */
+#include <stdlib.h> /* free() */
+#include <assert.h>
+#include <string.h> /* strcmp() */
+#include <stdbool.h> /* bool, true, false */
+
+static bool
+error_handler (const char* msg, const pkgconf_client_t* c, const void* d)
+{
+ (void) c; /* Unused. */
+ (void) d; /* Unused. */
+
+ /*
+ * Seems it always have a trailing newline char. Probably it still a good
+ * idea to check if it is. Let's see if it ever be missed.
+ *
+ */
+ fprintf (stderr, "%s", msg);
+ return true;
+}
+
+static void
+print_and_free (pkgconf_list_t* list)
+{
+ char* buf = pkgconf_fragment_render (list, /* escape */ true);
+ printf("%s", buf);
+ free (buf);
+
+ pkgconf_fragment_free (list);
+}
+
+/*
+ * Usage: argv[0] [--cflags] [--libs] (--with-path <dir>)* <name>
+ *
+ * Print package compiler and linker flags. If the package name has '.pc'
+ * extension it is interpreted as a file name. Prints all flags, as pkgconf
+ * utility does when --keep-system-libs and --keep-system-cflags are specified.
+ *
+ * --cflags
+ * Print compiler flags.
+ *
+ * --libs
+ * Print linker flags.
+ *
+ * --with-path <dir>
+ * Search through the directory for pc-files. If at least one --with-path
+ * is specified then the default directories are not searched through.
+ */
+int
+main (int argc, const char* argv[])
+{
+ pkgconf_client_t* c =
+ pkgconf_client_new (error_handler, /* error_handler_data */ NULL);
+
+ assert (c != NULL);
+
+ bool cflags = false;
+ bool libs = false;
+ bool default_dirs = true;
+ int client_flags = 0;
+
+ int i = 1;
+ for (; i < argc; ++i)
+ {
+ const char* o = argv[i];
+
+ if (strcmp (o, "--cflags") == 0)
+ cflags = true;
+ else if (strcmp (o, "--libs") == 0)
+ libs = true;
+ else if (strcmp (o, "--static") == 0)
+ client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE |
+ PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS;
+ else if (strcmp (o, "--with-path") == 0)
+ {
+ ++i;
+ assert (i < argc);
+
+ pkgconf_path_add (argv[i], &c->dir_list, /* filter_duplicates */ true);
+ default_dirs = false;
+ }
+ else
+ break;
+ }
+
+ assert (i + 1 == argc);
+ const char* name = argv[i];
+
+ int r = 1;
+ int max_depth = 2000;
+
+ pkgconf_client_set_flags (c, client_flags);
+
+ /*
+ * Bootstrap the package search default paths if not specified explicitly.
+ */
+ if (default_dirs)
+ pkgconf_pkg_dir_list_build (c);
+
+ pkgconf_pkg_t* p = pkgconf_pkg_find (c, name);
+
+ if (p != NULL)
+ {
+ int e = PKGCONF_PKG_ERRF_OK;
+
+ /*
+ * Print C flags.
+ */
+ if (cflags)
+ {
+ pkgconf_client_set_flags (
+ c,
+ client_flags | PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
+
+ pkgconf_list_t list = PKGCONF_LIST_INITIALIZER;
+ e = pkgconf_pkg_cflags (c, p, &list, max_depth);
+
+ if (e == PKGCONF_PKG_ERRF_OK)
+ print_and_free (&list);
+
+ pkgconf_client_set_flags (c, client_flags); /* Restore. */
+ }
+
+ /*
+ * Print libs.
+ */
+ if (libs && e == PKGCONF_PKG_ERRF_OK)
+ {
+ pkgconf_list_t list = PKGCONF_LIST_INITIALIZER;
+ e = pkgconf_pkg_libs (c, p, &list, max_depth);
+
+ if (e == PKGCONF_PKG_ERRF_OK)
+ print_and_free (&list);
+ }
+
+ if (e == PKGCONF_PKG_ERRF_OK)
+ {
+ r = 0;
+
+ if (cflags || libs)
+ printf ("\n");
+ }
+
+ pkgconf_pkg_unref (c, p);
+ }
+ else
+ fprintf (stderr, "package '%s' not found\n", name);
+
+ pkgconf_client_free (c);
+ return r;
+}
diff --git a/tests/basic/testscript b/tests/basic/testscript
new file mode 100644
index 0000000..d7fe796
--- /dev/null
+++ b/tests/basic/testscript
@@ -0,0 +1,74 @@
+# file : tests/basic/testscript
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+test.options = --with-path $~
+
++cat <<EOI >=openssl.pc
+prefix=/usr
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib64
+includedir=${prefix}/include
+
+Name: OpenSSL
+Description: Secure Sockets Layer and cryptography libraries and tools
+Version: 1.0.2g
+Requires: libssl libcrypto
+EOI
+
++cat <<EOI >=libssl.pc
+prefix=/usr
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib64
+includedir=${prefix}/include
+
+Name: OpenSSL-libssl
+Description: Secure Sockets Layer and cryptography libraries
+Version: 1.0.2g
+Requires.private: libcrypto
+Libs: -L${libdir} -lssl
+Libs.private: -ldl -lz -lgssapi_krb5 -lkrb5 -lcom_err -lk5crypto
+Cflags: -I${includedir} -I/usr/include
+EOI
+
++cat <<EOI >=libcrypto.pc
+prefix=/usr
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib64
+includedir=${prefix}/include
+
+Name: OpenSSL-libcrypto
+Description: OpenSSL cryptography library
+Version: 1.0.2g
+Requires:
+Libs: -L${libdir} -lcrypto
+Libs.private: -ldl -lz
+Cflags: -I${includedir}
+EOI
+
++cat <<EOI >=libfaulty.pc
+Name: faulty
+Description: Faulty library
+Version: 1.0
+Requires: non-existent
+EOI
+
+: cflags
+:
+$* --cflags openssl >'-I/usr/include '
+
+: libs
+:
+$* --libs openssl >'-L/usr/lib64 -lssl -lcrypto '
+
+: libs-static
+:
+$* --libs --static openssl >'-L/usr/lib64 -lssl -ldl -lz -lgssapi_krb5 -lkrb5 -lcom_err -lk5crypto -L/usr/lib64 -ldl -lz -lcrypto -ldl -lz '
+
+: non-existent
+:
+$* non-existent 2>"package 'non-existent' not found" == 1
+
+: faulty
+:
+$* --cflags libfaulty 2>- == 1
diff --git a/tests/build/.gitignore b/tests/build/.gitignore
new file mode 100644
index 0000000..225c27f
--- /dev/null
+++ b/tests/build/.gitignore
@@ -0,0 +1 @@
+config.build
diff --git a/tests/build/bootstrap.build b/tests/build/bootstrap.build
new file mode 100644
index 0000000..fcb38ff
--- /dev/null
+++ b/tests/build/bootstrap.build
@@ -0,0 +1,9 @@
+# file : tests/build/bootstrap.build
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+project = # Unnamed subproject.
+
+using config
+using dist
+using test
diff --git a/tests/build/root.build b/tests/build/root.build
new file mode 100644
index 0000000..ae354d5
--- /dev/null
+++ b/tests/build/root.build
@@ -0,0 +1,18 @@
+# file : tests/build/root.build
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+c.std = 99
+
+using c
+
+h{*}: extension = h
+c{*}: extension = c
+
+# Every exe{} in this subproject is by default a test.
+#
+exe{*}: test = true
+
+# Specify the test target for cross-testing.
+#
+test.target = $c.target
diff --git a/tests/buildfile b/tests/buildfile
new file mode 100644
index 0000000..cb7a219
--- /dev/null
+++ b/tests/buildfile
@@ -0,0 +1,5 @@
+# file : tests/buildfile
+# copyright : Copyright (c) 2016-2017 Code Synthesis Ltd
+# license : ISC; see accompanying COPYING file
+
+./: {*/ -build/}