2 * Copyright (C) 2004 Thomas Vander Stichele <thomas@apestaart.org>
4 * gst-run.c: tool to launch GStreamer tools with correct major/minor
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
41 /* global statics for option parsing */
42 static gboolean _print = FALSE;
43 static gchar * _arg_mm = NULL;
44 static gboolean _arg_list_mm = FALSE;
46 /* callback to parse arguments */
48 popt_callback (poptContext context, enum poptCallbackReason reason,
49 const struct poptOption *option, const char *arg, void *data)
51 if (reason == POPT_CALLBACK_REASON_OPTION)
56 _arg_mm = g_strdup (arg);
65 poptPrintHelp (context, stdout, 0);
72 g_print ("Unknown reason for callback\n");
76 /* popt options table for the wrapper */
77 static struct poptOption wrapper_options[] =
81 (void *) &popt_callback, 0, NULL, NULL },
83 POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN,
84 NULL, ARG_HELP, ("Show help"), NULL },
86 POPT_ARG_NONE | POPT_ARGFLAG_STRIP | POPT_ARGFLAG_ONEDASH
87 | POPT_ARGFLAG_DOC_HIDDEN,
88 NULL, ARG_HELP, NULL, NULL },
89 /* We cheat by specifying -p as long "p" with onedash, so that it
90 also gets stripped properly from our arg flags */
92 POPT_ARG_NONE | POPT_ARGFLAG_STRIP | POPT_ARGFLAG_ONEDASH
93 | POPT_ARGFLAG_DOC_HIDDEN,
94 NULL, ARG_PRINT, NULL, NULL },
96 POPT_ARG_NONE | POPT_ARGFLAG_STRIP,
97 NULL, ARG_PRINT, ("Print wrapped command line"), NULL },
99 POPT_ARG_STRING | POPT_ARGFLAG_STRIP,
100 NULL, ARG_MM, ("Force major/minor version"), NULL },
101 { "gst-list-mm", '\0',
102 POPT_ARG_NONE | POPT_ARGFLAG_STRIP,
103 NULL, ARG_LIST_MM, ("List found major/minor versions"), NULL },
107 /* helper table including our wrapper options */
108 static struct poptOption options[] =
110 { NULL, '\0', POPT_ARG_INCLUDE_TABLE, wrapper_options, 0,
111 "Wrapper options:", NULL},
116 /* print out the major/minor, which is the hash key */
118 hash_print_key (gchar * key, gchar * value)
120 g_print ("%s\n", (gchar *) key);
124 find_highest_version (gchar * key, gchar * value, gchar ** highest)
126 if (*highest == NULL)
128 /* first value, so just set it */
131 if (strcmp (key, *highest) > 0) *highest = key;
134 /* Libtool creates shell scripts named "base" that calls actual binaries as
135 * .libs/lt-base. If we detect this is a libtool script, unmangle so we
136 * find the right binaries */
138 unmangle_libtool (gchar ** dir, gchar ** base)
140 gchar *new_dir, *new_base;
145 /* we assume libtool when base starts with lt- and dir ends with .libs */
146 if (!g_str_has_prefix (*base, "lt-")) return;
147 if (!g_str_has_suffix (*dir, ".libs")) return;
149 new_base = g_strdup (&((*base)[3]));
150 new_dir = g_path_get_dirname (*dir);
157 /* Returns a directory path that contains the binary given as an argument.
158 * If the binary given contains a path, it gets looked for in that path.
159 * If it doesn't contain a path, it gets looked for in the standard path.
161 * The returned string is newly allocated.
164 get_dir_of_binary (const gchar * binary)
169 base = g_path_get_basename (binary);
170 dir = g_path_get_dirname (binary);
172 /* if putting these two together yields the same as binary,
173 * then we have the right breakup. If not, it's because no path was
174 * specified which caused get_basename to return "." */
175 full = g_build_filename (dir, base, NULL);
177 if (strcmp (full, binary) != 0)
179 if (strcmp (dir, ".") != 0)
181 g_warning ("This should not happen, g_path_get_dirname () has changed.");
188 /* we know no path was specified, so search standard path for binary */
190 full = g_find_program_in_path (base);
193 g_warning ("This should not happen, %s not in standard path.", base);
202 dir = g_path_get_dirname (full);
208 /* Search the given directory for candidate binaries matching the base binary.
209 * Return a GHashTable of major/minor -> directory pairs
212 get_candidates (const gchar * dir, const gchar * base)
215 GError *error = NULL;
229 GHashTable *candidates = NULL;
231 candidates = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
233 /* compile our pattern spec */
234 pattern = g_strdup_printf ("%s-*.*", base);
235 spec = g_pattern_spec_new (pattern);
238 /* get all dirs from the path and prepend with given dir */
239 path = g_strdup_printf ("%s%c%s",
240 dir, G_SEARCHPATH_SEPARATOR, g_getenv ("PATH"));
241 dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0);
244 /* check all of these in reverse order by winding to bottom and going up */
248 while (cur != &dirs[0])
251 if (! g_file_test (*cur, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
254 gdir = g_dir_open (*cur, 0, &error);
257 g_warning ("Could not open dir %s: %s", *cur, error->message);
258 g_error_free (error);
261 while ((entry = g_dir_read_name (gdir)))
263 if (g_pattern_match_string (spec, entry))
267 /* is it executable ? */
268 full = g_build_filename (*cur, entry, NULL);
269 if (! g_file_test (full, G_FILE_TEST_IS_EXECUTABLE))
276 /* strip base and dash from it */
277 suffix = g_strdup (&(entry[strlen (base) + 1]));
279 /* stricter pattern check: check if it only contains digits or dots */
280 test = g_strdup (suffix);
281 g_strcanon (test, "0123456789.", 'X');
282 if (strstr (test, "X"))
288 g_hash_table_insert (candidates, suffix, g_strdup (*cur));
294 g_pattern_spec_free (spec);
299 (int argc, char **argv)
301 GHashTable *candidates;
304 gchar *highest = NULL;
305 gchar *binary; /* actual binary we're going to run */
306 gchar *path = NULL; /* and its path */
310 /* parse command line options */
311 ctx = poptGetContext ("gst-run", argc, (const char **) argv, options, 0);
312 poptReadDefaultConfig (ctx, TRUE);
313 while ((nextopt = poptGetNextOpt (ctx)) > 0)
314 /* keep looping to parse */;
316 argc = poptStrippedArgv (ctx, argc, argv);
318 poptFreeContext (ctx);
321 dir = get_dir_of_binary (argv[0]);
322 base = g_path_get_basename (argv[0]);
324 /* unmangle libtool if necessary */
325 unmangle_libtool (&dir, &base);
327 /* get all candidate binaries */
328 candidates = get_candidates (dir, base);
333 /* if a version was forced, look it up in the hash table */
334 dir = g_hash_table_lookup (candidates, _arg_mm);
337 g_print ("ERROR: Major/minor %s of tool %s not found.\n", _arg_mm, base);
340 binary = g_strdup_printf ("%s-%s", base, _arg_mm);
344 /* otherwise, just look up the highest version */
345 g_hash_table_foreach (candidates, (GHFunc) find_highest_version,
347 dir = g_hash_table_lookup (candidates, highest);
350 g_print ("ERROR: No version of tool %s not found.\n", base);
353 binary = g_strdup_printf ("%s-%s", base, highest);
358 path = g_build_filename (dir, binary, NULL);
361 /* print out list of major/minors we found if asked for */
362 /* FIXME: do them in order by creating a GList of keys and sort them */
365 g_hash_table_foreach (candidates, (GHFunc) hash_print_key, NULL);
366 g_hash_table_destroy (candidates);
370 /* print out command line if asked for */
375 for (i = 0; i < argc; ++i)
377 g_print ("%s", argv[i]);
378 if (i < argc - 1) g_print (" ");
384 if (execv (path, argv) == -1)
386 g_warning ("Error executing %s: %s (%d)", path, g_strerror (errno), errno);