tags: add some comments for translators so tag mnemonics get translated correctly
[platform/upstream/gstreamer.git] / tools / gst-run.c
1 /* GStreamer
2  * Copyright (C) 2004 Thomas Vander Stichele <thomas@apestaart.org>
3  *
4  * gst-run.c: tool to launch GStreamer tools with correct major/minor
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <errno.h>
32 #include <glib.h>
33
34 /* global statics for option parsing */
35 static gboolean _print = FALSE;
36 static gchar *_arg_mm = NULL;
37 static gboolean _arg_list_mm = FALSE;
38
39 /* popt options table for the wrapper */
40 static GOptionEntry wrapper_options[] = {
41   {"print", 'p', 0, G_OPTION_ARG_NONE, &_print,
42       "print wrapped command line options", NULL},
43   {"gst-mm", 0, 0, G_OPTION_ARG_STRING, &_arg_mm,
44       "Force major/minor version", "VERSION"},
45   {"gst-list-mm", 0, 0, G_OPTION_ARG_NONE, &_arg_list_mm,
46       "List found major/minor versions", NULL},
47   {NULL}
48 };
49
50 /* print out the major/minor, which is the hash key */
51 static void
52 hash_print_key (gchar * key, gchar * value)
53 {
54   g_print ("%s\n", (gchar *) key);
55 }
56
57 /* return value like strcmp, but compares major/minor numerically */
58 static gint
59 compare_major_minor (const gchar * first, const gchar * second)
60 {
61   gchar **firsts, **seconds;
62   gint fmaj, fmin, smaj, smin;
63   gint ret = 0;
64
65   firsts = g_strsplit (first, ".", 0);
66   seconds = g_strsplit (second, ".", 0);
67
68   if (firsts[0] == NULL || firsts[1] == NULL) {
69     ret = -1;
70     goto beach;
71   }
72   if (seconds[0] == NULL || seconds[1] == NULL) {
73     ret = 1;
74     goto beach;
75   }
76
77   fmaj = atoi (firsts[0]);
78   fmin = atoi (firsts[1]);
79   smaj = atoi (seconds[0]);
80   smin = atoi (seconds[1]);
81
82   if (fmaj < smaj) {
83     ret = -1;
84     goto beach;
85   }
86   if (fmaj > smaj) {
87     ret = 1;
88     goto beach;
89   }
90
91   /* fmaj == smaj */
92   if (fmin < smin) {
93     ret = -1;
94     goto beach;
95   }
96   if (fmin > smin) {
97     ret = 1;
98     goto beach;
99   }
100   ret = 0;
101
102 beach:
103   g_strfreev (firsts);
104   g_strfreev (seconds);
105   return ret;
106 }
107
108 static void
109 find_highest_version (gchar * key, gchar * value, gchar ** highest)
110 {
111   if (*highest == NULL) {
112     /* first value, so just set it */
113     *highest = key;
114   }
115   if (compare_major_minor (key, *highest) > 0)
116     *highest = key;
117 }
118
119 /* Libtool creates shell scripts named "base" that calls actual binaries as
120  * .libs/lt-base.  If we detect this is a libtool script, unmangle so we
121  * find the right binaries */
122 static void
123 unmangle_libtool (gchar ** dir, gchar ** base)
124 {
125   gchar *new_dir, *new_base;
126
127   if (!*dir)
128     return;
129   if (!*base)
130     return;
131
132   /* we assume libtool when base starts with lt- and dir ends with .libs */
133   if (!g_str_has_prefix (*base, "lt-"))
134     return;
135   if (!g_str_has_suffix (*dir, ".libs"))
136     return;
137
138   new_base = g_strdup (&((*base)[3]));
139   new_dir = g_path_get_dirname (*dir);
140   g_free (*base);
141   g_free (*dir);
142   *base = new_base;
143   *dir = new_dir;
144 }
145
146 /* Returns a directory path that contains the binary given as an argument.
147  * If the binary given contains a path, it gets looked for in that path.
148  * If it doesn't contain a path, it gets looked for in the standard path.
149  *
150  * The returned string is newly allocated.
151  */
152 static gchar *
153 get_dir_of_binary (const gchar * binary)
154 {
155   gchar *base, *dir;
156   gchar *full;
157
158   base = g_path_get_basename (binary);
159   dir = g_path_get_dirname (binary);
160
161   /* if putting these two together yields the same as binary,
162    * then we have the right breakup.  If not, it's because no path was
163    * specified which caused get_basename to return "." */
164   full = g_build_filename (dir, base, NULL);
165
166   if (strcmp (full, binary) != 0) {
167     if (strcmp (dir, ".") != 0) {
168       g_warning ("This should not happen, g_path_get_dirname () has changed.");
169       g_free (base);
170       g_free (dir);
171       g_free (full);
172       return NULL;
173     }
174
175     /* we know no path was specified, so search standard path for binary */
176     g_free (full);
177     full = g_find_program_in_path (base);
178     if (!full) {
179       g_warning ("This should not happen, %s not in standard path.", base);
180       g_free (base);
181       g_free (dir);
182       return NULL;
183     }
184   }
185
186   g_free (base);
187   g_free (dir);
188   dir = g_path_get_dirname (full);
189   g_free (full);
190
191   return dir;
192 }
193
194 /* Search the given directory for candidate binaries matching the base binary.
195  * Return a GHashTable of major/minor -> directory pairs
196  */
197 static GHashTable *
198 get_candidates (const gchar * dir, const gchar * base)
199 {
200   GDir *gdir;
201   GError *error = NULL;
202   const gchar *entry;
203   gchar *path;
204   gchar *suffix, *copy;
205
206   gchar *pattern;
207   GPatternSpec *spec, *specexe;
208
209   gchar **dirs;
210   gchar **cur;
211
212   GHashTable *candidates = NULL;
213
214   candidates = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
215
216   /* compile our pattern specs */
217   pattern = g_strdup_printf ("%s-*.*", base);
218   spec = g_pattern_spec_new (pattern);
219   g_free (pattern);
220   pattern = g_strdup_printf ("%s-*.*.exe", base);
221   specexe = g_pattern_spec_new (pattern);
222   g_free (pattern);
223
224   /* get all dirs from the path and prepend with given dir */
225   if (dir)
226     path = g_strdup_printf ("%s%c%s",
227         dir, G_SEARCHPATH_SEPARATOR, g_getenv ("PATH"));
228   else
229     path = (gchar *) g_getenv ("PATH");
230   dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0);
231   if (dir)
232     g_free (path);
233
234   /* check all of these in reverse order by winding to bottom and going up  */
235   cur = &dirs[0];
236   while (*cur)
237     ++cur;
238
239   while (cur != &dirs[0]) {
240     --cur;
241     if (!g_file_test (*cur, G_FILE_TEST_EXISTS) ||
242         !g_file_test (*cur, G_FILE_TEST_IS_DIR)) {
243       continue;
244     }
245
246     gdir = g_dir_open (*cur, 0, &error);
247     if (!gdir) {
248       g_warning ("Could not open dir %s: %s", *cur, error->message);
249       g_error_free (error);
250       return NULL;
251     }
252     while ((entry = g_dir_read_name (gdir))) {
253       if (g_pattern_match_string (spec, entry)
254           || g_pattern_match_string (specexe, entry)) {
255         gchar *full;
256
257         /* is it executable ? */
258         full = g_build_filename (*cur, entry, NULL);
259         if (!g_file_test (full, G_FILE_TEST_IS_EXECUTABLE)) {
260           g_free (full);
261           continue;
262         }
263         g_free (full);
264
265         /* strip base and dash from it */
266         suffix = g_strdup (&(entry[strlen (base) + 1]));
267         copy = g_strdup (suffix);
268
269         /* strip possible .exe from copy */
270         if (g_strrstr (copy, ".exe"))
271           g_strrstr (copy, ".exe")[0] = '\0';
272
273         /* stricter pattern check: check if it only contains digits or dots */
274         g_strcanon (copy, "0123456789.", 'X');
275         if (strstr (copy, "X")) {
276           g_free (suffix);
277           g_free (copy);
278           continue;
279         }
280         g_free (copy);
281         g_hash_table_insert (candidates, suffix, g_strdup (*cur));
282       }
283     }
284   }
285
286   g_strfreev (dirs);
287   g_pattern_spec_free (spec);
288   return candidates;
289 }
290
291 int
292 main (int argc, char **argv)
293 {
294   GHashTable *candidates;
295   gchar *dir;
296   gchar *base;
297   gchar *highest = NULL;
298   gchar *binary;                /* actual binary we're going to run */
299   gchar *path = NULL;           /* and its path */
300   gchar *desc;
301   GOptionContext *ctx;
302   GError *err = NULL;
303
304   /* detect stuff */
305   dir = get_dir_of_binary (argv[0]);
306   base = g_path_get_basename (argv[0]);
307
308   /* parse command line options */
309   desc = g_strdup_printf ("wrapper to call versioned %s", base);
310   ctx = g_option_context_new (desc);
311   g_free (desc);
312   g_option_context_set_ignore_unknown_options (ctx, TRUE);
313   g_option_context_add_main_entries (ctx, wrapper_options, GETTEXT_PACKAGE);
314   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
315     g_print ("Error initializing: %s\n", err->message);
316     exit (1);
317   }
318   g_option_context_free (ctx);
319
320   /* unmangle libtool if necessary */
321   unmangle_libtool (&dir, &base);
322
323   /* get all candidate binaries */
324   candidates = get_candidates (dir, base);
325   g_free (dir);
326
327   if (_arg_mm) {
328     /* if a version was forced, look it up in the hash table */
329     dir = g_hash_table_lookup (candidates, _arg_mm);
330     if (!dir) {
331       g_print ("ERROR: Major/minor %s of tool %s not found.\n", _arg_mm, base);
332       return 1;
333     }
334     binary = g_strdup_printf ("%s-%s", base, _arg_mm);
335   } else {
336     highest = NULL;
337
338     /* otherwise, just look up the highest version */
339     if (candidates) {
340       g_hash_table_foreach (candidates, (GHFunc) find_highest_version,
341           &highest);
342     }
343
344     if (highest == NULL) {
345       g_print ("ERROR: No version of tool %s found.\n", base);
346       return 1;
347     }
348     dir = g_hash_table_lookup (candidates, highest);
349     binary = g_strdup_printf ("%s-%s", base, highest);
350   }
351
352   g_free (base);
353
354   path = g_build_filename (dir, binary, NULL);
355   g_free (binary);
356
357   /* print out list of major/minors we found if asked for */
358   /* FIXME: do them in order by creating a GList of keys and sort them */
359   if (_arg_list_mm) {
360     g_hash_table_foreach (candidates, (GHFunc) hash_print_key, NULL);
361     g_hash_table_destroy (candidates);
362     return 0;
363   }
364
365   /* print out command line if asked for */
366   argv[0] = path;
367   if (_print) {
368     int i;
369
370     for (i = 0; i < argc; ++i) {
371       g_print ("%s", argv[i]);
372       if (i < argc - 1)
373         g_print (" ");
374     }
375     g_print ("\n");
376   }
377
378   /* execute */
379   if (execv (path, argv) == -1) {
380     g_warning ("Error executing %s: %s (%d)", path, g_strerror (errno), errno);
381   }
382   g_free (path);
383
384   return 0;
385 }