resources: Skip initial underscores in resource section names
[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   char *c_name_no_underscores;
391   GOptionContext *context;
392   GOptionEntry entries[] = {
393     { "target", 0, 0, G_OPTION_ARG_FILENAME, &target, N_("name of the output file"), N_("FILE") },
394     { "sourcedir", 0, 0, G_OPTION_ARG_FILENAME, &sourcedir, N_("The directory where files are to be read from (default to current directory)"), N_("DIRECTORY") },
395     { "generate-header", 0, 0, G_OPTION_ARG_NONE, &generate_header, N_("Generate source header"), NULL },
396     { "generate-source", 0, 0, G_OPTION_ARG_NONE, &generate_source, N_("Generate sourcecode used to link in the resource file into your code"), NULL },
397     { "manual-register", 0, 0, G_OPTION_ARG_NONE, &manual_register, N_("Don't automatically create and register resource"), NULL },
398     { "c-name", 0, 0, G_OPTION_ARG_STRING, &c_name, N_("C identifier name used for the generated source code"), NULL },
399     { NULL }
400   };
401
402 #ifdef G_OS_WIN32
403   extern gchar *_glib_get_locale_dir (void);
404   gchar *tmp;
405 #endif
406
407   setlocale (LC_ALL, "");
408   textdomain (GETTEXT_PACKAGE);
409
410 #ifdef G_OS_WIN32
411   tmp = _glib_get_locale_dir ();
412   bindtextdomain (GETTEXT_PACKAGE, tmp);
413   g_free (tmp);
414 #else
415   bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
416 #endif
417
418 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
419   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
420 #endif
421
422   g_type_init ();
423
424   context = g_option_context_new (N_("FILE"));
425   g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
426   g_option_context_set_summary (context,
427     N_("Compile a resource specification into a resource file.\n"
428        "Resource specification files have the extension .gresource.xml,\n"
429        "and the resource file have the extension called .gresource."));
430   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
431
432   error = NULL;
433   if (!g_option_context_parse (context, &argc, &argv, &error))
434     {
435       g_printerr ("%s\n", error->message);
436       return 1;
437     }
438
439   g_option_context_free (context);
440
441   if (argc != 2)
442     {
443       g_printerr (_("You should give exactly one file name\n"));
444       return 1;
445     }
446
447   srcfile = argv[1];
448
449   if (sourcedir == NULL)
450     sourcedir = "";
451
452   if (target == NULL)
453     {
454       char *dirname = g_path_get_dirname (srcfile);
455       char *base = g_path_get_basename (srcfile);
456       char *target_basename;
457       if (g_str_has_suffix (base, ".xml"))
458         base[strlen(base) - strlen (".xml")] = 0;
459
460       if (generate_source)
461         {
462           if (g_str_has_suffix (base, ".gresource"))
463             base[strlen(base) - strlen (".gresource")] = 0;
464           target_basename = g_strconcat (base, ".c", NULL);
465         }
466       else
467         {
468           if (g_str_has_suffix (base, ".gresource"))
469             target_basename = g_strdup (base);
470           else
471             target_basename = g_strconcat (base, ".gresource", NULL);
472         }
473
474       target = g_build_filename (dirname, target_basename, NULL);
475       g_free (target_basename);
476       g_free (dirname);
477       g_free (base);
478     }
479
480   if ((table = parse_resource_file (srcfile)) == NULL)
481     {
482       g_free (target);
483       return 1;
484     }
485
486   if (generate_source || generate_header)
487     {
488       if (generate_source)
489         {
490           int fd = g_file_open_tmp (NULL, &binary_target, NULL);
491           if (fd == -1)
492             {
493               g_printerr ("Can't open temp file\n");
494               return 1;
495             }
496           close (fd);
497         }
498       else
499         binary_target = NULL;
500
501       if (c_name == NULL)
502         {
503           char *base = g_path_get_basename (srcfile);
504           GString *s;
505           char *dot;
506           int i;
507
508           /* Remove extensions */
509           dot = strchr (base, '.');
510           if (dot)
511             *dot = 0;
512
513           s = g_string_new ("");
514
515           for (i = 0; base[i] != 0; i++)
516             {
517               const char *first = G_CSET_A_2_Z G_CSET_a_2_z "_";
518               const char *rest = G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_";
519               if (strchr ((i == 0) ? first : rest, base[i]) != NULL)
520                 g_string_append_c (s, base[i]);
521               else if (base[i] == '-')
522                 g_string_append_c (s, '_');
523
524             }
525
526           c_name = g_string_free (s, FALSE);
527         }
528     }
529   else
530     binary_target = g_strdup (target);
531
532   c_name_no_underscores = c_name;
533   while (c_name_no_underscores && *c_name_no_underscores == '_')
534     c_name_no_underscores++;
535
536   if (binary_target != NULL &&
537       !write_to_file (table, binary_target, &error))
538     {
539       g_printerr ("%s\n", error->message);
540       g_free (target);
541       return 1;
542     }
543
544   if (generate_header)
545     {
546       FILE *file;
547
548       file = fopen (target, "w");
549       if (file == NULL)
550         {
551           g_printerr ("can't write to file %s", target);
552           return 1;
553         }
554
555       fprintf (file,
556                "#ifndef __RESOURCE_%s_H__\n"
557                "#define __RESOURCE_%s_H__\n"
558                "\n"
559                "#include <gio/gio.h>\n"
560                "\n"
561                "extern GResource *%s_resource;\n",
562                c_name, c_name, c_name);
563
564       if (manual_register)
565         fprintf (file,
566                  "\n"
567                  "extern void %s_register_resource (void);\n"
568                  "extern void %s_unregister_resource (void);\n"
569                  "\n",
570                  c_name, c_name);
571
572       fprintf (file,
573                "#endif\n");
574
575       fclose (file);
576     }
577   else if (generate_source)
578     {
579       FILE *file;
580       guint8 *data;
581       gsize data_size;
582       gsize i;
583       char *static_str;
584
585       if (!g_file_get_contents (binary_target, (char **)&data,
586                                 &data_size, NULL))
587         {
588           g_printerr ("can't read back temporary file");
589           return 1;
590         }
591       g_unlink (binary_target);
592
593       file = fopen (target, "w");
594       if (file == NULL)
595         {
596           g_printerr ("can't write to file %s", target);
597           return 1;
598         }
599
600       fprintf (file,
601                "#include <gio/gio.h>\n"
602                "\n"
603                "#if defined (__ELF__) && ( __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))\n"
604                "# define SECTION __attribute__ ((section (\".gresource.%s\"), aligned (8)))\n"
605                "#else\n"
606                "# define SECTION\n"
607                "#endif\n"
608                "\n"
609                "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;}  %s_resource_data = { {\n",
610                c_name_no_underscores, data_size, c_name);
611
612       for (i = 0; i < data_size; i++) {
613         if (i % 8 == 0)
614           fprintf (file, "  ");
615         fprintf (file, "0x%2.2x", (int)data[i]);
616         if (i != data_size - 1)
617           fprintf (file, ", ");
618         if ((i % 8 == 7) || (i == data_size - 1))
619           fprintf (file, "\n");
620       }
621
622       fprintf (file, "} };\n");
623
624       if (manual_register)
625         {
626           static_str = "";
627         }
628       else
629         {
630           static_str = "static ";
631           fprintf (file,
632                    "\n"
633                    "#ifdef G_HAS_CONSTRUCTORS\n"
634                    "\n"
635                    "#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA\n"
636                    "#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(resource_constructor)\n"
637                    "#endif\n"
638                    "G_DEFINE_CONSTRUCTOR(resource_constructor)\n"
639                    "#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA\n"
640                    "#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(resource_destructor)\n"
641                    "#endif\n"
642                    "G_DEFINE_DESTRUCTOR(resource_destructor)\n"
643                    "\n"
644                    "#else\n"
645                    "#warning \"Constructor not supported on this compiler, linking in resources will not work\"\n"
646                    "#endif\n"
647                    "\n");
648         }
649
650       fprintf (file,
651                "\n"
652                "GResource *%s_resource = NULL;\n"
653                "\n"
654                "%svoid %s_unregister_resource (void)\n"
655                "{\n"
656                "  if (%s_resource)\n"
657                "    {\n"
658                "      g_resources_unregister (%s_resource);\n"
659                "      g_resource_unref (%s_resource);\n"
660                "      %s_resource = NULL;\n"
661                "    }\n"
662                "}\n"
663                "\n"
664                "%svoid %s_register_resource (void)\n"
665                "{\n"
666                "  if (%s_resource == NULL)\n"
667                "    {\n"
668                "      GBytes *bytes = g_bytes_new_static (%s_resource_data.data, sizeof (%s_resource_data.data));\n"
669                "      %s_resource = g_resource_new_from_data (bytes, NULL);\n"
670                "      if (%s_resource)\n"
671                "        g_resources_register (%s_resource);\n"
672                "       g_bytes_unref (bytes);\n"
673                "    }\n"
674                "}\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);
675
676       if (!manual_register)
677         fprintf (file,
678                  "\n"
679                  "static void resource_constructor (void)\n"
680                  "{\n"
681                  "  %s_register_resource ();\n"
682                  "}\n"
683                  "\n"
684                  "static void resource_destructor (void)\n"
685                  "{\n"
686                  "  %s_unregister_resource ();\n"
687                  "}\n", c_name, c_name);
688
689       fclose (file);
690     }
691
692   g_free (binary_target);
693
694
695   g_free (target);
696
697   return 0;
698 }