/* $Id: pkgconfig.c 1971 2007-04-15 14:13:18Z coudercd $ */
/*
* Copyright (c) 2003-2005 Damien Couderc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* - Neither the name of the copyright holder(s) nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/* include it first as if it was <sys/types.h> - this will avoid errors */
#include "compat/pmk_sys_types.h"
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include "compat/pmk_ctype.h"
#include "compat/pmk_stdio.h"
#include "compat/pmk_stdbool.h"
#include "compat/pmk_string.h"
#include "common.h"
#include "hash.h"
#include "hash_tools.h"
#include "parse.h"
#include "pkgconfig.h"
#include "premake.h"
/*#define PKGCFG_DEBUG 1*/
/*
* env PKG_CONFIG_PATH => list of colon separated path
* env PKG_CONFIG_LIBDIR => replace default location (aka prefix/lib/pkgconfig)
*/
/**********
constants
************************************************************************/
#define PKGCFG_HTABLE_SIZE 32
#define PKGCFG_TOK_ADDCT 1
#define PKGCFG_KW_NAME 0
#define PKGCFG_KW_DESCR 1
#define PKGCFG_KW_VERS 2
#define PKGCFG_KW_REQS 3
#define PKGCFG_KW_LIBS 4
#define PKGCFG_KW_CFLGS 5
#define PKGCFG_KW_CFLCT 6
/**********
variables
************************************************************************/
/* pc file keywords */
pkgkw kw_pkg[] = {
{"Name", PKGCFG_KW_NAME},
{"Description", PKGCFG_KW_DESCR},
{"Version", PKGCFG_KW_VERS},
{"Requires", PKGCFG_KW_REQS},
{"Libs", PKGCFG_KW_LIBS},
{"Cflags", PKGCFG_KW_CFLGS},
{"CFlags", PKGCFG_KW_CFLGS},
{"Conflicts", PKGCFG_KW_CFLCT}
};
size_t nb_pkgkw = sizeof(kw_pkg) / sizeof(pkgkw);
/**********
functions
************************************************************************/
/***************
pkgcell_init()
DESCR
initialize pkgcell structure
IN
NONE
OUT
pkgcell structure or NULL
************************************************************************/
pkgcell *pkgcell_init(void) {
pkgcell *ppc;
ppc = (pkgcell *) malloc(sizeof(pkgcell));
if (ppc == NULL) {
errorf("cannot initialize pkgcell.");
return(NULL);
}
ppc->variables = hash_init(PKGCFG_HTABLE_SIZE);
if (ppc->variables == NULL) {
free(ppc);
errorf("cannot initialize pkgcell hash table.");
return(NULL);
}
ppc->cflags = NULL;
ppc->libs = NULL;
ppc->requires = NULL;
return(ppc);
}
/******************
pkgcell_destroy()
DESCR
free pkgcell structure
IN
ppc : pkgcell structure
OUT
NONE
************************************************************************/
void pkgcell_destroy(pkgcell *ppc) {
if (ppc->name != NULL)
free(ppc->name);
if (ppc->descr != NULL)
free(ppc->descr);
if (ppc->version != NULL)
free(ppc->version);
if (ppc->requires != NULL)
free(ppc->requires);
da_destroy(ppc->cflags);
da_destroy(ppc->libs);
hash_destroy(ppc->variables);
free(ppc);
}
/***************
pkgdata_init()
DESCR
initialize pkgdata structure
IN
NONE
OUT
pkgdata structure or NULL
************************************************************************/
pkgdata *pkgdata_init(void) {
pkgdata *ppd;
ppd = (pkgdata *) malloc(sizeof(pkgdata));
if (ppd == NULL)
return(NULL);
/* init pc files hash table */
ppd->files = hash_init(PKGCFG_HT_SIZE);
if (ppd->files == NULL) {
free(ppd);
return(NULL);
}
/* init package cells hash table */
ppd->cells = hash_init_adv(PKGCFG_HT_SIZE, NULL,
(void (*)(void *))pkgcell_destroy, NULL);
if (ppd->cells == NULL) {
hash_destroy(ppd->files);
free(ppd);
return(NULL);
}
/* init recursed modules hash table */
ppd->mods = da_init();
if (ppd->mods == NULL) {
hash_destroy(ppd->files);
hash_destroy(ppd->cells);
free(ppd);
return(NULL);
}
return(ppd);
}
/******************
pkgdata_destroy()
DESCR
free pkgdata structure
IN
ppd : pkgdata structure
OUT
NONE
************************************************************************/
void pkgdata_destroy(pkgdata *ppd) {
#ifdef PKGCFG_DEBUG
debugf("clean pc files hash");
#endif
hash_destroy(ppd->files);
#ifdef PKGCFG_DEBUG
debugf("clean cells hash");
#endif
hash_destroy(ppd->cells);
#ifdef PKGCFG_DEBUG
debugf("clean modules list");
#endif
da_destroy(ppd->mods);
#ifdef PKGCFG_DEBUG
debugf("clean structure");
#endif
free(ppd);
}
/*************
skip_blank()
DESCR
skip blank character(s)
IN
pstr: current parsing cursor
OUT
new parsing cursor
***********************************************************************/
char *skip_blank(char *pstr) {
while (isblank(*pstr) != 0) {
pstr++;
}
return(pstr);
}
/***********
scan_dir()
DESCR
scan given directory for pc files
IN
dir : directory to scan
ppd : pkgdata structure
OUT
boolean
************************************************************************/
bool scan_dir(char *dir, pkgdata *ppd) {
struct dirent *pde;
DIR *pdir;
char *pstr,
buf[MAXPATHLEN],
fpath[MAXPATHLEN];
size_t l;
/* open directory */
pdir = opendir(dir);
if (pdir == NULL) {
errorf("cannot open '%s' directory : %s.",
dir, strerror(errno));
return(false);
}
/* parse directory */
do {
pde = readdir(pdir);
if (pde != NULL) {
/* got an entry */
pstr = pde->d_name;
l = strlen(pstr);
/* check if it's a .pc file */
if ((pstr[l-3] == '.') && (pstr[l-2] == 'p')
&& (pstr[l-1] == 'c')) {
/* build pkg name */
if (strlcpy_b(buf, pstr,
sizeof(buf)) == false) {
/* XXX err msg ? */
closedir(pdir);
return(false);
}
buf[l-3] = CHAR_EOS;
/* build full path of pc file */
strlcpy(fpath, dir, sizeof(fpath)); /* no check */
strlcat(fpath, "/", sizeof(fpath)); /* no check */
if (strlcat_b(fpath, pstr, sizeof(fpath)) == false) {
/* XXX err msg ? */
closedir(pdir);
return(false);
}
hash_update_dup(ppd->files, buf, fpath);
/* XXX detect if the package has already been detected ? */
#ifdef PKGCFG_DEBUG
debugf("add module '%s' with file '%s'", buf, pstr);
#endif
}
}
} while (pde != NULL);
closedir(pdir);
return(true);
}
/**************
pkg_collect()
DESCR
collect packages data
IN
pkglibdir : default packages data directory
ppd : pkgdata structure
OUT
return : boolean
************************************************************************/
bool pkg_collect(char *pkglibdir, pkgdata *ppd) {
char *pstr;
dynary *ppath,
*ppcpath;
unsigned int i;
pstr = getenv(PMKCFG_ENV_PATH);
if (pstr != NULL) {
/* also scan path provided in env variable */
ppath = str_to_dynary(pstr, PKGCFG_CHAR_PATH_SEP);
for (i = 0 ; i < da_usize(ppath) ; i++) {
pstr = da_idx(ppath, i);
if (scan_dir(pstr, ppd) == false) {
da_destroy(ppath);
return(false);
}
}
da_destroy(ppath);
}
/* check if pkg-config library directory has been set in env variable */
pstr = getenv(PMKCFG_ENV_LIBDIR);
if (pstr == NULL) {
/* env variable is not set, getting pmk.conf variable */
ppcpath = str_to_dynary(pkglibdir, PKGCFG_CHAR_PATH_SEP);
} else {
/* else use env variable */
ppcpath = str_to_dynary(pstr, PKGCFG_CHAR_PATH_SEP);
}
/* scan all path for pc files */
for (i = 0 ; i < da_usize(ppcpath) ; i++) {
/* get directory */
pstr = da_idx(ppcpath, i);
/* scan directory */
if (scan_dir(pstr, ppd) == false) {
return(false);
}
}
return(true);
}
/****************
parse_keyword()
DESCR
parse keyword and set data
IN
ppc : pkgcell structure
kword : keyword
value : value to assign
OUT
boolean
************************************************************************/
bool parse_keyword(pkgcell *ppc, char *kword, char *value) {
unsigned int i;
for (i = 0 ; i < nb_pkgkw ; i ++) {
if (strncmp(kword, kw_pkg[i].kw_name, sizeof(kword)) == 0) {
/* assgin keyword */
switch (kw_pkg[i].kw_id) {
case PKGCFG_KW_NAME :
ppc->name = strdup(value);
if (ppc->name == NULL) {
errorf(ERRMSG_MEM);
return(false);
}
break;
case PKGCFG_KW_DESCR :
ppc->descr = strdup(value);
if (ppc->descr == NULL) {
errorf(ERRMSG_MEM);
return(false);
}
break;
case PKGCFG_KW_VERS :
ppc->version = strdup(value);
if (ppc->version == NULL) {
errorf(ERRMSG_MEM);
return(false);
}
break;
case PKGCFG_KW_REQS :
if (*value != CHAR_EOS) {
ppc->requires = strdup(value);
if (ppc->requires == NULL) {
errorf(ERRMSG_MEM);
return(false);
}
}
break;
case PKGCFG_KW_CFLGS :
ppc->cflags = str_to_dynary(value, ' ');
break;
case PKGCFG_KW_LIBS :
ppc->libs = str_to_dynary(value, ' ');
break;
case PKGCFG_KW_CFLCT :
/* unused */
break;
default :
break;
}
return(true);
}
}
return(true);
}
/********************
process_variables()
DESCR
process string to substitute variables with their values
IN
pstr : string to process
pht : hash table where variables are stored
OUT
new string or NULL
************************************************************************/
char *process_variables(char *pstr, htable *pht) {
bool bs = false;
char buf[OPT_VALUE_LEN],
var[OPT_NAME_LEN],
*pvar,
*pbuf;
size_t size;
size = sizeof(buf);
pbuf = buf;
while ((*pstr != CHAR_EOS) && (size > 0)) {
switch(*pstr) {
case '\\' :
bs = true;
pstr++;
break;
case '$' :
if (bs == false) {
/* found variable */
pstr++;
/* skip '{' */
pstr++;
pstr = parse_idtf(pstr, var, sizeof(var));
if (pstr == NULL) {
/* debugf("parse_idtf returned null."); */
return(NULL);
} else {
/* check if identifier exists */
pvar = hash_get(pht, var);
if (pvar != NULL) {
/* identifier found, append value */
while ((*pvar != CHAR_EOS) && (size > 0)) {
*pbuf = *pvar;
pbuf++;
pvar++;
size--;
}
/* skip '}' */
pstr++;
}
}
} else {
/* copy character */
*pbuf = *pstr;
pbuf++;
pstr++;
size--;
bs = false;
}
break;
default :
if (bs == true) {
*pbuf = '\\';
pbuf++;
pstr++;
size--;
if (size == 0) {
/* debugf("overflow."); */
return(NULL);
}
bs = false;
}
/* copy character */
*pbuf = *pstr;
pbuf++;
pstr++;
size--;
break;
}
}
if (size == 0) {
/* debugf("overflow."); */
return(NULL);
}
*pbuf = CHAR_EOS;
return(strdup(buf));
}
/****************
parse_pc_file()
DESCR
parse pc file
IN
pcfile : file to parse
OUT
pkgcell structure or NULL
************************************************************************/
pkgcell *parse_pc_file(char *pcfile) {
FILE *fp;
char *pstr,
*pps,
buf[TMP_BUF_LEN],
line[TMP_BUF_LEN];
pkgcell *ppc;
fp = fopen(pcfile, "r");
if (fp == NULL) {
errorf("cannot open .pc file '%s' : %s.", pcfile, strerror(errno));
return(NULL);
}
ppc = pkgcell_init();
if (ppc == NULL) {
errorf("cannot initialize pkgcell.");
return(NULL);
}
/* main parsing */
while (get_line(fp, line, sizeof(line)) == true) {
/* collect identifier */
pstr = parse_idtf(line, buf, sizeof(buf));
switch (*pstr) {
case ':' :
/* keyword */
pstr++;
pstr = skip_blank(pstr);
/* process variables */
pps = process_variables(pstr, ppc->variables);
#ifdef PKGCFG_DEBUG
debugf("keyword = '%s', value = '%s', string = '%s'", buf, pps, pstr);
#endif
/* set variable with parsed data */
if (parse_keyword(ppc, buf, pps) == false) {
errorf("cannot fill pkgcell structure (keyword).");
pkgcell_destroy(ppc);
return(NULL);
}
break;
case '=' :
/* variable */
pstr++;
pstr = skip_blank(pstr);
/* process variables */
pps = process_variables(pstr, ppc->variables);
#ifdef PKGCFG_DEBUG
debugf("variable = '%s', value = '%s', string = '%s'", buf, pps, pstr);
#endif
/* store variable in hash */
if (hash_update_dup(ppc->variables, buf, pps) == HASH_ADD_FAIL) {
errorf("cannot fill pkgcell structure (variables).");
pkgcell_destroy(ppc);
return(NULL);
}
break;
default :
/* unknown or empty line */
#ifdef PKGCFG_DEBUG
debugf("unknown = '%s'", line);
#endif
break;
}
}
fclose(fp);
return(ppc);
}
/***************
pkg_cell_add()
DESCR
insert a package cell and return a pointer on it
IN
mod : module name to process
ppd : packages data structure
OUT
pkgcell structure or NULL
************************************************************************/
pkgcell *pkg_cell_add(pkgdata *ppd, char *mod) {
char *pcf;
pkgcell *ppc;
/* get pc file name */
pcf = hash_get(ppd->files, mod); /* XXX check if NULL ?? */
/* parse pc file */
ppc = parse_pc_file(pcf);
if (ppc == NULL)
return(NULL);
/* store pkgcell in hash */
if (hash_update(ppd->cells, mod, ppc) == HASH_ADD_FAIL) {
return(NULL);
#ifdef PKGCFG_DEBUG
} else {
debugf("adding pkgcell for '%s'", mod);
#endif
}
/* add module in list */
da_push(ppd->mods, strdup(mod));
if (ppc->requires != NULL) {
#ifdef PKGCFG_DEBUG
debugf("pkgcell requires = '%s'", ppc->requires);
#endif
if (pkg_recurse(ppd, ppc->requires) == false) {
pkgcell_destroy(ppc);
return(NULL);
}
}
return(ppc);
}
/**************
pkg_recurse()
DESCR
recurse packages
IN
ppd : packages data structure
reqs : requires string
OUT
boolean
************************************************************************/
bool pkg_recurse(pkgdata *ppd, char *reqs) {
char *mod;
dynary *pda;
unsigned int i;
#ifdef PKGCFG_DEBUG
debugf("recursing '%s'", reqs);
#endif
pda = str_to_dynary_adv(reqs, " ,");
if (pda == NULL) {
errorf("failed to process requirements in pkg_recurse().");
return(false);
}
for (i = 0 ; i < da_usize(pda) ; i++) {
/* get module name */
mod = da_idx(pda, i);
/* check if module has been already processed */
if (hash_get(ppd->cells, mod) == NULL) {
if (pkg_cell_add(ppd, mod) == NULL) {
errorf("failed to add a cell in pkg_recurse().");
da_destroy(pda);
return(false);
}
}
}
da_destroy(pda);
return(true);
}
/********************
pkg_single_append()
DESCR
append module name if it does not already exists
IN
ostr : modules list string
astr : module to append
OUT
return : new list of modules or NULL
************************************************************************/
char *pkg_single_append(char *ostr, char *astr) {
char *pstr,
*buf;
size_t s;
#ifdef PKGCFG_DEBUG
debugf("single_append '%s', '%s'", ostr, astr);
#endif
if (*astr == CHAR_EOS)
return(ostr);
if ((*ostr != CHAR_EOS) && (ostr != NULL)) {
pstr = strstr(ostr, astr);
while (pstr != NULL) {
pstr = pstr + strlen (astr);
if ((*pstr == ' ') || (*pstr == CHAR_EOS)) {
/* found existing value */
return(ostr);
}
pstr = strstr(pstr, astr);
}
/* 2 is for the separator and the end of file char */
s = strlen(ostr) + strlen(astr) + 2;
buf = (char *) malloc(s);
if (buf == NULL)
return(NULL);
if (snprintf_b(buf, s, "%s %s", ostr, astr) == false) {
return(NULL);
}
free(ostr);
} else {
buf = strdup(astr);
}
return(buf);
}
/*****************
pkg_get_cflags()
DESCR
get recursed list of cflags
IN
ppd : packages data structure
OUT
cflags string
************************************************************************/
char *pkg_get_cflags(pkgdata *ppd) {
return(pkg_get_cflags_adv(ppd, PKGCFG_CFLAGS_ALL));
}
/*********************
pkg_get_cflags_adv()
DESCR
get recursed list of cflags with output filtering
IN
ppd : packages data structure
opts : output filtering options
OUT
cflags string
************************************************************************/
char *pkg_get_cflags_adv(pkgdata *ppd, unsigned int opts) {
char *cflags = "",
*pstr;
dynary *pda;
pkgcell *ppc;
unsigned int i,
j,
o;
pda = ppd->mods;
for (i = 0 ; i < da_usize(pda) ; i++) {
/* get module name */
ppc = hash_get(ppd->cells, da_idx(pda, i));
/* append each element but avoid duplicate*/
for (j = 0 ; j < da_usize(ppc->cflags) ; j++) {
/* get element */
pstr = da_idx(ppc->cflags, j);
/* check type of element */
if (pstr[0] == '-' && pstr[1] == 'I') {
/* include path */
o = PKGCFG_CFLAGS_I;
} else {
/* other */
o = PKGCFG_CFLAGS_o;
}
/* append if not filtered */
if ((opts & o) != 0)
cflags = pkg_single_append(cflags, pstr);
}
}
return(cflags);
}
/***************
pkg_get_libs()
DESCR
get recursed list of libs
IN
ppd : packages data structure
OUT
libs string
************************************************************************/
char *pkg_get_libs(pkgdata *ppd) {
return(pkg_get_libs_adv(ppd, PKGCFG_LIBS_ALL));
}
/*******************
pkg_get_libs_adv()
DESCR
get recursed list of libs with output filtering
IN
ppd : packages data structure
opts : output filtering options
OUT
libs string
************************************************************************/
char *pkg_get_libs_adv(pkgdata *ppd, unsigned int opts) {
char *libs = "",
*pstr;
dynary *pda;
pkgcell *ppc;
unsigned int i,
j,
o;
pda = ppd->mods;
for (i = 0 ; i < da_usize(pda) ; i++) {
/* get module name */
ppc = hash_get(ppd->cells, da_idx(pda, i));
/* append each element but avoid duplicate*/
for (j = 0 ; j < da_usize(ppc->libs) ; j++) {
/* get element */
pstr = da_idx(ppc->libs, j);
/* check type of element */
if (pstr[0] == '-') {
switch (pstr[1]) {
case 'L':
/* lib path */
o = PKGCFG_LIBS_L;
break;
case 'l':
/* lib name */
o = PKGCFG_LIBS_l;
break;
default:
/* other */
o = PKGCFG_LIBS_o;
break;
}
} else {
/* other */
o = PKGCFG_LIBS_o;
}
/* append if not filtered */
if ((opts & o) != 0)
libs = pkg_single_append(libs, pstr);
}
}
return(libs);
}
/*****************
pkg_mod_exists()
DESCR
check if the given module exists
IN
mod : module to check
OUT
boolean
************************************************************************/
bool pkg_mod_exists(pkgdata *ppd, char *mod) {
if (hash_get(ppd->files, mod) == NULL) {
return(false);
} else {
return(true);
}
}
/******************
compare_version()
DESCR
compare two version strings
IN
vref : reference version
vcomp : version to check
OUT
<0 if vref is greater than vcomp
=0 if vref is equal to vcomp
>0 if vref is smaller than vcomp
************************************************************************/
int compare_version(char *vref, char *vcomp) {
bool bexit = false;
char *sr,
*sc;
dynary *vr,
*vc;
int delta,
ref,
cmp;
unsigned int i = 0;
unsigned long tl;
/* need to check da_* returns */
vr = str_to_dynary(vref, VERSION_CHAR_SEP);
if (vr == NULL) {
errorf("cannot parse reference version '%s'.", vref);
return(false);
}
vc = str_to_dynary(vcomp, VERSION_CHAR_SEP);
if (vc == NULL) {
errorf("cannot parse comparison version '%s'.", vcomp);
return(false);
}
while (bexit == false) {
/* process reference version */
sr = da_idx(vr, i);
if (sr != NULL) {
/* convert string */
if (str_to_ulong(sr, 10, &tl) == false) {
errorf("cannot get numerical value of '%s'.", sr);
return(false);
}
ref = (int) tl;
} else {
/* end of version string */
ref = 0;
bexit = true;
}
/* process compared version */
sc = da_idx(vc, i);
if (sc != NULL) {
if (str_to_ulong(sc, 10, &tl) == false) {
errorf("cannot get numerical value of '%s'.", sc);
return(false);
}
cmp = (int) tl;
} else {
/* end of version string */
cmp = 0;
bexit = true;
}
/* compare versions */
delta = cmp - ref;
if (delta != 0) {
/* not equal end of comparison */
bexit = true;
} else {
i++;
}
}
/* destroy dynaries */
da_destroy(vr);
da_destroy(vc);
return(delta);
}
syntax highlighted by Code2HTML, v. 0.9.1