/* * 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 #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" static unsigned int pkgconf_pkg_traverse_main(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags); 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 char * pkg_get_parent_dir(pkgconf_pkg_t *pkg) { char buf[PKGCONF_ITEM_SIZE], *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 strdup(buf); } typedef void (*pkgconf_pkg_parser_keyword_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const 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(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) { (void) keyword; (void) lineno; char **dest = (char **)((char *) pkg + offset); *dest = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); } static void pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) { (void) keyword; (void) lineno; char *p, *i; size_t len; char **dest = (char **)((char *) pkg + offset); /* cut at any detected whitespace */ p = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); len = strcspn(p, " \t"); if (len != strlen(p)) { i = p + (ptrdiff_t) len; *i = '\0'; pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: malformed version field with whitespace, trimming to [%s]\n", pkg->filename, lineno, p); } *dest = p; } static void pkgconf_pkg_parser_fragment_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) { pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); bool ret = pkgconf_fragment_parse(client, dest, &pkg->vars, value, pkg->flags); if (!ret) { pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: unable to parse field '%s' into an argument vector, value [%s]\n", pkg->filename, lineno, keyword, value); } } static void pkgconf_pkg_parser_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) { (void) keyword; (void) lineno; pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); pkgconf_dependency_parse(client, pkg, dest, value, 0); } /* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as an "internal" dependency. */ static void pkgconf_pkg_parser_internal_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) { (void) keyword; (void) lineno; pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_INTERNAL); } /* 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)}, {"License", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, license)}, {"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, required)}, {"Requires.internal", pkgconf_pkg_parser_internal_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, {"Requires.private", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, {"Version", pkgconf_pkg_parser_version_func, offsetof(pkgconf_pkg_t, version)}, }; static void pkgconf_pkg_parser_keyword_set(void *opaque, const size_t lineno, const char *keyword, const char *value) { pkgconf_pkg_t *pkg = opaque; 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; pair->func(pkg->owner, pkg, keyword, lineno, pair->offset, value); } static const char * determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen) { char *pathiter; pkgconf_strlcpy(buf, pkg->filename, buflen); pkgconf_path_relocate(buf, buflen); 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; } /* * Takes a real path and converts it to a pkgconf value. This means normalizing * directory separators and escaping things (only spaces covered atm). * * This is useful for things like prefix/pcfiledir which might get injected * at runtime and are not sourced from the .pc file. * * "C:\foo bar\baz" -> "C:/foo\ bar/baz" * "/foo bar/baz" -> "/foo\ bar/baz" */ static char * convert_path_to_value(const char *path) { char *buf = calloc((strlen(path) + 1) * 2, 1); char *bptr = buf; const char *i; for (i = path; *i != '\0'; i++) { if (*i == PKG_DIR_SEP_S) *bptr++ = '/'; else if (*i == ' ') { *bptr++ = '\\'; *bptr++ = *i; } else *bptr++ = *i; } return buf; } static void remove_additional_separators(char *buf) { char *p = buf; while (*p) { if (*p == '/') { char *q; q = ++p; while (*q && *q == '/') q++; if (p != q) memmove (p, q, strlen (q) + 1); } else { p++; } } } static void canonicalize_path(char *buf) { remove_additional_separators(buf); } static bool is_path_prefix_equal(const char *path1, const char *path2, size_t path2_len) { #ifdef _WIN32 return !_strnicmp(path1, path2, path2_len); #else return !strncmp(path1, path2, path2_len); #endif } static void pkgconf_pkg_parser_value_set(void *opaque, const size_t lineno, const char *keyword, const char *value) { char canonicalized_value[PKGCONF_ITEM_SIZE]; pkgconf_pkg_t *pkg = opaque; (void) lineno; pkgconf_strlcpy(canonicalized_value, value, sizeof canonicalized_value); canonicalize_path(canonicalized_value); /* Some pc files will use absolute paths for all of their directories * which is broken when redefining the prefix. We try to outsmart the * file and rewrite any directory that starts with the same prefix. */ if (pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX && pkg->orig_prefix && is_path_prefix_equal(canonicalized_value, pkg->orig_prefix->value, strlen(pkg->orig_prefix->value))) { char newvalue[PKGCONF_ITEM_SIZE]; pkgconf_strlcpy(newvalue, pkg->prefix->value, sizeof newvalue); pkgconf_strlcat(newvalue, canonicalized_value + strlen(pkg->orig_prefix->value), sizeof newvalue); pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, newvalue, false, pkg->flags); } else if (strcmp(keyword, pkg->owner->prefix_varname) || !(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX)) pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); else { char pathbuf[PKGCONF_ITEM_SIZE]; const char *relvalue = determine_prefix(pkg, pathbuf, sizeof pathbuf); if (relvalue != NULL) { char *prefix_value = convert_path_to_value(relvalue); pkg->orig_prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, "orig_prefix", canonicalized_value, true, pkg->flags); pkg->prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, prefix_value, false, pkg->flags); free(prefix_value); } else pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); } } 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 const pkgconf_parser_operand_func_t pkg_parser_funcs[256] = { [':'] = pkgconf_pkg_parser_keyword_set, ['='] = pkgconf_pkg_parser_value_set }; static void pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) PRINTFLIKE(2, 3); static void pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) { char buf[PKGCONF_ITEM_SIZE]; va_list va; va_start(va, fmt); vsnprintf(buf, sizeof buf, fmt, va); va_end(va); pkgconf_warn(pkg->owner, "%s", buf); } 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, unsigned int flags) * * 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. * :param uint flags: The flags to use when parsing. * :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, unsigned int flags) { pkgconf_pkg_t *pkg; char *idptr; pkg = calloc(sizeof(pkgconf_pkg_t), 1); pkg->owner = client; pkg->filename = strdup(filename); pkg->pc_filedir = pkg_get_parent_dir(pkg); pkg->flags = flags; char *pc_filedir_value = convert_path_to_value(pkg->pc_filedir); pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pc_filedir_value, true, pkg->flags); free(pc_filedir_value); /* If pc_filedir is outside of sysroot_dir, override sysroot_dir for this * package. * See https://github.com/pkgconf/pkgconf/issues/213 */ if (client->sysroot_dir && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir))) pkgconf_tuple_add(client, &pkg->vars, "pc_sysrootdir", "", false, pkg->flags); /* make module id */ if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL) idptr++; else idptr = pkg->filename; #ifdef _WIN32 /* On Windows, both \ and / are allowed in paths, so we have to chop both. * strrchr() took us to the last \ in that case, so we just have to see if * it is followed by a /. If so, lop it off. */ char *mungeptr; if ((mungeptr = strrchr(idptr, '/')) != NULL) idptr = ++mungeptr; #endif pkg->id = strdup(idptr); idptr = strrchr(pkg->id, '.'); if (idptr) *idptr = '\0'; pkgconf_parser_parse(f, pkg, pkg_parser_funcs, (pkgconf_parser_warn_func_t) pkg_warn_func, pkg->filename); 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_t *dep = pkgconf_dependency_add(client, &pkg->provides, pkg->id, pkg->version, PKGCONF_CMP_EQUAL, 0); pkgconf_dependency_unref(dep->owner, dep); 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) return; if (pkg->flags & PKGCONF_PKG_PROPF_STATIC && !(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)) return; pkgconf_cache_remove(client, pkg); pkgconf_dependency_free(&pkg->required); 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->flags & PKGCONF_PKG_PROPF_VIRTUAL) return; 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(pkgconf_client_t *client, pkgconf_pkg_t *pkg) { if (pkg->owner != NULL && pkg->owner != client) PKGCONF_TRACE(client, "WTF: client %p refers to package %p owned by other client %p", client, pkg, pkg->owner); pkg->refcount++; PKGCONF_TRACE(client, "%s refcount@%p: %d", pkg->id, pkg, 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) { if (pkg->owner != NULL && pkg->owner != client) PKGCONF_TRACE(client, "WTF: client %p unrefs package %p owned by other client %p", client, pkg, pkg->owner); pkg->refcount--; PKGCONF_TRACE(pkg->owner, "%s refcount@%p: %d", pkg->id, pkg, pkg->refcount); if (pkg->refcount <= 0) pkgconf_pkg_free(pkg->owner, 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[PKGCONF_ITEM_SIZE]; char uninst_locbuf[PKGCONF_ITEM_SIZE]; PKGCONF_TRACE(client, "trying path: %s for %s", path, name); snprintf(locbuf, sizeof locbuf, "%s%c%s" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, name); snprintf(uninst_locbuf, sizeof uninst_locbuf, "%s%c%s-uninstalled" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, 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, 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, 0); } 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)) { char filebuf[PKGCONF_ITEM_SIZE]; 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, 0); 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[PKGCONF_ITEM_SIZE]; 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, 0); if (pkg != NULL) { pkgconf_path_add(pkg->pc_filedir, &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); 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_ITEM_SIZE], buf2[PKGCONF_ITEM_SIZE]; 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 != 0) return ret < 0 ? -1 : 1; *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){ .next = &(pkgconf_node_t){ .next = &(pkgconf_node_t){ .data = &(pkgconf_tuple_t){ .key = "pc_system_libdirs", .value = SYSTEM_LIBDIR, } }, .data = &(pkgconf_tuple_t){ .key = "pc_system_includedirs", .value = SYSTEM_INCLUDEDIR, } }, .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){ .next = &(pkgconf_node_t){ .next = &(pkgconf_node_t){ .data = &(pkgconf_tuple_t){ .key = "pc_system_libdirs", .value = SYSTEM_LIBDIR, } }, .data = &(pkgconf_tuple_t){ .key = "pc_system_includedirs", .value = SYSTEM_INCLUDEDIR, } }, .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) { pkgdep->match = pkgconf_pkg_ref(client, pkg); 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); if (pkgdep->match != NULL) { PKGCONF_TRACE(client, "cached dependency: %s -> %s@%p", pkgdep->package, pkgdep->match->id, pkgdep->match); return pkgconf_pkg_ref(client, pkgdep->match); } 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) { if (eflags != NULL) *eflags |= PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH; } else pkgdep->match = pkgconf_pkg_ref(client, pkg); 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, 0); } 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) { if (eflags & PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND) { if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) & !client->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"); client->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 skip_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; pkgconf_node_t *node, *next; PKGCONF_FOREACH_LIST_ENTRY_SAFE(deplist->head, next, 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->serial == client->serial) { pkgdep->hits++; /* In this case we have a circular reference. * We break that by deleteing the circular node from the * the list, so that we dont create a situation where * memory is leaked due to circular ownership. * i.e: A owns B owns A */ pkgconf_warn(client, "%s: breaking circular reference (%s -> %s -> %s)\n", parent->id, parent->id, pkgdep->id, parent->id); pkgconf_node_delete(node, deplist); pkgconf_dependency_unref(client, depnode); goto next; } if (skip_flags && (depnode->flags & skip_flags) == skip_flags) goto next; pkgconf_audit_log_dependency(client, pkgdep, depnode); pkgdep->hits++; pkgdep->serial = client->serial; eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags); next: 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->required.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 : ""); if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS)) { 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, unsigned int skip_flags) * * 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. * :param uint skip_flags: Skip over dependency nodes containing the specified flags. A setting of 0 skips no dependency nodes. * :return: ``PKGCONF_PKG_ERRF_OK`` on success, else an error code. * :rtype: unsigned int */ static unsigned int pkgconf_pkg_traverse_main(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags) { unsigned int eflags = PKGCONF_PKG_ERRF_OK; if (maxdepth == 0) return eflags; PKGCONF_TRACE(client, "%s: level %d, serial %lu", root->id, maxdepth, client->serial); 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->required, func, data, maxdepth, skip_flags); 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, skip_flags); client->flags &= ~PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; if (eflags != PKGCONF_PKG_ERRF_OK) return eflags; } return eflags; } 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 skip_flags) { client->serial++; return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags); } 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; unsigned int skip_flags = (client->flags & PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS) == 0 ? PKGCONF_PKG_DEPF_INTERNAL : 0; pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &frags, maxdepth, skip_flags); if (eflag == PKGCONF_PKG_ERRF_OK && client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, &frags, maxdepth, skip_flags); if (eflag != PKGCONF_PKG_ERRF_OK) { pkgconf_fragment_free(&frags); return eflag; } pkgconf_fragment_copy_list(client, list, &frags); pkgconf_fragment_free(&frags); 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, 0); if (eflag != PKGCONF_PKG_ERRF_OK) { pkgconf_fragment_free(list); return eflag; } return eflag; }