Initial version of glib-compile-resources
[platform/upstream/glib.git] / gio / glib-compile-resources.c
1 /*
2  * Copyright © 2011 Red Hat, Inc
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Alexander Larsson <alexl@redhat.com>
20  */
21
22 #include "config.h"
23
24 #include <gstdio.h>
25 #include <gi18n.h>
26 #include <gioenums.h>
27
28 #include <string.h>
29 #include <stdio.h>
30 #include <locale.h>
31
32 #include <gio/gmemoryoutputstream.h>
33 #include <gio/gzlibcompressor.h>
34 #include <gio/gconverteroutputstream.h>
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #include <glib.h>
41 #include "gvdb/gvdb-builder.h"
42
43 typedef struct
44 {
45   char *content;
46   gsize content_size;
47   gsize size;
48   guint32 flags;
49 } FileData;
50
51 typedef struct
52 {
53   GHashTable *table; /* resource path -> FileData */
54
55   /* per gresource */
56   char *prefix;
57
58   /* per file */
59   char *alias;
60   gboolean compressed;
61
62   GString *string;  /* non-NULL when accepting text */
63 } ParseState;
64
65 gchar *sourcedir = NULL;
66
67 static void
68 file_data_free (FileData *data)
69 {
70   g_free (data->content);
71   g_free (data);
72 }
73
74 static void
75 start_element (GMarkupParseContext  *context,
76                const gchar          *element_name,
77                const gchar         **attribute_names,
78                const gchar         **attribute_values,
79                gpointer              user_data,
80                GError              **error)
81 {
82   ParseState *state = user_data;
83   const GSList *element_stack;
84   const gchar *container;
85
86   element_stack = g_markup_parse_context_get_element_stack (context);
87   container = element_stack->next ? element_stack->next->data : NULL;
88
89 #define COLLECT(first, ...) \
90   g_markup_collect_attributes (element_name,                                 \
91                                attribute_names, attribute_values, error,     \
92                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
93 #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
94 #define STRDUP     G_MARKUP_COLLECT_STRDUP
95 #define STRING     G_MARKUP_COLLECT_STRING
96 #define BOOL       G_MARKUP_COLLECT_BOOLEAN
97 #define NO_ATTRS()  COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
98
99   if (container == NULL)
100     {
101       if (strcmp (element_name, "gresources") == 0)
102         return;
103     }
104   else if (strcmp (container, "gresources") == 0)
105     {
106       if (strcmp (element_name, "gresource") == 0)
107         {
108           COLLECT (OPTIONAL | STRDUP,
109                    "prefix", &state->prefix);
110           return;
111         }
112     }
113   else if (strcmp (container, "gresource") == 0)
114     {
115       if (strcmp (element_name, "file") == 0)
116         {
117           COLLECT (OPTIONAL | STRDUP, "alias", &state->alias,
118                    OPTIONAL | BOOL, "compressed", &state->compressed);
119           state->string = g_string_new ("");
120           return;
121         }
122     }
123
124   if (container)
125     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
126                  _("Element <%s> not allowed inside <%s>"),
127                  element_name, container);
128   else
129     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
130                  _("Element <%s> not allowed at toplevel"), element_name);
131
132 }
133
134 static GvdbItem *
135 get_parent (GHashTable *table,
136             gchar      *key,
137             gint        length)
138 {
139   GvdbItem *grandparent, *parent;
140
141   if (length == 1)
142     return NULL;
143
144   while (key[--length - 1] != '/');
145   key[length] = '\0';
146
147   parent = g_hash_table_lookup (table, key);
148
149   if (parent == NULL)
150     {
151       parent = gvdb_hash_table_insert (table, key);
152
153       grandparent = get_parent (table, key, length);
154
155       if (grandparent != NULL)
156         gvdb_item_set_parent (parent, grandparent);
157     }
158
159   return parent;
160 }
161
162 static void
163 end_element (GMarkupParseContext  *context,
164              const gchar          *element_name,
165              gpointer              user_data,
166              GError              **error)
167 {
168   ParseState *state = user_data;
169   GError *my_error = NULL;
170
171   if (strcmp (element_name, "gresource") == 0)
172     {
173       g_free (state->prefix);
174       state->prefix = NULL;
175     }
176
177   else if (strcmp (element_name, "file") == 0)
178     {
179       gchar *file, *real_file;
180       gchar *key;
181       FileData *data;
182
183       file = state->string->str;
184       key = file;
185       if (state->alias)
186         key = state->alias;
187
188       if (state->prefix)
189         key = g_build_path ("/", "/", state->prefix, key, NULL);
190       else
191         key = g_build_path ("/", "/", key, NULL);
192
193       if (g_hash_table_lookup (state->table, key) != NULL)
194         {
195           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
196                        _("File %s appears multiple times in the resource"),
197                        key);
198           return;
199         }
200
201       data = g_new0 (FileData, 1);
202
203       if (sourcedir != NULL)
204         real_file = g_build_filename (sourcedir, file, NULL);
205       else
206         real_file = g_strdup (file);
207
208       if (!g_file_get_contents (real_file, &data->content, &data->size, &my_error))
209         {
210           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
211                        _("Error reading file %s: %s"),
212                        real_file, my_error->message);
213           g_clear_error (&my_error);
214           return;
215         }
216       /* Include zero termination in content_size for uncompressed files (but not in size) */
217       data->content_size = data->size + 1;
218
219       if (state->compressed)
220         {
221           GOutputStream *out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
222           GZlibCompressor *compressor =
223             g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 9);
224           GOutputStream *out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor));
225
226           if (!g_output_stream_write_all (out2, data->content, data->size,
227                                           NULL, NULL, NULL) ||
228               !g_output_stream_close (out2, NULL, NULL))
229             {
230               g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
231                            _("Error compressing file %s"),
232                            real_file);
233               return;
234             }
235
236           g_free (data->content);
237           data->content_size = g_memory_output_stream_get_size (G_MEMORY_OUTPUT_STREAM (out));
238           data->content = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out));
239
240           g_object_unref (compressor);
241           g_object_unref (out);
242           g_object_unref (out2);
243
244           data->flags |= G_RESOURCE_FLAGS_COMPRESSED;
245         }
246
247       g_free (real_file);
248
249       g_hash_table_insert (state->table, key, data);
250
251       /* Cleanup */
252
253       g_free (state->alias);
254       state->alias = NULL;
255       g_string_free (state->string, TRUE);
256       state->string = NULL;
257     }
258 }
259
260 static void
261 text (GMarkupParseContext  *context,
262       const gchar          *text,
263       gsize                 text_len,
264       gpointer              user_data,
265       GError              **error)
266 {
267   ParseState *state = user_data;
268   gsize i;
269
270   for (i = 0; i < text_len; i++)
271     if (!g_ascii_isspace (text[i]))
272       {
273         if (state->string)
274           g_string_append_len (state->string, text, text_len);
275
276         else
277           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
278                        _("text may not appear inside <%s>"),
279                        g_markup_parse_context_get_element (context));
280
281         break;
282       }
283 }
284
285 static GHashTable *
286 parse_resource_file (const gchar *filename)
287 {
288   GMarkupParser parser = { start_element, end_element, text };
289   ParseState state = { 0, };
290   GMarkupParseContext *context;
291   GError *error = NULL;
292   gchar *contents;
293   GHashTable *table = NULL;
294   gsize size;
295
296   if (!g_file_get_contents (filename, &contents, &size, &error))
297     {
298       g_printerr ("%s\n", error->message);
299       g_clear_error (&error);
300       return NULL;
301     }
302
303   state.table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)file_data_free);
304
305   context = g_markup_parse_context_new (&parser,
306                                         G_MARKUP_TREAT_CDATA_AS_TEXT |
307                                         G_MARKUP_PREFIX_ERROR_POSITION,
308                                         &state, NULL);
309
310   if (!g_markup_parse_context_parse (context, contents, size, &error) ||
311       !g_markup_parse_context_end_parse (context, &error))
312     {
313       g_printerr ("%s: %s.\n", filename, error->message);
314       g_clear_error (&error);
315     }
316   else
317     {
318       GHashTableIter iter;
319       const char *key;
320       char *mykey;
321       gsize key_len;
322       FileData *data;
323       GVariant *v_data;
324       GVariantBuilder builder;
325       GvdbItem *item;
326
327       table = gvdb_hash_table_new (NULL, NULL);
328
329       g_hash_table_iter_init (&iter, state.table);
330       while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&data))
331         {
332           key_len = strlen (key);
333           mykey = g_strdup (key);
334
335           item = gvdb_hash_table_insert (table, key);
336           gvdb_item_set_parent (item,
337                                 get_parent (table, mykey, key_len));
338
339           g_free (mykey);
340
341           g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuay)"));
342
343           g_variant_builder_add (&builder, "u", data->size); /* Size */
344           g_variant_builder_add (&builder, "u", data->flags); /* Flags */
345
346           v_data = g_variant_new_from_data (G_VARIANT_TYPE("ay"),
347                                             data->content, data->content_size, TRUE,
348                                             g_free, data->content);
349           g_variant_builder_add_value (&builder, v_data);
350           data->content = NULL; /* Take ownership */
351
352           gvdb_item_set_value (item,
353                                g_variant_builder_end (&builder));
354         }
355     }
356
357   g_hash_table_destroy (state.table);
358   g_markup_parse_context_free (context);
359   g_free (contents);
360
361   return table;
362 }
363
364 static gboolean
365 write_to_file (GHashTable   *table,
366                const gchar  *filename,
367                GError      **error)
368 {
369   gboolean success;
370
371   success = gvdb_table_write_contents (table, filename,
372                                        G_BYTE_ORDER != G_LITTLE_ENDIAN,
373                                        error);
374
375   return success;
376 }
377
378 int
379 main (int argc, char **argv)
380 {
381   GError *error;
382   GHashTable *table;
383   gchar *srcfile;
384   gchar *target = NULL;
385   gchar *binary_target;
386   gboolean generate_source = FALSE;
387   gboolean generate_header = FALSE;
388   gboolean manual_register = FALSE;
389   char *c_name = NULL;
390   GOptionContext *context;
391   GOptionEntry entries[] = {
392     { "target", 0, 0, G_OPTION_ARG_FILENAME, &target, N_("name of the output file"), N_("FILE") },
393     { "sourcedir", 0, 0, G_OPTION_ARG_FILENAME, &sourcedir, N_("The directory where files are to be read from (default to current directory)"), N_("DIRECTORY") },
394     { "generate-header", 0, 0, G_OPTION_ARG_NONE, &generate_header, N_("Generate source header"), NULL },
395     { "generate-source", 0, 0, G_OPTION_ARG_NONE, &generate_source, N_("Generate sourcecode used to link in the resource file into your code"), NULL },
396     { "manual-register", 0, 0, G_OPTION_ARG_NONE, &manual_register, N_("Don't automatically create and register resource"), NULL },
397     { "c-name", 0, 0, G_OPTION_ARG_STRING, &c_name, N_("C identifier name used for the generated source code"), NULL },
398     { NULL }
399   };
400
401 #ifdef G_OS_WIN32
402   extern gchar *_glib_get_locale_dir (void);
403   gchar *tmp;
404 #endif
405
406   setlocale (LC_ALL, "");
407   textdomain (GETTEXT_PACKAGE);
408
409 #ifdef G_OS_WIN32
410   tmp = _glib_get_locale_dir ();
411   bindtextdomain (GETTEXT_PACKAGE, tmp);
412   g_free (tmp);
413 #else
414   bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
415 #endif
416
417 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
418   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
419 #endif
420
421   g_type_init ();
422
423   context = g_option_context_new (N_("FILE"));
424   g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
425   g_option_context_set_summary (context,
426     N_("Compile a resource specification into a resource file.\n"
427        "Resource specification files have the extension .gresource.xml,\n"
428        "and the resource file have the extension called .gresource."));
429   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
430
431   error = NULL;
432   if (!g_option_context_parse (context, &argc, &argv, &error))
433     {
434       g_printerr ("%s\n", error->message);
435       return 1;
436     }
437
438   g_option_context_free (context);
439
440   if (argc != 2)
441     {
442       g_printerr (_("You should give exactly one file name\n"));
443       return 1;
444     }
445
446   srcfile = argv[1];
447
448   if (sourcedir == NULL)
449     sourcedir = "";
450
451   if (target == NULL)
452     {
453       char *dirname = g_path_get_dirname (srcfile);
454       char *base = g_path_get_basename (srcfile);
455       char *target_basename;
456       if (g_str_has_suffix (base, ".xml"))
457         base[strlen(base) - strlen (".xml")] = 0;
458
459       if (generate_source)
460         {
461           if (g_str_has_suffix (base, ".gresource"))
462             base[strlen(base) - strlen (".gresource")] = 0;
463           target_basename = g_strconcat (base, ".c", NULL);
464         }
465       else
466         {
467           if (g_str_has_suffix (base, ".gresource"))
468             target_basename = g_strdup (base);
469           else
470             target_basename = g_strconcat (base, ".gresource", NULL);
471         }
472
473       target = g_build_filename (dirname, target_basename, NULL);
474       g_free (target_basename);
475       g_free (dirname);
476       g_free (base);
477     }
478
479   if ((table = parse_resource_file (srcfile)) == NULL)
480     {
481       g_free (target);
482       return 1;
483     }
484
485   if (generate_source || generate_header)
486     {
487       if (generate_source)
488         {
489           int fd = g_file_open_tmp (NULL, &binary_target, NULL);
490           if (fd == -1)
491             {
492               g_printerr ("Can't open temp file\n");
493               return 1;
494             }
495           close (fd);
496         }
497       else
498         binary_target = NULL;
499
500       if (c_name == NULL)
501         {
502           char *base = g_path_get_basename (srcfile);
503           GString *s;
504           char *dot;
505           int i;
506
507           /* Remove extensions */
508           dot = strchr (base, '.');
509           if (dot)
510             *dot = 0;
511
512           s = g_string_new ("");
513
514           for (i = 0; base[i] != 0; i++)
515             {
516               const char *first = G_CSET_A_2_Z G_CSET_a_2_z "_";
517               const char *rest = G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_";
518               if (strchr ((i == 0) ? first : rest, base[i]) != NULL)
519                 g_string_append_c (s, base[i]);
520               else if (base[i] == '-')
521                 g_string_append_c (s, '_');
522
523             }
524
525           c_name = g_string_free (s, FALSE);
526         }
527     }
528   else
529     binary_target = g_strdup (target);
530
531   if (binary_target != NULL &&
532       !write_to_file (table, binary_target, &error))
533     {
534       g_printerr ("%s\n", error->message);
535       g_free (target);
536       return 1;
537     }
538
539   if (generate_header)
540     {
541       FILE *file;
542
543       file = fopen (target, "w");
544       if (file == NULL)
545         {
546           g_printerr ("can't write to file %s", target);
547           return 1;
548         }
549
550       fprintf (file,
551                "#ifndef __RESOURCE_%s_H__\n"
552                "#define __RESOURCE_%s_H__\n"
553                "\n"
554                "#include <gio/gio.h>\n"
555                "\n"
556                "extern GResource *%s_resource;\n",
557                c_name, c_name, c_name);
558
559       if (manual_register)
560         fprintf (file,
561                  "\n"
562                  "extern void %s_register_resource (void);\n"
563                  "extern void %s_unregister_resource (void);\n"
564                  "\n",
565                  c_name, c_name);
566
567       fprintf (file,
568                "#endif\n");
569
570       fclose (file);
571     }
572   else if (generate_source)
573     {
574       FILE *file;
575       guint8 *data;
576       gsize data_size;
577       gsize i;
578       char *static_str;
579
580       if (!g_file_get_contents (binary_target, (char **)&data,
581                                 &data_size, NULL))
582         {
583           g_printerr ("can't read back temporary file");
584           return 1;
585         }
586       g_unlink (binary_target);
587
588       file = fopen (target, "w");
589       if (file == NULL)
590         {
591           g_printerr ("can't write to file %s", target);
592           return 1;
593         }
594
595       fprintf (file,
596                "#include <gio/gio.h>\n"
597                "\n"
598                "#if defined (__ELF__) && ( __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))\n"
599                "# define SECTION __attribute__ ((section (\".gresource.%s\"), aligned (8)))\n"
600                "#else\n"
601                "# define SECTION\n"
602                "#endif\n"
603                "\n"
604                "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;}  %s_resource_data = { {\n",
605                c_name, data_size, c_name);
606
607       for (i = 0; i < data_size; i++) {
608         if (i % 8 == 0)
609           fprintf (file, "  ");
610         fprintf (file, "0x%2.2x", (int)data[i]);
611         if (i != data_size - 1)
612           fprintf (file, ", ");
613         if ((i % 8 == 7) || (i == data_size - 1))
614           fprintf (file, "\n");
615       }
616
617       fprintf (file, "} };\n");
618
619       if (manual_register)
620         {
621           static_str = "";
622         }
623       else
624         {
625           static_str = "static ";
626           fprintf (file,
627                    "\n"
628                    "#ifdef G_HAS_CONSTRUCTORS\n"
629                    "\n"
630                    "#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA\n"
631                    "#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(resource_constructor)\n"
632                    "#endif\n"
633                    "G_DEFINE_CONSTRUCTOR(resource_constructor)\n"
634                    "#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA\n"
635                    "#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(resource_destructor)\n"
636                    "#endif\n"
637                    "G_DEFINE_DESTRUCTOR(resource_destructor)\n"
638                    "\n"
639                    "#else\n"
640                    "#warning \"Constructor not supported on this compiler, linking in resources will not work\"\n"
641                    "#endif\n"
642                    "\n");
643         }
644
645       fprintf (file,
646                "\n"
647                "GResource *%s_resource = NULL;\n"
648                "\n"
649                "%svoid %s_unregister_resource (void)\n"
650                "{\n"
651                "  if (%s_resource)\n"
652                "    {\n"
653                "      g_resources_unregister (%s_resource);\n"
654                "      g_resource_unref (%s_resource);\n"
655                "      %s_resource = NULL;\n"
656                "    }\n"
657                "}\n"
658                "\n"
659                "%svoid %s_register_resource (void)\n"
660                "{\n"
661                "  if (%s_resource == NULL)\n"
662                "    {\n"
663                "      GBytes *bytes = g_bytes_new_static (%s_resource_data.data, sizeof (%s_resource_data.data));\n"
664                "      %s_resource = g_resource_new_from_data (bytes, NULL);\n"
665                "      if (%s_resource)\n"
666                "        g_resources_register (%s_resource);\n"
667                "       g_bytes_unref (bytes);\n"
668                "    }\n"
669                "}\n", c_name, static_str, c_name, c_name, c_name, c_name, c_name, static_str, c_name, c_name, c_name, c_name, c_name, c_name, c_name);
670
671       if (!manual_register)
672         fprintf (file,
673                  "\n"
674                  "static void resource_constructor (void)\n"
675                  "{\n"
676                  "  %s_register_resource ();\n"
677                  "}\n"
678                  "\n"
679                  "static void resource_destructor (void)\n"
680                  "{\n"
681                  "  %s_unregister_resource ();\n"
682                  "}\n", c_name, c_name);
683
684       fclose (file);
685     }
686
687   g_free (binary_target);
688
689
690   g_free (target);
691
692   return 0;
693 }