From 4beec8f055fd3b0cc4ef618cce8b52c58dd0ee08 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 6 Sep 2017 17:40:06 +0300 Subject: Add implementation --- AUTHORS | 4 + COPYING | 10 + INSTALL | 7 + README | 20 + TODO | 18 + build/.gitignore | 1 + build/bootstrap.build | 25 + build/export.build | 10 + build/root.build | 10 + buildfile | 18 + libpkgconf/.gitignore | 3 + libpkgconf/argvsplit.c | 156 +++++ libpkgconf/audit.c | 98 +++ libpkgconf/bsdstubs.c | 139 ++++ libpkgconf/bsdstubs.h | 25 + libpkgconf/buildfile | 97 +++ libpkgconf/cache.c | 136 ++++ libpkgconf/client.c | 570 +++++++++++++++ libpkgconf/config.h | 37 + libpkgconf/dependency.c | 320 +++++++++ libpkgconf/fileio.c | 120 ++++ libpkgconf/fragment.c | 626 +++++++++++++++++ libpkgconf/iter.h | 97 +++ libpkgconf/libpkgconf-api.h | 20 + libpkgconf/libpkgconf.h | 319 +++++++++ libpkgconf/path.c | 326 +++++++++ libpkgconf/pkg.c | 1597 +++++++++++++++++++++++++++++++++++++++++++ libpkgconf/queue.c | 194 ++++++ libpkgconf/stdinc.h | 54 ++ libpkgconf/tuple.c | 347 ++++++++++ libpkgconf/version.h.in | 10 + libpkgconf/win-dirent.h | 929 +++++++++++++++++++++++++ manifest | 16 + tests/.gitignore | 3 + tests/basic/buildfile | 7 + tests/basic/driver.c | 166 +++++ tests/basic/testscript | 74 ++ tests/build/.gitignore | 1 + tests/build/bootstrap.build | 9 + tests/build/root.build | 18 + tests/buildfile | 5 + 41 files changed, 6642 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 README create mode 100644 TODO create mode 100644 build/.gitignore create mode 100644 build/bootstrap.build create mode 100644 build/export.build create mode 100644 build/root.build create mode 100644 buildfile create mode 100644 libpkgconf/.gitignore create mode 100644 libpkgconf/argvsplit.c create mode 100644 libpkgconf/audit.c create mode 100644 libpkgconf/bsdstubs.c create mode 100644 libpkgconf/bsdstubs.h create mode 100644 libpkgconf/buildfile create mode 100644 libpkgconf/cache.c create mode 100644 libpkgconf/client.c create mode 100644 libpkgconf/config.h create mode 100644 libpkgconf/dependency.c create mode 100644 libpkgconf/fileio.c create mode 100644 libpkgconf/fragment.c create mode 100644 libpkgconf/iter.h create mode 100644 libpkgconf/libpkgconf-api.h create mode 100644 libpkgconf/libpkgconf.h create mode 100644 libpkgconf/path.c create mode 100644 libpkgconf/pkg.c create mode 100644 libpkgconf/queue.c create mode 100644 libpkgconf/stdinc.h create mode 100644 libpkgconf/tuple.c create mode 100644 libpkgconf/version.h.in create mode 100644 libpkgconf/win-dirent.h create mode 100644 manifest create mode 100644 tests/.gitignore create mode 100644 tests/basic/buildfile create mode 100644 tests/basic/driver.c create mode 100644 tests/basic/testscript create mode 100644 tests/build/.gitignore create mode 100644 tests/build/bootstrap.build create mode 100644 tests/build/root.build create mode 100644 tests/buildfile diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..92e4b23 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Baptiste Daroussin +Jeff Horelick +Michał Górny 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 + +/* + * !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 + +/* + * !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 + * + * 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 +#include +#include + +#include +#include + +#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 . + * + * 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 . + * + * 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 + +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: + # + # /../lib/pkgconfig;/../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 + +/* + * !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 +#include +/* + * !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 : ""); + + 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 : ""); + + 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 + +/* + * 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 + +/* + * !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 + +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 + +/* + * !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 +#include +#include +#include + +/* 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 +#include + +#if defined(HAVE_CYGWIN_CONV_PATH) && defined(__MSYS__) +# include +#endif + +#if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32) +# include +# 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 +#include + +/* + * !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 + +/* + * !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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +# define PATH_DEV_NULL "nul" +# ifndef ssize_t +# ifndef __MINGW32__ +# include +# else +# include +# endif +# define ssize_t SSIZE_T +# endif +# ifndef __MINGW32__ +# include "win-dirent.h" +# else +# include +# endif +#else +# define PATH_DEV_NULL "/dev/null" +# include +# include +#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 + +/* + * !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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + +#include /* printf(), fprintf(), stderr */ +#include /* NULL */ +#include /* free() */ +#include +#include /* strcmp() */ +#include /* 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 )* + * + * 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 + * 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 <=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 <=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 <=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 <=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/} -- cgit v1.1