summaryrefslogtreecommitdiff
path: root/parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'parse.c')
-rw-r--r--parse.c1561
1 files changed, 1561 insertions, 0 deletions
diff --git a/parse.c b/parse.c
new file mode 100644
index 0000000..48f6f2b
--- /dev/null
+++ b/parse.c
@@ -0,0 +1,1561 @@
+/*
+ * Copyright (C) 2006-2010 Tollef Fog Heen <tfheen@err.no>
+ * Copyright (C) 2001, 2002, 2005-2006 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "parse.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <popt.h>
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include <sys/types.h>
+
+#ifdef G_OS_WIN32
+int dont_define_prefix = FALSE;
+char *prefix_variable = "prefix";
+int msvc_syntax = FALSE;
+#endif
+
+#ifdef G_OS_WIN32
+#ifndef G_IS_DIR_SEPARATOR
+#define G_IS_DIR_SEPARATOR(c) ((c) == G_DIR_SEPARATOR || (c) == '/')
+#endif
+#endif
+
+/**
+ * Read an entire line from a file into a buffer. Lines may
+ * be delimited with '\n', '\r', '\n\r', or '\r\n'. The delimiter
+ * is not written into the buffer. Text after a '#' character is treated as
+ * a comment and skipped. '\' can be used to escape a # character.
+ * '\' proceding a line delimiter combines adjacent lines. A '\' proceding
+ * any other character is ignored and written into the output buffer
+ * unmodified.
+ *
+ * Return value: %FALSE if the stream was already at an EOF character.
+ **/
+static gboolean
+read_one_line (FILE *stream, GString *str)
+{
+ gboolean quoted = FALSE;
+ gboolean comment = FALSE;
+ int n_read = 0;
+
+ g_string_truncate (str, 0);
+
+ while (1)
+ {
+ int c;
+
+ c = getc (stream);
+
+ if (c == EOF)
+ {
+ if (quoted)
+ g_string_append_c (str, '\\');
+
+ goto done;
+ }
+ else
+ n_read++;
+
+ if (quoted)
+ {
+ quoted = FALSE;
+
+ switch (c)
+ {
+ case '#':
+ g_string_append_c (str, '#');
+ break;
+ case '\r':
+ case '\n':
+ {
+ int next_c = getc (stream);
+
+ if (!(c == EOF ||
+ (c == '\r' && next_c == '\n') ||
+ (c == '\n' && next_c == '\r')))
+ ungetc (next_c, stream);
+
+ break;
+ }
+ default:
+ g_string_append_c (str, '\\');
+ g_string_append_c (str, c);
+ }
+ }
+ else
+ {
+ switch (c)
+ {
+ case '#':
+ comment = TRUE;
+ break;
+ case '\\':
+ if (!comment)
+ quoted = TRUE;
+ break;
+ case '\n':
+ {
+ int next_c = getc (stream);
+
+ if (!(c == EOF ||
+ (c == '\r' && next_c == '\n') ||
+ (c == '\n' && next_c == '\r')))
+ ungetc (next_c, stream);
+
+ goto done;
+ }
+ default:
+ if (!comment)
+ g_string_append_c (str, c);
+ }
+ }
+ }
+
+ done:
+
+ return n_read > 0;
+}
+
+static char *
+trim_string (const char *str)
+{
+ int len;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ while (*str && isspace ((guchar)*str))
+ str++;
+
+ len = strlen (str);
+ while (len > 0 && isspace ((guchar)str[len-1]))
+ len--;
+
+ return g_strndup (str, len);
+}
+
+static char *
+trim_and_sub (Package *pkg, const char *str, const char *path)
+{
+ char *trimmed;
+ GString *subst;
+ char *p;
+
+ trimmed = trim_string (str);
+
+ subst = g_string_new ("");
+
+ p = trimmed;
+ while (*p)
+ {
+ if (p[0] == '$' &&
+ p[1] == '$')
+ {
+ /* escaped $ */
+ g_string_append_c (subst, '$');
+ p += 2;
+ }
+ else if (p[0] == '$' &&
+ p[1] == '{')
+ {
+ /* variable */
+ char *var_start;
+ char *varname;
+ char *varval;
+
+ var_start = &p[2];
+
+ /* Get up to close brace. */
+ while (*p && *p != '}')
+ ++p;
+
+ varname = g_strndup (var_start, p - var_start);
+
+ ++p; /* past brace */
+
+ varval = package_get_var (pkg, varname);
+
+ if (varval == NULL)
+ {
+ verbose_error ("Variable '%s' not defined in '%s'\n",
+ varname, path);
+
+ exit (1);
+ }
+
+ g_free (varname);
+
+ g_string_append (subst, varval);
+ g_free (varval);
+ }
+ else
+ {
+ g_string_append_c (subst, *p);
+
+ ++p;
+ }
+ }
+
+ g_free (trimmed);
+ p = subst->str;
+ g_string_free (subst, FALSE);
+
+ return p;
+}
+
+static void
+parse_name (Package *pkg, const char *str, const char *path)
+{
+ if (pkg->name)
+ {
+ verbose_error ("Name field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ pkg->name = trim_and_sub (pkg, str, path);
+}
+
+static void
+parse_version (Package *pkg, const char *str, const char *path)
+{
+ if (pkg->version)
+ {
+ verbose_error ("Version field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ pkg->version = trim_and_sub (pkg, str, path);
+}
+
+static void
+parse_description (Package *pkg, const char *str, const char *path)
+{
+ if (pkg->description)
+ {
+ verbose_error ("Description field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ pkg->description = trim_and_sub (pkg, str, path);
+}
+
+
+#define MODULE_SEPARATOR(c) ((c) == ',' || isspace ((guchar)(c)))
+#define OPERATOR_CHAR(c) ((c) == '<' || (c) == '>' || (c) == '!' || (c) == '=')
+
+/* A module list is a list of modules with optional version specification,
+ * separated by commas and/or spaces. Commas are treated just like whitespace,
+ * in order to allow stuff like: Requires: @FRIBIDI_PC@, glib, gmodule
+ * where @FRIBIDI_PC@ gets substituted to nothing or to 'fribidi'
+ */
+
+typedef enum
+{
+ /* put numbers to help interpret lame debug spew ;-) */
+ OUTSIDE_MODULE = 0,
+ IN_MODULE_NAME = 1,
+ BEFORE_OPERATOR = 2,
+ IN_OPERATOR = 3,
+ AFTER_OPERATOR = 4,
+ IN_MODULE_VERSION = 5
+} ModuleSplitState;
+
+#define PARSE_SPEW 0
+
+static GSList*
+split_module_list (const char *str, const char *path)
+{
+ GSList *retval = NULL;
+ const char *p;
+ const char *start;
+ ModuleSplitState state = OUTSIDE_MODULE;
+ ModuleSplitState last_state = OUTSIDE_MODULE;
+
+ /* fprintf (stderr, "Parsing: '%s'\n", str); */
+
+ start = str;
+ p = str;
+
+ while (*p)
+ {
+#if PARSE_SPEW
+ fprintf (stderr, "p: %c state: %d last_state: %d\n", *p, state, last_state);
+#endif
+
+ switch (state)
+ {
+ case OUTSIDE_MODULE:
+ if (!MODULE_SEPARATOR (*p))
+ state = IN_MODULE_NAME;
+ break;
+
+ case IN_MODULE_NAME:
+ if (isspace ((guchar)*p))
+ {
+ /* Need to look ahead to determine next state */
+ const char *s = p;
+ while (*s && isspace ((guchar)*s))
+ ++s;
+
+ if (*s == '\0')
+ state = OUTSIDE_MODULE;
+ else if (MODULE_SEPARATOR (*s))
+ state = OUTSIDE_MODULE;
+ else if (OPERATOR_CHAR (*s))
+ state = BEFORE_OPERATOR;
+ else
+ state = OUTSIDE_MODULE;
+ }
+ else if (MODULE_SEPARATOR (*p))
+ state = OUTSIDE_MODULE; /* comma precludes any operators */
+ break;
+
+ case BEFORE_OPERATOR:
+ /* We know an operator is coming up here due to lookahead from
+ * IN_MODULE_NAME
+ */
+ if (isspace ((guchar)*p))
+ ; /* no change */
+ else if (OPERATOR_CHAR (*p))
+ state = IN_OPERATOR;
+ else
+ g_assert_not_reached ();
+ break;
+
+ case IN_OPERATOR:
+ if (!OPERATOR_CHAR (*p))
+ state = AFTER_OPERATOR;
+ break;
+
+ case AFTER_OPERATOR:
+ if (!isspace ((guchar)*p))
+ state = IN_MODULE_VERSION;
+ break;
+
+ case IN_MODULE_VERSION:
+ if (MODULE_SEPARATOR (*p))
+ state = OUTSIDE_MODULE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (state == OUTSIDE_MODULE &&
+ last_state != OUTSIDE_MODULE)
+ {
+ /* We left a module */
+ char *module = g_strndup (start, p - start);
+ retval = g_slist_prepend (retval, module);
+
+#if PARSE_SPEW
+ fprintf (stderr, "found module: '%s'\n", module);
+#endif
+
+ /* reset start */
+ start = p;
+ }
+
+ last_state = state;
+ ++p;
+ }
+
+ if (p != start)
+ {
+ /* get the last module */
+ char *module = g_strndup (start, p - start);
+ retval = g_slist_prepend (retval, module);
+
+#if PARSE_SPEW
+ fprintf (stderr, "found module: '%s'\n", module);
+#endif
+
+ }
+
+ retval = g_slist_reverse (retval);
+
+ return retval;
+}
+
+GSList*
+parse_module_list (Package *pkg, const char *str, const char *path)
+{
+ GSList *split;
+ GSList *iter;
+ GSList *retval = NULL;
+
+ split = split_module_list (str, path);
+
+ iter = split;
+ while (iter != NULL)
+ {
+ RequiredVersion *ver;
+ char *p;
+ char *start;
+
+ p = iter->data;
+
+ ver = g_new0 (RequiredVersion, 1);
+ ver->comparison = ALWAYS_MATCH;
+ ver->owner = pkg;
+ retval = g_slist_prepend (retval, ver);
+
+ while (*p && MODULE_SEPARATOR (*p))
+ ++p;
+
+ start = p;
+
+ while (*p && !isspace ((guchar)*p))
+ ++p;
+
+ while (*p && MODULE_SEPARATOR (*p))
+ {
+ *p = '\0';
+ ++p;
+ }
+
+ if (*start == '\0')
+ {
+ verbose_error ("Empty package name in Requires or Conflicts in file '%s'\n", path);
+
+ exit (1);
+ }
+
+ ver->name = g_strdup (start);
+
+ start = p;
+
+ while (*p && !isspace ((guchar)*p))
+ ++p;
+
+ while (*p && isspace ((guchar)*p))
+ {
+ *p = '\0';
+ ++p;
+ }
+
+ if (*start != '\0')
+ {
+ if (strcmp (start, "=") == 0)
+ ver->comparison = EQUAL;
+ else if (strcmp (start, ">=") == 0)
+ ver->comparison = GREATER_THAN_EQUAL;
+ else if (strcmp (start, "<=") == 0)
+ ver->comparison = LESS_THAN_EQUAL;
+ else if (strcmp (start, ">") == 0)
+ ver->comparison = GREATER_THAN;
+ else if (strcmp (start, "<") == 0)
+ ver->comparison = LESS_THAN;
+ else if (strcmp (start, "!=") == 0)
+ ver->comparison = NOT_EQUAL;
+ else
+ {
+ verbose_error ("Unknown version comparison operator '%s' after package name '%s' in file '%s'\n", start, ver->name, path);
+
+ exit (1);
+ }
+ }
+
+ start = p;
+
+ while (*p && !MODULE_SEPARATOR (*p))
+ ++p;
+
+ while (*p && MODULE_SEPARATOR (*p))
+ {
+ *p = '\0';
+ ++p;
+ }
+
+ if (ver->comparison != ALWAYS_MATCH && *start == '\0')
+ {
+ verbose_error ("Comparison operator but no version after package name '%s' in file '%s'\n", ver->name, path);
+
+ exit (1);
+ }
+
+ if (*start != '\0')
+ {
+ ver->version = g_strdup (start);
+ }
+
+ g_assert (ver->name);
+
+ iter = g_slist_next (iter);
+ }
+
+ g_slist_foreach (split, (GFunc) g_free, NULL);
+ g_slist_free (split);
+
+ retval = g_slist_reverse (retval);
+
+ return retval;
+}
+
+static void
+parse_requires (Package *pkg, const char *str, const char *path)
+{
+ GSList *parsed;
+ GSList *iter;
+ char *trimmed;
+
+ if (pkg->requires)
+ {
+ verbose_error ("Requires field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+ parsed = parse_module_list (pkg, trimmed, path);
+ g_free (trimmed);
+
+ iter = parsed;
+ while (iter != NULL)
+ {
+ Package *req;
+ RequiredVersion *ver = iter->data;
+
+ req = get_package (ver->name);
+
+ if (req == NULL)
+ {
+ verbose_error ("Package '%s', required by '%s', not found\n",
+ ver->name, pkg->name ? pkg->name : path);
+
+ exit (1);
+ }
+
+ if (pkg->required_versions == NULL)
+ pkg->required_versions = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_hash_table_insert (pkg->required_versions, ver->name, ver);
+
+ pkg->requires = g_slist_prepend (pkg->requires, req);
+
+ iter = g_slist_next (iter);
+ }
+
+ g_slist_free (parsed);
+}
+
+static void
+parse_requires_private (Package *pkg, const char *str, const char *path)
+{
+ GSList *parsed;
+ GSList *iter;
+ char *trimmed;
+
+ if (pkg->requires_private)
+ {
+ verbose_error ("Requires.private field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+ parsed = parse_module_list (pkg, trimmed, path);
+ g_free (trimmed);
+
+ iter = parsed;
+ while (iter != NULL)
+ {
+ Package *req;
+ RequiredVersion *ver = iter->data;
+
+ req = get_package (ver->name);
+
+ if (req == NULL)
+ {
+ verbose_error ("Package '%s', required by '%s', not found\n",
+ ver->name, pkg->name ? pkg->name : path);
+
+ exit (1);
+ }
+
+ if (pkg->required_versions == NULL)
+ pkg->required_versions = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_hash_table_insert (pkg->required_versions, ver->name, ver);
+
+ pkg->requires_private = g_slist_prepend (pkg->requires_private, req);
+
+ iter = g_slist_next (iter);
+ }
+
+ g_slist_free (parsed);
+}
+
+static void
+parse_conflicts (Package *pkg, const char *str, const char *path)
+{
+ GSList *parsed;
+ GSList *iter;
+ char *trimmed;
+
+ if (pkg->conflicts)
+ {
+ verbose_error ("Conflicts field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+ pkg->conflicts = parse_module_list (pkg, trimmed, path);
+ g_free (trimmed);
+}
+
+static char *strdup_escape_shell(const char *s)
+{
+ size_t r_s = strlen(s)+10, c = 0;
+ char *r = g_malloc(r_s);
+ while (s[0]) {
+ if ((s[0] < '+') ||
+ (s[0] > ':' && s[0] < '=') ||
+ (s[0] > '=' && s[0] < '@') ||
+ (s[0] > 'Z' && s[0] < '^') ||
+ (s[0] == '`') ||
+ (s[0] > 'z')) {
+ r[c] = '\\';
+ c++;
+ }
+ r[c] = *s;
+ c++;
+ if (c+2 >= r_s) {
+ r_s *= 2;
+ r = g_realloc(r, r_s);
+ }
+ s++;
+ }
+ r[c] = 0;
+ return r;
+}
+
+static void _do_parse_libs (Package *pkg, int argc, char **argv)
+{
+ int i;
+#ifdef G_OS_WIN32
+ char *L_flag = (msvc_syntax ? "/libpath:" : "-L");
+ char *l_flag = (msvc_syntax ? "" : "-l");
+ char *lib_suffix = (msvc_syntax ? ".lib" : "");
+#else
+ char *L_flag = "-L";
+ char *l_flag = "-l";
+ char *lib_suffix = "";
+#endif
+
+ i = 0;
+ while (i < argc)
+ {
+ char *tmp = trim_string (argv[i]);
+ char *arg = strdup_escape_shell(tmp);
+ char *p;
+ p = arg;
+ g_free(tmp);
+
+ if (p[0] == '-' &&
+ p[1] == 'l' &&
+ /* -lib: is used by the C# compiler for libs; it's not an -l
+ flag. */
+ (strncmp(p, "-lib:", 5) != 0))
+ {
+ p += 2;
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ pkg->l_libs = g_slist_prepend (pkg->l_libs,
+ g_strconcat (l_flag, p, lib_suffix, NULL));
+
+ }
+ else if (p[0] == '-' &&
+ p[1] == 'L')
+ {
+ p += 2;
+ while (*p && isspace ((guchar)*p))
+ ++p;
+ pkg->L_libs = g_slist_prepend (pkg->L_libs,
+ g_strconcat (L_flag, p, NULL));
+ }
+ else if (strcmp("-framework",p) == 0 && i+1 < argc)
+ {
+ /* Mac OS X has a -framework Foo which is really one option,
+ * so we join those to avoid having -framework Foo
+ * -framework Bar being changed into -framework Foo Bar
+ * later
+ */
+ gchar *framework, *tmp = trim_string (argv[i+1]);
+
+ framework = strdup_escape_shell(tmp);
+ pkg->other_libs = g_slist_prepend (pkg->other_libs,
+ g_strconcat(arg, " ", framework, NULL));
+ i++;
+ g_free(framework);
+ g_free(tmp);
+ }
+ else
+ {
+ if (*arg != '\0')
+ pkg->other_libs = g_slist_prepend (pkg->other_libs,
+ g_strdup (arg));
+ }
+
+ g_free (arg);
+
+ ++i;
+ }
+
+}
+
+
+static void
+parse_libs (Package *pkg, const char *str, const char *path)
+{
+ /* Strip out -l and -L flags, put them in a separate list. */
+
+ char *trimmed;
+ char **argv = NULL;
+ int argc = 0;
+ int result;
+
+ if (pkg->libs_num > 0)
+ {
+ verbose_error ("Libs field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+
+ if (trimmed && *trimmed)
+ {
+ result = poptParseArgvString (trimmed, &argc, &argv);
+
+ if (result < 0)
+ {
+ verbose_error ("Couldn't parse Libs field into an argument vector: %s\n",
+ poptStrerror (result));
+
+ exit (1);
+ }
+ }
+
+ _do_parse_libs(pkg, argc, argv);
+
+ g_free (trimmed);
+ g_free (argv);
+ pkg->libs_num++;
+}
+
+static void
+parse_libs_private (Package *pkg, const char *str, const char *path)
+{
+ /*
+ List of private libraries. Private libraries are libraries which
+ are needed in the case of static linking or on platforms not
+ supporting inter-library dependencies. They are not supposed to
+ be used for libraries which are exposed through the library in
+ question. An example of an exposed library is GTK+ exposing Glib.
+ A common example of a private library is libm.
+
+ Generally, if include another library's headers in your own, it's
+ a public dependency and not a private one.
+ */
+
+ char *trimmed;
+ char **argv = NULL;
+ int argc = 0;
+ int result;
+
+ if (pkg->libs_private_num > 0)
+ {
+ verbose_error ("Libs.private field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+
+ if (trimmed && *trimmed)
+ {
+ result = poptParseArgvString (trimmed, &argc, &argv);
+
+ if (result < 0)
+ {
+ verbose_error ("Couldn't parse Libs.private field into an argument vector: %s\n",
+ poptStrerror (result));
+
+ exit (1);
+ }
+ }
+
+ _do_parse_libs(pkg, argc, argv);
+
+ g_free (argv);
+ g_free (trimmed);
+
+ pkg->libs_private_num++;
+}
+
+static void
+parse_cflags (Package *pkg, const char *str, const char *path)
+{
+ /* Strip out -I flags, put them in a separate list. */
+
+ char *trimmed;
+ char **argv = NULL;
+ int argc = 0;
+ int result;
+ int i;
+
+ if (pkg->I_cflags || pkg->other_cflags)
+ {
+ verbose_error ("Cflags field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ trimmed = trim_and_sub (pkg, str, path);
+
+ if (trimmed && *trimmed)
+ {
+ result = poptParseArgvString (trimmed, &argc, &argv);
+
+ if (result < 0)
+ {
+ verbose_error ("Couldn't parse Cflags field into an argument vector: %s\n",
+ poptStrerror (result));
+
+ exit (1);
+ }
+ }
+
+ i = 0;
+ while (i < argc)
+ {
+ char *tmp = trim_string (argv[i]);
+ char *arg = strdup_escape_shell(tmp);
+ char *p = arg;
+ g_free(tmp);
+
+ if (p[0] == '-' &&
+ p[1] == 'I')
+ {
+ p += 2;
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ pkg->I_cflags = g_slist_prepend (pkg->I_cflags,
+ g_strconcat ("-I", p, NULL));
+
+ } else {
+ if (*arg != '\0')
+ pkg->other_cflags = g_slist_prepend (pkg->other_cflags,
+ g_strdup (arg));
+ if (strcmp("-idirafter", arg) == 0) {
+ tmp = trim_string(argv[++i]);
+ char *n = strdup_escape_shell(tmp);
+ pkg->other_cflags = g_slist_prepend(pkg->other_cflags, n);
+ g_free(tmp);
+ }
+ }
+
+ g_free (arg);
+
+ ++i;
+ }
+
+ g_free (argv);
+ g_free (trimmed);
+}
+
+static void
+parse_url (Package *pkg, const char *str, const char *path)
+{
+ if (pkg->url != NULL)
+ {
+ verbose_error ("URL field occurs twice in '%s'\n", path);
+
+ exit (1);
+ }
+
+ pkg->url = trim_and_sub (pkg, str, path);
+}
+
+#ifdef G_OS_WIN32
+static char *orig_prefix = NULL;
+
+static int
+pathnamecmp (const char *a,
+ const char *b)
+{
+ while (*a && *b &&
+ ((G_IS_DIR_SEPARATOR (*a) && G_IS_DIR_SEPARATOR (*b)) ||
+ g_ascii_toupper (*a) == g_ascii_toupper (*b)))
+ {
+ a++;
+ b++;
+ }
+ return g_ascii_toupper (*a) - g_ascii_toupper (*b);
+}
+#endif
+
+static void
+parse_line (Package *pkg, const char *untrimmed, const char *path,
+ gboolean ignore_requires, gboolean ignore_private_libs,
+ gboolean ignore_requires_private)
+{
+ char *str;
+ char *p;
+ char *tag;
+
+ debug_spew (" line>%s\n", untrimmed);
+
+ str = trim_string (untrimmed);
+
+ if (*str == '\0') /* empty line */
+ {
+ g_free(str);
+ return;
+ }
+
+ p = str;
+
+ /* Get first word */
+ while ((*p >= 'A' && *p <= 'Z') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= '0' && *p <= '9') ||
+ *p == '_' || *p == '.')
+ p++;
+
+ tag = g_strndup (str, p - str);
+
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (*p == ':')
+ {
+ /* keyword */
+ ++p;
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (strcmp (tag, "Name") == 0)
+ parse_name (pkg, p, path);
+ else if (strcmp (tag, "Description") == 0)
+ parse_description (pkg, p, path);
+ else if (strcmp (tag, "Version") == 0)
+ parse_version (pkg, p, path);
+ else if (strcmp (tag, "Requires.private") == 0)
+ {
+ if (!ignore_requires_private)
+ parse_requires_private (pkg, p, path);
+ }
+ else if (strcmp (tag, "Requires") == 0)
+ {
+ if (ignore_requires == FALSE)
+ parse_requires (pkg, p, path);
+ else
+ goto cleanup;
+ }
+ else if ((strcmp (tag, "Libs.private") == 0) &&
+ ignore_private_libs == FALSE)
+ parse_libs_private (pkg, p, path);
+ else if (strcmp (tag, "Libs") == 0)
+ parse_libs (pkg, p, path);
+ else if (strcmp (tag, "Cflags") == 0 ||
+ strcmp (tag, "CFlags") == 0)
+ parse_cflags (pkg, p, path);
+ else if (strcmp (tag, "Conflicts") == 0)
+ parse_conflicts (pkg, p, path);
+ else if (strcmp (tag, "URL") == 0)
+ parse_url (pkg, p, path);
+ else
+ {
+ /* we don't error out on unknown keywords because they may
+ * represent additions to the .pc file format from future
+ * versions of pkg-config. We do make a note of them in the
+ * debug spew though, in order to help catch mistakes in .pc
+ * files. */
+ debug_spew ("Unknown keyword '%s' in '%s'\n",
+ tag, path);
+ }
+ }
+ else if (*p == '=')
+ {
+ /* variable */
+ char *varname;
+ char *varval;
+
+ ++p;
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (pkg->vars == NULL)
+ pkg->vars = g_hash_table_new (g_str_hash, g_str_equal);
+
+#ifdef G_OS_WIN32
+ if (!dont_define_prefix && strcmp (tag, prefix_variable) == 0)
+ {
+ /* This is the prefix variable. Try to guesstimate a value for it
+ * for this package from the location of the .pc file.
+ */
+
+ gchar *prefix = pkg->pcfiledir;
+ const int prefix_len = strlen (prefix);
+ const char *const lib_pkgconfig = "\\lib\\pkgconfig";
+ const char *const share_pkgconfig = "\\share\\pkgconfig";
+ const int lib_pkgconfig_len = strlen (lib_pkgconfig);
+ const int share_pkgconfig_len = strlen (share_pkgconfig);
+
+ if ((strlen (prefix) > lib_pkgconfig_len &&
+ pathnamecmp (prefix + prefix_len - lib_pkgconfig_len, lib_pkgconfig) == 0) ||
+ (strlen (prefix) > share_pkgconfig_len &&
+ pathnamecmp (prefix + prefix_len - share_pkgconfig_len, share_pkgconfig) == 0))
+ {
+ /* It ends in lib\pkgconfig or share\pkgconfig. Good. */
+
+ gchar *q;
+
+ orig_prefix = g_strdup (p);
+
+ prefix = g_strdup (prefix);
+ if (strlen (prefix) > lib_pkgconfig_len &&
+ pathnamecmp (prefix + prefix_len - lib_pkgconfig_len, lib_pkgconfig) == 0)
+ prefix[prefix_len - lib_pkgconfig_len] = '\0';
+ else
+ prefix[prefix_len - share_pkgconfig_len] = '\0';
+
+ /* Turn backslashes into slashes or
+ * poptParseArgvString() will eat them when ${prefix}
+ * has been expanded in parse_libs().
+ */
+ q = prefix;
+ while (*q)
+ {
+ if (*q == '\\')
+ *q = '/';
+ q++;
+ }
+ varname = g_strdup (tag);
+ debug_spew (" Variable declaration, '%s' overridden with '%s'\n",
+ tag, prefix);
+ g_hash_table_insert (pkg->vars, varname, prefix);
+ goto cleanup;
+ }
+ }
+ else if (!dont_define_prefix &&
+ orig_prefix != NULL &&
+ strncmp (p, orig_prefix, strlen (orig_prefix)) == 0 &&
+ G_IS_DIR_SEPARATOR (p[strlen (orig_prefix)]))
+ {
+ char *oldstr = str;
+
+ p = str = g_strconcat (g_hash_table_lookup (pkg->vars, prefix_variable), p + strlen (orig_prefix), NULL);
+ g_free (oldstr);
+ }
+#endif
+
+ if (g_hash_table_lookup (pkg->vars, tag))
+ {
+ verbose_error ("Duplicate definition of variable '%s' in '%s'\n",
+ tag, path);
+
+ exit (1);
+ }
+
+ varname = g_strdup (tag);
+ varval = trim_and_sub (pkg, p, path);
+
+ debug_spew (" Variable declaration, '%s' has value '%s'\n",
+ varname, varval);
+ g_hash_table_insert (pkg->vars, varname, varval);
+
+ }
+
+ cleanup:
+ g_free (str);
+ g_free (tag);
+}
+
+Package*
+parse_package_file (const char *path, gboolean ignore_requires,
+ gboolean ignore_private_libs,
+ gboolean ignore_requires_private)
+{
+ FILE *f;
+ Package *pkg;
+ GString *str;
+ gboolean one_line = FALSE;
+
+ f = fopen (path, "r");
+
+ if (f == NULL)
+ {
+ verbose_error ("Failed to open '%s': %s\n",
+ path, strerror (errno));
+
+ return NULL;
+ }
+
+ debug_spew ("Parsing package file '%s'\n", path);
+
+ pkg = g_new0 (Package, 1);
+
+ if (path)
+ {
+ pkg->pcfiledir = g_dirname (path);
+ }
+ else
+ {
+ debug_spew ("No pcfiledir determined for package\n");
+ pkg->pcfiledir = g_strdup ("???????");
+ }
+
+ str = g_string_new ("");
+
+ while (read_one_line (f, str))
+ {
+ one_line = TRUE;
+
+ parse_line (pkg, str->str, path, ignore_requires, ignore_private_libs,
+ ignore_requires_private);
+
+ g_string_truncate (str, 0);
+ }
+
+ if (!one_line)
+ verbose_error ("Package file '%s' appears to be empty\n",
+ path);
+ g_string_free (str, TRUE);
+ fclose(f);
+
+ /* make ->requires_private include a copy of the public requires too */
+ pkg->requires_private = g_slist_concat(g_slist_copy (pkg->requires),
+ pkg->requires_private);
+
+ pkg->requires = g_slist_reverse (pkg->requires);
+
+ pkg->requires_private = g_slist_reverse (pkg->requires_private);
+
+ pkg->I_cflags = g_slist_reverse (pkg->I_cflags);
+ pkg->other_cflags = g_slist_reverse (pkg->other_cflags);
+
+ pkg->l_libs = g_slist_reverse (pkg->l_libs);
+ pkg->L_libs = g_slist_reverse (pkg->L_libs);
+ pkg->other_libs = g_slist_reverse (pkg->other_libs);
+
+ return pkg;
+}
+
+static char *
+backticks (const char *command)
+{
+ FILE *f;
+ char buf[4096];
+ size_t len;
+ int status;
+
+ f = popen (command, "r");
+
+ if (f == NULL)
+ return NULL;
+
+ len = fread (buf, 1, 4090, f);
+
+ if (ferror (f))
+ {
+ pclose (f);
+ return NULL;
+ }
+
+ buf[len] = '\0';
+
+ status = pclose (f);
+
+ return g_strdup (buf);
+}
+
+static gboolean
+try_command (const char *command)
+{
+ int status;
+ char *munged;
+
+#ifdef G_OS_WIN32
+ munged = g_strdup_printf ("%s > NUL", command);
+#else
+ munged = g_strdup_printf ("%s > /dev/null 2>&1", command);
+#endif
+
+ status = system (munged);
+
+ g_free (munged);
+
+#ifdef G_OS_WIN32
+ return status == 0;
+#else
+ return WIFEXITED(status) && (WEXITSTATUS(status) == 0);
+#endif
+}
+
+Package *
+get_compat_package (const char *name)
+{
+#ifdef G_OS_WIN32
+ /* There has never been any of these legacy *-config scripts on
+ * Windows as far as I know. No use trying to execute them, will
+ * only confuse users to see the "blabla is not recognized as an
+ * internal or external command, operable program or batch file"
+ * messages.
+ */
+ return NULL;
+#else
+
+ Package *pkg;
+
+ if (name_ends_in_uninstalled (name))
+ debug_spew ("Suspiciously looking for compat package for -uninstalled: %s\n", name);
+
+ debug_spew ("Looking for '%s' using legacy -config scripts\n", name);
+
+ pkg = g_new0 (Package, 1);
+
+ pkg->path_position = G_MAXINT;
+
+ if (strcmp (name, "glib") == 0)
+ {
+ char *output;
+
+ debug_spew ("Calling glib-config\n");
+
+ pkg->version = backticks ("glib-config --version");
+ if (pkg->version == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ pkg->name = g_strdup ("GLib");
+ pkg->key = g_strdup ("glib");
+ pkg->description = g_strdup ("C Utility Library");
+
+ output = backticks ("glib-config --libs");
+ parse_libs (pkg, output, "glib-config");
+ g_free (output);
+
+ output = backticks ("glib-config --cflags");
+ parse_cflags (pkg, output, "glib-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else if (strcmp (name, "gtk+") == 0)
+ {
+ char *output;
+
+ debug_spew ("Calling gtk-config\n");
+
+ pkg->version = backticks ("gtk-config --version");
+ if (pkg->version == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ pkg->name = g_strdup ("GTK+");
+ pkg->key = g_strdup ("gtk+");
+ pkg->description = g_strdup ("GIMP Tool Kit");
+
+ output = backticks ("gtk-config --libs");
+ parse_libs (pkg, output, "gtk-config");
+ g_free (output);
+
+ output = backticks ("gtk-config --cflags");
+ parse_cflags (pkg, output, "gtk-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else if (strcmp (name, "libgnomevfs") == 0)
+ {
+ char *output;
+
+ debug_spew ("Calling gnome-vfs-config\n");
+
+ pkg->version = backticks ("gnome-vfs-config --version");
+ if (pkg->version == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ pkg->name = g_strdup ("GNOME VFS");
+ pkg->key = g_strdup ("libgnomevfs");
+ pkg->description = g_strdup ("GNOME Virtual File System");
+
+ output = backticks ("gnome-vfs-config --libs");
+ parse_libs (pkg, output, "gnome-vfs-config");
+ g_free (output);
+
+ output = backticks ("gnome-vfs-config --cflags");
+ parse_cflags (pkg, output, "gnome-vfs-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else if (strcmp (name, "imlib") == 0)
+ {
+ char *output;
+
+ debug_spew ("Calling imlib-config\n");
+
+ pkg->version = backticks ("imlib-config --version");
+ if (pkg->version == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ pkg->name = g_strdup ("Imlib");
+ pkg->key = g_strdup ("imlib");
+ pkg->description = g_strdup ("Imlib image loading library");
+
+ output = backticks ("imlib-config --libs-gdk");
+ parse_libs (pkg, output, "imlib-config");
+ g_free (output);
+
+ output = backticks ("imlib-config --cflags-gdk");
+ parse_cflags (pkg, output, "imlib-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else if (strcmp (name, "orbit-client") == 0)
+ {
+ char *output;
+ char *p;
+
+ debug_spew ("Calling orbit-config\n");
+
+ output = backticks ("orbit-config --version");
+
+ if (output == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ p = output;
+
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (*p == '\0')
+ {
+ /* empty output */
+ g_free (output);
+ g_free (pkg);
+ return NULL;
+ }
+
+ /* only heuristic; find a number or . */
+ while (*p && ! (isdigit ((guchar)*p) || *p == '.'))
+ ++p;
+
+ pkg->version = g_strdup (p);
+
+ g_free (output);
+
+ pkg->name = g_strdup ("ORBit Client");
+ pkg->key = g_strdup ("orbit-client");
+ pkg->description = g_strdup ("ORBit Client Libraries");
+
+ output = backticks ("orbit-config --libs client");
+ parse_libs (pkg, output, "orbit-config");
+ g_free (output);
+
+ output = backticks ("orbit-config --cflags client");
+ parse_cflags (pkg, output, "orbit-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else if (strcmp (name, "orbit-server") == 0)
+ {
+ char *output;
+ char *p;
+
+ debug_spew ("Calling orbit-config\n");
+
+ output = backticks ("orbit-config --version");
+
+ if (output == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ p = output;
+
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (*p == '\0')
+ {
+ /* empty output */
+ g_free (output);
+ g_free (pkg);
+ return NULL;
+ }
+
+ /* only heuristic; find a number or . */
+ while (*p && ! (isdigit ((guchar)*p) || *p == '.'))
+ ++p;
+
+ pkg->version = g_strdup (p);
+
+ g_free (output);
+
+ pkg->name = g_strdup ("ORBit Server");
+ pkg->key = g_strdup ("orbit-server");
+ pkg->description = g_strdup ("ORBit Server Libraries");
+
+ output = backticks ("orbit-config --libs server");
+ parse_libs (pkg, output, "orbit-config");
+ g_free (output);
+
+ output = backticks ("orbit-config --cflags server");
+ parse_cflags (pkg, output, "orbit-config");
+ g_free (output);
+
+ return pkg;
+ }
+ else
+ {
+ /* Check for the module in gnome-config */
+ char *output;
+ char *p;
+ char *command;
+
+ debug_spew ("Calling gnome-config\n");
+
+ /* Annoyingly, --modversion doesn't return a failure
+ * code if the lib is unknown, so we have to use --libs
+ * for that.
+ */
+
+ command = g_strdup_printf ("gnome-config --libs %s",
+ name);
+
+ if (!try_command (command))
+ {
+ g_free (command);
+ g_free (pkg);
+ return NULL;
+ }
+ else
+ g_free (command);
+
+ command = g_strdup_printf ("gnome-config --modversion %s",
+ name);
+
+ output = backticks (command);
+ g_free (command);
+ if (output == NULL)
+ {
+ g_free (pkg);
+ return NULL;
+ }
+
+ /* Unknown modules give "Unknown library `foo'" from gnome-config
+ * (but on stderr so this is useless, nevermind)
+ */
+ if (strstr (output, "Unknown") || *output == '\0')
+ {
+ g_free (output);
+ g_free (pkg);
+ return NULL;
+ }
+
+ /* gnome-config --modversion gnomeui outputs e.g. "gnome-libs-1.2.4"
+ * or libglade-0.12
+ */
+ p = output;
+
+ while (*p && isspace ((guchar)*p))
+ ++p;
+
+ if (*p == '\0')
+ {
+ /* empty output */
+ g_free (output);
+ g_free (pkg);
+ return NULL;
+ }
+
+ /* only heuristic; find a number or . */
+ while (*p && ! (isdigit ((guchar)*p) || *p == '.'))
+ ++p;
+
+ pkg->version = g_strdup (p);
+
+ g_free (output);
+
+ /* Strip newline */
+ p = pkg->version;
+ while (*p)
+ {
+ if (*p == '\n')
+ *p = '\0';
+
+ ++p;
+ }
+
+ pkg->name = g_strdup (name);
+ pkg->key = g_strdup (name);
+ pkg->description = g_strdup ("No description");
+
+ command = g_strdup_printf ("gnome-config --libs %s", name);
+ output = backticks (command);
+ g_free (command);
+ parse_libs (pkg, output, "gnome-config");
+ g_free (output);
+
+ command = g_strdup_printf ("gnome-config --cflags %s", name);
+ output = backticks (command);
+ g_free (command);
+ parse_cflags (pkg, output, "gnome-config");
+ g_free (output);
+
+ return pkg;
+ }
+#endif
+}