forked from ariadne/pkgconf
1068 lines
23 KiB
C
1068 lines
23 KiB
C
/*
|
|
* pkg.c
|
|
* higher-level dependency graph compilation, management and manipulation
|
|
*
|
|
* Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS).
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* This software is provided 'as is' and without any warranty, express or
|
|
* implied. In no event shall the authors be liable for any damages arising
|
|
* from the use of this software.
|
|
*/
|
|
|
|
#include <libpkgconf/libpkgconf.h>
|
|
|
|
#ifdef _WIN32
|
|
# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
|
|
# undef PKG_DEFAULT_PATH
|
|
# define PKG_DEFAULT_PATH "../lib/pkgconfig;../share/pkgconfig"
|
|
#endif
|
|
|
|
#define PKG_CONFIG_EXT ".pc"
|
|
#define PKG_CONFIG_PATH_SZ (65535)
|
|
|
|
/* 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
|
|
|
|
typedef struct {
|
|
char *path;
|
|
pkgconf_node_t node;
|
|
} pkg_path_t;
|
|
|
|
static inline void
|
|
path_add(const char *text, pkgconf_list_t *dirlist)
|
|
{
|
|
pkg_path_t *pkg_path;
|
|
|
|
pkg_path = calloc(sizeof(pkg_path_t), 1);
|
|
pkg_path->path = strdup(text);
|
|
|
|
pkgconf_node_insert_tail(&pkg_path->node, pkg_path, dirlist);
|
|
}
|
|
|
|
static inline size_t
|
|
path_split(const char *text, pkgconf_list_t *dirlist)
|
|
{
|
|
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)
|
|
{
|
|
path_add(p, dirlist);
|
|
|
|
count++, iter = NULL;
|
|
}
|
|
free(workbuf);
|
|
|
|
return count;
|
|
}
|
|
|
|
static inline void
|
|
path_free(pkgconf_list_t *dirlist)
|
|
{
|
|
pkgconf_node_t *n, *tn;
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n)
|
|
{
|
|
pkg_path_t *pkg_path = n->data;
|
|
|
|
free(pkg_path->path);
|
|
free(pkg_path);
|
|
}
|
|
}
|
|
|
|
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_pkgconfig_path(void)
|
|
{
|
|
const char *env_path;
|
|
#ifdef _WIN32
|
|
static char outbuf[MAX_PATH];
|
|
char namebuf[MAX_PATH];
|
|
char *p;
|
|
#endif
|
|
|
|
env_path = getenv("PKG_CONFIG_LIBDIR");
|
|
if (env_path != NULL)
|
|
return env_path;
|
|
|
|
#ifdef _WIN32
|
|
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';
|
|
strlcpy(outbuf, namebuf, sizeof outbuf);
|
|
strlcat(outbuf, "/", sizeof outbuf);
|
|
strlcat(outbuf, "../lib/pkgconfig", sizeof outbuf);
|
|
strlcat(outbuf, ";", sizeof outbuf);
|
|
strlcat(outbuf, namebuf, sizeof outbuf);
|
|
strlcat(outbuf, "/", sizeof outbuf);
|
|
strlcat(outbuf, "../share/pkgconfig", sizeof outbuf);
|
|
|
|
return outbuf;
|
|
#endif
|
|
|
|
return PKG_DEFAULT_PATH;
|
|
}
|
|
|
|
static const char *
|
|
pkg_get_parent_dir(pkg_t *pkg)
|
|
{
|
|
static char buf[PKG_BUFSIZE];
|
|
char *pathbuf;
|
|
|
|
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;
|
|
}
|
|
|
|
static pkgconf_list_t pkg_dir_list = PKGCONF_LIST_INITIALIZER;
|
|
|
|
void
|
|
pkg_dir_list_build(unsigned int flags)
|
|
{
|
|
const char *env_path;
|
|
|
|
if (pkg_dir_list.head != NULL || pkg_dir_list.tail != NULL)
|
|
return;
|
|
|
|
/* PKG_CONFIG_PATH has to take precedence */
|
|
env_path = getenv("PKG_CONFIG_PATH");
|
|
if (env_path)
|
|
path_split(env_path, &pkg_dir_list);
|
|
|
|
if (!(flags & PKGF_ENV_ONLY))
|
|
{
|
|
env_path = get_pkgconfig_path();
|
|
path_split(env_path, &pkg_dir_list);
|
|
}
|
|
}
|
|
|
|
void
|
|
pkg_dir_list_free(void)
|
|
{
|
|
path_free(&pkg_dir_list);
|
|
}
|
|
|
|
/*
|
|
* pkg_new_from_file(filename, file, flags)
|
|
*
|
|
* Parse a .pc file into a pkg_t object structure.
|
|
*/
|
|
pkg_t *
|
|
pkg_new_from_file(const char *filename, FILE *f, unsigned int flags)
|
|
{
|
|
pkg_t *pkg;
|
|
char readbuf[PKG_BUFSIZE];
|
|
char *idptr;
|
|
|
|
pkg = calloc(sizeof(pkg_t), 1);
|
|
pkg->filename = strdup(filename);
|
|
pkgconf_tuple_add(&pkg->vars, "pcfiledir", pkg_get_parent_dir(pkg));
|
|
|
|
/* make module id */
|
|
if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL)
|
|
idptr++;
|
|
else
|
|
idptr = pkg->filename;
|
|
|
|
pkg->id = strdup(idptr);
|
|
idptr = strrchr(pkg->id, '.');
|
|
if (idptr)
|
|
*idptr = '\0';
|
|
|
|
while (pkgconf_fgetline(readbuf, PKG_BUFSIZE, f) != NULL)
|
|
{
|
|
char op, *p, *key, *value;
|
|
|
|
p = readbuf;
|
|
while (*p && (isalpha(*p) || isdigit(*p) || *p == '_' || *p == '.'))
|
|
p++;
|
|
|
|
key = readbuf;
|
|
if (!isalpha(*key) && !isdigit(*p))
|
|
continue;
|
|
|
|
while (*p && isspace(*p)) {
|
|
/* set to null to avoid trailing spaces in key */
|
|
*p = '\0';
|
|
p++;
|
|
}
|
|
|
|
op = *p;
|
|
*p = '\0';
|
|
p++;
|
|
|
|
while (*p && isspace(*p))
|
|
p++;
|
|
|
|
value = p;
|
|
|
|
switch (op)
|
|
{
|
|
case ':':
|
|
if (!strcmp(key, "Name"))
|
|
pkg->realname = pkgconf_tuple_parse(&pkg->vars, value);
|
|
else if (!strcmp(key, "Description"))
|
|
pkg->description = pkgconf_tuple_parse(&pkg->vars, value);
|
|
else if (!strcmp(key, "Version"))
|
|
pkg->version = pkgconf_tuple_parse(&pkg->vars, value);
|
|
else if (!strcasecmp(key, "CFLAGS"))
|
|
pkgconf_fragment_parse(&pkg->cflags, &pkg->vars, value, flags);
|
|
else if (!strcasecmp(key, "CFLAGS.private"))
|
|
pkgconf_fragment_parse(&pkg->cflags_private, &pkg->vars, value, flags);
|
|
else if (!strcasecmp(key, "LIBS"))
|
|
pkgconf_fragment_parse(&pkg->libs, &pkg->vars, value, flags);
|
|
else if (!strcasecmp(key, "LIBS.private"))
|
|
pkgconf_fragment_parse(&pkg->libs_private, &pkg->vars, value, flags);
|
|
else if (!strcmp(key, "Requires"))
|
|
pkgconf_dependency_parse(pkg, &pkg->requires, value);
|
|
else if (!strcmp(key, "Requires.private"))
|
|
pkgconf_dependency_parse(pkg, &pkg->requires_private, value);
|
|
else if (!strcmp(key, "Conflicts"))
|
|
pkgconf_dependency_parse(pkg, &pkg->conflicts, value);
|
|
break;
|
|
case '=':
|
|
pkgconf_tuple_add(&pkg->vars, key, value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
return pkg_ref(pkg);
|
|
}
|
|
|
|
void
|
|
pkg_free(pkg_t *pkg)
|
|
{
|
|
if (pkg == NULL || pkg->flags & PKG_PROPF_VIRTUAL)
|
|
return;
|
|
|
|
pkgconf_cache_remove(pkg);
|
|
|
|
pkgconf_dependency_free(&pkg->requires);
|
|
pkgconf_dependency_free(&pkg->requires_private);
|
|
pkgconf_dependency_free(&pkg->conflicts);
|
|
|
|
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);
|
|
}
|
|
|
|
pkg_t *
|
|
pkg_ref(pkg_t *pkg)
|
|
{
|
|
pkg->refcount++;
|
|
return pkg;
|
|
}
|
|
|
|
void
|
|
pkg_unref(pkg_t *pkg)
|
|
{
|
|
pkg->refcount--;
|
|
if (pkg->refcount <= 0)
|
|
pkg_free(pkg);
|
|
}
|
|
|
|
static inline pkg_t *
|
|
pkg_try_specific_path(const char *path, const char *name, unsigned int flags)
|
|
{
|
|
pkg_t *pkg = NULL;
|
|
FILE *f;
|
|
char locbuf[PKG_CONFIG_PATH_SZ];
|
|
char uninst_locbuf[PKG_CONFIG_PATH_SZ];
|
|
|
|
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 (!(flags & PKGF_NO_UNINSTALLED) && (f = fopen(uninst_locbuf, "r")) != NULL)
|
|
{
|
|
pkg = pkg_new_from_file(uninst_locbuf, f, flags);
|
|
pkg->flags |= PKG_PROPF_UNINSTALLED;
|
|
}
|
|
else if ((f = fopen(locbuf, "r")) != NULL)
|
|
pkg = pkg_new_from_file(locbuf, f, flags);
|
|
|
|
return pkg;
|
|
}
|
|
|
|
static void
|
|
pkg_scan_dir(const char *path, pkg_iteration_func_t func)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
|
|
dir = opendir(path);
|
|
if (dir == NULL)
|
|
return;
|
|
|
|
for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir))
|
|
{
|
|
static char filebuf[PKG_BUFSIZE];
|
|
pkg_t *pkg;
|
|
FILE *f;
|
|
struct stat st;
|
|
|
|
strlcpy(filebuf, path, sizeof filebuf);
|
|
strlcat(filebuf, "/", sizeof filebuf);
|
|
strlcat(filebuf, dirent->d_name, sizeof filebuf);
|
|
|
|
stat(filebuf, &st);
|
|
if (!(S_ISREG(st.st_mode)))
|
|
continue;
|
|
|
|
f = fopen(filebuf, "r");
|
|
if (f == NULL)
|
|
continue;
|
|
|
|
pkg = pkg_new_from_file(filebuf, f, 0);
|
|
if (pkg != NULL)
|
|
{
|
|
func(pkg);
|
|
pkg_unref(pkg);
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
void
|
|
pkg_scan_all(pkg_iteration_func_t func)
|
|
{
|
|
pkgconf_node_t *n;
|
|
|
|
pkg_dir_list_build(0);
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg_dir_list.head, n)
|
|
{
|
|
pkg_path_t *pkg_path = n->data;
|
|
|
|
pkg_scan_dir(pkg_path->path, func);
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
pkg_t *
|
|
pkg_find_in_registry_key(HKEY hkey, const char *name, unsigned int flags)
|
|
{
|
|
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 = pkg_try_specific_path(pathbuf, name, flags);
|
|
if (pkg != NULL)
|
|
break;
|
|
}
|
|
|
|
bufsize = sizeof buf;
|
|
}
|
|
|
|
RegCloseKey(key);
|
|
return pkg;
|
|
}
|
|
#endif
|
|
|
|
pkg_t *
|
|
pkg_find(const char *name, unsigned int flags)
|
|
{
|
|
pkg_t *pkg = NULL;
|
|
pkgconf_node_t *n;
|
|
FILE *f;
|
|
|
|
pkg_dir_list_build(flags);
|
|
|
|
/* name might actually be a filename. */
|
|
if (str_has_suffix(name, PKG_CONFIG_EXT))
|
|
{
|
|
if ((f = fopen(name, "r")) != NULL)
|
|
{
|
|
pkg_t *pkg;
|
|
|
|
pkg = pkg_new_from_file(name, f, flags);
|
|
path_add(pkg_get_parent_dir(pkg), &pkg_dir_list);
|
|
|
|
return pkg;
|
|
}
|
|
}
|
|
|
|
/* check cache */
|
|
if (!(flags & PKGF_NO_CACHE))
|
|
{
|
|
if ((pkg = pkgconf_cache_lookup(name)) != NULL)
|
|
{
|
|
pkg->flags |= PKG_PROPF_CACHED;
|
|
return pkg;
|
|
}
|
|
}
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg_dir_list.head, n)
|
|
{
|
|
pkg_path_t *pkg_path = n->data;
|
|
|
|
pkg = pkg_try_specific_path(pkg_path->path, name, flags);
|
|
if (pkg != NULL)
|
|
goto out;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/* support getting PKG_CONFIG_PATH from registry */
|
|
pkg = pkg_find_in_registry_key(HKEY_CURRENT_USER, name, flags);
|
|
if (!pkg)
|
|
pkg = pkg_find_in_registry_key(HKEY_LOCAL_MACHINE, name, flags);
|
|
#endif
|
|
|
|
out:
|
|
pkgconf_cache_add(pkg);
|
|
|
|
return pkg;
|
|
}
|
|
|
|
/*
|
|
* pkg_compare_version(a, b)
|
|
*
|
|
* compare versions using RPM version comparison rules as described in the LSB.
|
|
*/
|
|
int
|
|
pkg_compare_version(const char *a, const char *b)
|
|
{
|
|
char oldch1, oldch2;
|
|
char buf1[PKG_BUFSIZE], buf2[PKG_BUFSIZE];
|
|
char *str1, *str2;
|
|
char *one, *two;
|
|
int ret;
|
|
bool isnum;
|
|
|
|
/* optimization: if version matches then it's the same version. */
|
|
if (!strcasecmp(a, b))
|
|
return 0;
|
|
|
|
strlcpy(buf1, a, sizeof buf1);
|
|
strlcpy(buf2, b, sizeof buf2);
|
|
|
|
one = str1 = buf1;
|
|
two = str2 = buf2;
|
|
|
|
while (*one || *two)
|
|
{
|
|
while (*one && !isalnum(*one) && *one != '~')
|
|
one++;
|
|
while (*two && !isalnum(*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(*str1))
|
|
{
|
|
while (*str1 && isdigit(*str1))
|
|
str1++;
|
|
|
|
while (*str2 && isdigit(*str2))
|
|
str2++;
|
|
|
|
isnum = true;
|
|
}
|
|
else
|
|
{
|
|
while (*str1 && isalpha(*str1))
|
|
str1++;
|
|
|
|
while (*str2 && isalpha(*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 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 = PKG_PROPF_VIRTUAL,
|
|
.vars = {
|
|
.head = &(pkgconf_node_t){
|
|
.prev = NULL,
|
|
.next = NULL,
|
|
.data = &(pkgconf_tuple_t){
|
|
.key = "pc_path",
|
|
.value = PKG_DEFAULT_PATH,
|
|
},
|
|
},
|
|
.tail = NULL,
|
|
},
|
|
};
|
|
|
|
typedef bool (*pkgconf_vercmp_res_func_t)(pkg_t *pkg, pkgconf_dependency_t *pkgdep);
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
pkgconf_pkg_comparator_t compare;
|
|
} pkgconf_pkg_comparator_name_t;
|
|
|
|
static pkgconf_pkg_comparator_name_t pkgconf_pkg_comparator_names[PKG_CMP_SIZE + 1] = {
|
|
{"<", PKG_LESS_THAN},
|
|
{">", PKG_GREATER_THAN},
|
|
{"<=", PKG_LESS_THAN_EQUAL},
|
|
{">=", PKG_GREATER_THAN_EQUAL},
|
|
{"=", PKG_EQUAL},
|
|
{"!=", PKG_NOT_EQUAL},
|
|
{"(any)", PKG_ANY},
|
|
{"???", PKG_CMP_SIZE},
|
|
};
|
|
|
|
static bool pkgconf_pkg_comparator_lt(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) < 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_gt(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) > 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_lte(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) <= 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_gte(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) >= 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_eq(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) == 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_ne(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
return (pkg_compare_version(pkg->version, pkgdep->version) != 0);
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_any(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
(void) pkg;
|
|
(void) pkgdep;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pkgconf_pkg_comparator_unimplemented(pkg_t *pkg, pkgconf_dependency_t *pkgdep)
|
|
{
|
|
(void) pkg;
|
|
(void) pkgdep;
|
|
|
|
return false;
|
|
}
|
|
|
|
static pkgconf_vercmp_res_func_t pkgconf_pkg_comparator_impls[PKG_CMP_SIZE + 1] = {
|
|
[PKG_ANY] = pkgconf_pkg_comparator_any,
|
|
[PKG_LESS_THAN] = pkgconf_pkg_comparator_lt,
|
|
[PKG_GREATER_THAN] = pkgconf_pkg_comparator_gt,
|
|
[PKG_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte,
|
|
[PKG_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte,
|
|
[PKG_EQUAL] = pkgconf_pkg_comparator_eq,
|
|
[PKG_NOT_EQUAL] = pkgconf_pkg_comparator_ne,
|
|
[PKG_CMP_SIZE] = pkgconf_pkg_comparator_unimplemented,
|
|
};
|
|
|
|
/*
|
|
* pkgconf_pkg_get_comparator(pkgdep)
|
|
*
|
|
* returns the comparator used in a depgraph dependency node as a string.
|
|
*/
|
|
const char *
|
|
pkgconf_pkg_get_comparator(pkgconf_dependency_t *pkgdep)
|
|
{
|
|
const pkgconf_pkg_comparator_name_t *i;
|
|
|
|
for (i = pkgconf_pkg_comparator_names; i->compare != PKG_CMP_SIZE; i++)
|
|
{
|
|
if (i->compare == pkgdep->compare)
|
|
return i->name;
|
|
}
|
|
|
|
return "???";
|
|
}
|
|
|
|
/*
|
|
* pkgconf_pkg_comparator_lookup_by_name(name)
|
|
*
|
|
* look up the appropriate comparator bytecode in the comparator set (defined
|
|
* above, see pkgconf_pkg_comparator_names and pkgconf_pkg_comparator_impls).
|
|
*
|
|
* XXX: on error return PKG_ANY or maybe we should return PKG_CMP_SIZE which
|
|
* is poisoned?
|
|
*/
|
|
pkgconf_pkg_comparator_t
|
|
pkgconf_pkg_comparator_lookup_by_name(const char *name)
|
|
{
|
|
const pkgconf_pkg_comparator_name_t *i;
|
|
|
|
for (i = pkgconf_pkg_comparator_names; i->compare != PKG_CMP_SIZE; i++)
|
|
{
|
|
if (!strcmp(i->name, name))
|
|
return i->compare;
|
|
}
|
|
|
|
return PKG_ANY;
|
|
}
|
|
|
|
/*
|
|
* pkg_verify_dependency(pkgdep, flags)
|
|
*
|
|
* verify a pkgconf_dependency_t node in the depgraph. if the dependency is solvable,
|
|
* return the appropriate pkg_t object, else NULL.
|
|
*/
|
|
pkg_t *
|
|
pkg_verify_dependency(pkgconf_dependency_t *pkgdep, unsigned int flags, unsigned int *eflags)
|
|
{
|
|
pkg_t *pkg = &pkg_config_virtual;
|
|
|
|
if (eflags != NULL)
|
|
*eflags = PKG_ERRF_OK;
|
|
|
|
/* pkg-config package name is special cased. */
|
|
if (strcasecmp(pkgdep->package, "pkg-config"))
|
|
{
|
|
pkg = pkg_find(pkgdep->package, flags);
|
|
if (pkg == NULL)
|
|
{
|
|
if (eflags != NULL)
|
|
*eflags |= PKG_ERRF_PACKAGE_NOT_FOUND;
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (pkg->id == NULL)
|
|
pkg->id = strdup(pkgdep->package);
|
|
|
|
if (pkgconf_pkg_comparator_impls[pkgdep->compare](pkg, pkgdep) == true)
|
|
return pkg;
|
|
|
|
if (eflags != NULL)
|
|
*eflags |= PKG_ERRF_PACKAGE_VER_MISMATCH;
|
|
|
|
return pkg;
|
|
}
|
|
|
|
/*
|
|
* pkg_verify_graph(root, depth)
|
|
*
|
|
* verify the graph dependency nodes are satisfiable by walking the tree using
|
|
* pkg_traverse().
|
|
*/
|
|
unsigned int
|
|
pkg_verify_graph(pkg_t *root, int depth, unsigned int flags)
|
|
{
|
|
return pkg_traverse(root, NULL, NULL, depth, flags);
|
|
}
|
|
|
|
static unsigned int
|
|
pkg_report_graph_error(pkg_t *parent, pkg_t *pkg, pkgconf_dependency_t *node, unsigned int eflags)
|
|
{
|
|
static bool already_sent_notice = false;
|
|
|
|
if (eflags & PKG_ERRF_PACKAGE_NOT_FOUND)
|
|
{
|
|
if (!already_sent_notice)
|
|
{
|
|
fprintf(error_msgout, "Package %s was not found in the pkg-config search path.\n", node->package);
|
|
fprintf(error_msgout, "Perhaps you should add the directory containing `%s.pc'\n", node->package);
|
|
fprintf(error_msgout, "to the PKG_CONFIG_PATH environment variable\n");
|
|
already_sent_notice = true;
|
|
}
|
|
|
|
fprintf(error_msgout, "Package '%s', required by '%s', not found\n", node->package, parent->id);
|
|
}
|
|
else if (eflags & PKG_ERRF_PACKAGE_VER_MISMATCH)
|
|
{
|
|
fprintf(error_msgout, "Package dependency requirement '%s %s %s' could not be satisfied.\n",
|
|
node->package, pkgconf_pkg_get_comparator(node), node->version);
|
|
|
|
if (pkg != NULL)
|
|
fprintf(error_msgout, "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)
|
|
pkg_unref(pkg);
|
|
|
|
return eflags;
|
|
}
|
|
|
|
static inline unsigned int
|
|
pkg_walk_list(pkg_t *parent,
|
|
pkgconf_list_t *deplist,
|
|
pkg_traverse_func_t func,
|
|
void *data,
|
|
int depth,
|
|
unsigned int flags)
|
|
{
|
|
unsigned int eflags = PKG_ERRF_OK;
|
|
pkgconf_node_t *node;
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(deplist->head, node)
|
|
{
|
|
unsigned int eflags_local = PKG_ERRF_OK;
|
|
pkgconf_dependency_t *depnode = node->data;
|
|
pkg_t *pkgdep;
|
|
|
|
if (*depnode->package == '\0')
|
|
continue;
|
|
|
|
pkgdep = pkg_verify_dependency(depnode, flags, &eflags_local);
|
|
|
|
eflags |= eflags_local;
|
|
if (eflags_local != PKG_ERRF_OK && !(flags & PKGF_SKIP_ERRORS))
|
|
{
|
|
pkg_report_graph_error(parent, pkgdep, depnode, eflags_local);
|
|
continue;
|
|
}
|
|
if (pkgdep == NULL)
|
|
continue;
|
|
|
|
if (pkgdep->flags & PKG_PROPF_SEEN)
|
|
{
|
|
pkg_unref(pkgdep);
|
|
continue;
|
|
}
|
|
|
|
pkgdep->flags |= PKG_PROPF_SEEN;
|
|
eflags |= pkg_traverse(pkgdep, func, data, depth - 1, flags);
|
|
pkgdep->flags &= ~PKG_PROPF_SEEN;
|
|
pkg_unref(pkgdep);
|
|
}
|
|
|
|
return eflags;
|
|
}
|
|
|
|
static inline unsigned int
|
|
pkg_walk_conflicts_list(pkg_t *root, pkgconf_list_t *deplist, unsigned int flags)
|
|
{
|
|
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)
|
|
{
|
|
pkg_t *pkgdep;
|
|
pkgconf_dependency_t *depnode = childnode->data;
|
|
|
|
if (*depnode->package == '\0' || strcmp(depnode->package, parentnode->package))
|
|
continue;
|
|
|
|
pkgdep = pkg_verify_dependency(parentnode, flags, &eflags);
|
|
if (eflags == PKG_ERRF_OK)
|
|
{
|
|
fprintf(error_msgout, "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 : "");
|
|
fprintf(error_msgout, "It may be possible to ignore this conflict and continue, try the\n");
|
|
fprintf(error_msgout, "PKG_CONFIG_IGNORE_CONFLICTS environment variable.\n");
|
|
|
|
pkg_unref(pkgdep);
|
|
|
|
return PKG_ERRF_PACKAGE_CONFLICT;
|
|
}
|
|
|
|
pkg_unref(pkgdep);
|
|
}
|
|
}
|
|
|
|
return PKG_ERRF_OK;
|
|
}
|
|
|
|
/*
|
|
* pkg_traverse(root, func, data, maxdepth, flags)
|
|
*
|
|
* walk the dependency graph up to maxdepth levels. -1 means infinite recursion.
|
|
*/
|
|
unsigned int
|
|
pkg_traverse(pkg_t *root,
|
|
pkg_traverse_func_t func,
|
|
void *data,
|
|
int maxdepth,
|
|
unsigned int flags)
|
|
{
|
|
unsigned int rflags = flags & ~PKGF_SKIP_ROOT_VIRTUAL;
|
|
unsigned int eflags = PKG_ERRF_OK;
|
|
|
|
if (maxdepth == 0)
|
|
return eflags;
|
|
|
|
if ((root->flags & PKG_PROPF_VIRTUAL) != PKG_PROPF_VIRTUAL || (flags & PKGF_SKIP_ROOT_VIRTUAL) != PKGF_SKIP_ROOT_VIRTUAL)
|
|
{
|
|
if (func != NULL)
|
|
func(root, data, flags);
|
|
}
|
|
|
|
if (!(flags & PKGF_SKIP_CONFLICTS))
|
|
{
|
|
eflags = pkg_walk_conflicts_list(root, &root->conflicts, rflags);
|
|
if (eflags != PKG_ERRF_OK)
|
|
return eflags;
|
|
}
|
|
|
|
eflags = pkg_walk_list(root, &root->requires, func, data, maxdepth, rflags);
|
|
if (eflags != PKG_ERRF_OK)
|
|
return eflags;
|
|
|
|
if (flags & PKGF_SEARCH_PRIVATE)
|
|
{
|
|
eflags = pkg_walk_list(root, &root->requires_private, func, data, maxdepth, rflags | PKGF_ITER_PKG_IS_PRIVATE);
|
|
if (eflags != PKG_ERRF_OK)
|
|
return eflags;
|
|
}
|
|
|
|
return eflags;
|
|
}
|
|
|
|
static void
|
|
pkg_cflags_collect(pkg_t *pkg, void *data, unsigned int flags)
|
|
{
|
|
pkgconf_list_t *list = data;
|
|
pkgconf_node_t *node;
|
|
(void) flags;
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags.head, node)
|
|
{
|
|
pkgconf_fragment_t *frag = node->data;
|
|
pkgconf_fragment_copy(list, frag, flags, false);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pkg_cflags_private_collect(pkg_t *pkg, void *data, unsigned int flags)
|
|
{
|
|
pkgconf_list_t *list = data;
|
|
pkgconf_node_t *node;
|
|
(void) flags;
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_private.head, node)
|
|
{
|
|
pkgconf_fragment_t *frag = node->data;
|
|
|
|
pkgconf_fragment_copy(list, frag, flags, true);
|
|
}
|
|
}
|
|
|
|
int
|
|
pkg_cflags(pkg_t *root, pkgconf_list_t *list, int maxdepth, unsigned int flags)
|
|
{
|
|
int eflag;
|
|
|
|
eflag = pkg_traverse(root, pkg_cflags_collect, list, maxdepth, flags);
|
|
if (eflag != PKG_ERRF_OK)
|
|
pkgconf_fragment_free(list);
|
|
|
|
if (flags & PKGF_MERGE_PRIVATE_FRAGMENTS)
|
|
{
|
|
eflag = pkg_traverse(root, pkg_cflags_private_collect, list, maxdepth, flags);
|
|
if (eflag != PKG_ERRF_OK)
|
|
pkgconf_fragment_free(list);
|
|
}
|
|
|
|
return eflag;
|
|
}
|
|
|
|
static void
|
|
pkg_libs_collect(pkg_t *pkg, void *data, unsigned int flags)
|
|
{
|
|
pkgconf_list_t *list = data;
|
|
pkgconf_node_t *node;
|
|
(void) flags;
|
|
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->libs.head, node)
|
|
{
|
|
pkgconf_fragment_t *frag = node->data;
|
|
pkgconf_fragment_copy(list, frag, flags, (flags & PKGF_ITER_PKG_IS_PRIVATE) != 0);
|
|
}
|
|
|
|
if (flags & PKGF_MERGE_PRIVATE_FRAGMENTS)
|
|
{
|
|
PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_private.head, node)
|
|
{
|
|
pkgconf_fragment_t *frag = node->data;
|
|
pkgconf_fragment_copy(list, frag, flags, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
pkg_libs(pkg_t *root, pkgconf_list_t *list, int maxdepth, unsigned int flags)
|
|
{
|
|
int eflag;
|
|
|
|
eflag = pkg_traverse(root, pkg_libs_collect, list, maxdepth, flags);
|
|
|
|
if (eflag != PKG_ERRF_OK)
|
|
{
|
|
pkgconf_fragment_free(list);
|
|
return eflag;
|
|
}
|
|
|
|
return eflag;
|
|
}
|