60ccdbf81623091f7bb080b662751923cc47f0df
[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.1 of the License, 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, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Alexander Larsson <alexl@redhat.com>
18  */
19
20 #include "config.h"
21
22 #include <glib.h>
23 #include <gstdio.h>
24 #include <gi18n.h>
25 #include <gioenums.h>
26
27 #include <string.h>
28 #include <stdio.h>
29 #include <locale.h>
30 #include <errno.h>
31 #ifdef G_OS_UNIX
32 #include <unistd.h>
33 #endif
34 #ifdef G_OS_WIN32
35 #include <io.h>
36 #endif
37
38 #include <gio/gmemoryoutputstream.h>
39 #include <gio/gzlibcompressor.h>
40 #include <gio/gconverteroutputstream.h>
41
42 #include <glib.h>
43 #include "gvdb/gvdb-builder.h"
44
45 #include "gconstructor_as_data.h"
46
47 #ifdef G_OS_WIN32
48 #include "glib/glib-private.h"
49 #endif
50
51 typedef struct
52 {
53   char *filename;
54   char *content;
55   gsize content_size;
56   gsize size;
57   guint32 flags;
58 } FileData;
59
60 typedef struct
61 {
62   GHashTable *table; /* resource path -> FileData */
63
64   gboolean collect_data;
65
66   /* per gresource */
67   char *prefix;
68
69   /* per file */
70   char *alias;
71   gboolean compressed;
72   char *preproc_options;
73
74   GString *string;  /* non-NULL when accepting text */
75 } ParseState;
76
77 static gchar **sourcedirs = NULL;
78 static gchar *xmllint = NULL;
79 static gchar *jsonformat = NULL;
80 static gchar *gdk_pixbuf_pixdata = NULL;
81
82 static void
83 file_data_free (FileData *data)
84 {
85   g_free (data->filename);
86   g_free (data->content);
87   g_free (data);
88 }
89
90 static void
91 start_element (GMarkupParseContext  *context,
92                const gchar          *element_name,
93                const gchar         **attribute_names,
94                const gchar         **attribute_values,
95                gpointer              user_data,
96                GError              **error)
97 {
98   ParseState *state = user_data;
99   const GSList *element_stack;
100   const gchar *container;
101
102   element_stack = g_markup_parse_context_get_element_stack (context);
103   container = element_stack->next ? element_stack->next->data : NULL;
104
105 #define COLLECT(first, ...) \
106   g_markup_collect_attributes (element_name,                                 \
107                                attribute_names, attribute_values, error,     \
108                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
109 #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
110 #define STRDUP     G_MARKUP_COLLECT_STRDUP
111 #define STRING     G_MARKUP_COLLECT_STRING
112 #define BOOL       G_MARKUP_COLLECT_BOOLEAN
113 #define NO_ATTRS()  COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
114
115   if (container == NULL)
116     {
117       if (strcmp (element_name, "gresources") == 0)
118         return;
119     }
120   else if (strcmp (container, "gresources") == 0)
121     {
122       if (strcmp (element_name, "gresource") == 0)
123         {
124           COLLECT (OPTIONAL | STRDUP,
125                    "prefix", &state->prefix);
126           return;
127         }
128     }
129   else if (strcmp (container, "gresource") == 0)
130     {
131       if (strcmp (element_name, "file") == 0)
132         {
133           COLLECT (OPTIONAL | STRDUP, "alias", &state->alias,
134                    OPTIONAL | BOOL, "compressed", &state->compressed,
135                    OPTIONAL | STRDUP, "preprocess", &state->preproc_options);
136           state->string = g_string_new ("");
137           return;
138         }
139     }
140
141   if (container)
142     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
143                  _("Element <%s> not allowed inside <%s>"),
144                  element_name, container);
145   else
146     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
147                  _("Element <%s> not allowed at toplevel"), element_name);
148
149 }
150
151 static GvdbItem *
152 get_parent (GHashTable *table,
153             gchar      *key,
154             gint        length)
155 {
156   GvdbItem *grandparent, *parent;
157
158   if (length == 1)
159     return NULL;
160
161   while (key[--length - 1] != '/');
162   key[length] = '\0';
163
164   parent = g_hash_table_lookup (table, key);
165
166   if (parent == NULL)
167     {
168       parent = gvdb_hash_table_insert (table, key);
169
170       grandparent = get_parent (table, key, length);
171
172       if (grandparent != NULL)
173         gvdb_item_set_parent (parent, grandparent);
174     }
175
176   return parent;
177 }
178
179 static gchar *
180 find_file (const gchar *filename)
181 {
182   guint i;
183   gchar *real_file;
184   gboolean exists;
185
186   if (g_path_is_absolute (filename))
187     return g_strdup (filename);
188
189   /* search all the sourcedirs for the correct files in order */
190   for (i = 0; sourcedirs[i] != NULL; i++)
191     {
192         real_file = g_build_path ("/", sourcedirs[i], filename, NULL);
193         exists = g_file_test (real_file, G_FILE_TEST_EXISTS);
194         if (exists)
195           return real_file;
196         g_free (real_file);
197     }
198     return NULL;
199 }
200
201 static void
202 end_element (GMarkupParseContext  *context,
203              const gchar          *element_name,
204              gpointer              user_data,
205              GError              **error)
206 {
207   ParseState *state = user_data;
208   GError *my_error = NULL;
209
210   if (strcmp (element_name, "gresource") == 0)
211     {
212       g_free (state->prefix);
213       state->prefix = NULL;
214     }
215
216   else if (strcmp (element_name, "file") == 0)
217     {
218       gchar *file;
219       gchar *real_file = NULL;
220       gchar *key;
221       FileData *data = NULL;
222       char *tmp_file = NULL;
223
224       file = state->string->str;
225       key = file;
226       if (state->alias)
227         key = state->alias;
228
229       if (state->prefix)
230         key = g_build_path ("/", "/", state->prefix, key, NULL);
231       else
232         key = g_build_path ("/", "/", key, NULL);
233
234       if (g_hash_table_lookup (state->table, key) != NULL)
235         {
236           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
237                        _("File %s appears multiple times in the resource"),
238                        key);
239           return;
240         }
241
242       if (sourcedirs != NULL)
243         {
244           real_file = find_file (file);
245           if (real_file == NULL && state->collect_data)
246             {
247                 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
248                              _("Failed to locate “%s” in any source directory"), file);
249                 return;
250             }
251         }
252       else
253         {
254           gboolean exists;
255           exists = g_file_test (file, G_FILE_TEST_EXISTS);
256           if (!exists && state->collect_data)
257             {
258               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
259                            _("Failed to locate “%s” in current directory"), file);
260               return;
261             }
262         }
263
264       if (real_file == NULL)
265         real_file = g_strdup (file);
266
267       data = g_new0 (FileData, 1);
268       data->filename = g_strdup (real_file);
269       if (!state->collect_data)
270         goto done;
271
272       if (state->preproc_options)
273         {
274           gchar **options;
275           guint i;
276           gboolean xml_stripblanks = FALSE;
277           gboolean json_stripblanks = FALSE;
278           gboolean to_pixdata = FALSE;
279
280           options = g_strsplit (state->preproc_options, ",", -1);
281
282           for (i = 0; options[i]; i++)
283             {
284               if (!strcmp (options[i], "xml-stripblanks"))
285                 xml_stripblanks = TRUE;
286               else if (!strcmp (options[i], "to-pixdata"))
287                 to_pixdata = TRUE;
288               else if (!strcmp (options[i], "json-stripblanks"))
289                 json_stripblanks = TRUE;
290               else
291                 {
292                   g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
293                                _("Unknown processing option “%s”"), options[i]);
294                   g_strfreev (options);
295                   goto cleanup;
296                 }
297             }
298           g_strfreev (options);
299
300           if (xml_stripblanks)
301             {
302               /* This is not fatal: pretty-printed XML is still valid XML */
303               if (xmllint == NULL)
304                 {
305                   static gboolean xmllint_warned = FALSE;
306
307                   if (!xmllint_warned)
308                     {
309                       /* Translators: the first %s is a gresource XML attribute,
310                        * the second %s is an environment variable, and the third
311                        * %s is a command line tool
312                        */
313                       char *warn = g_strdup_printf (_("%s preprocessing requested, but %s is not set, and %s is not in PATH"),
314                                                     "xml-stripblanks",
315                                                     "XMLLINT",
316                                                     "xmllint");
317                       g_printerr ("%s\n", warn);
318                       g_free (warn);
319
320                       /* Only warn once */
321                       xmllint_warned = TRUE;
322                     }
323                 }
324               else
325                 {
326                   GSubprocess *proc;
327                   int fd;
328
329                   fd = g_file_open_tmp ("resource-XXXXXXXX", &tmp_file, error);
330                   if (fd < 0)
331                     goto cleanup;
332
333                   close (fd);
334
335                   proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE, error,
336                                            xmllint, "--nonet", "--noblanks", "--output", tmp_file, real_file, NULL);
337                   g_free (real_file);
338                   real_file = NULL;
339
340                   if (!proc)
341                     goto cleanup;
342
343                   if (!g_subprocess_wait_check (proc, NULL, error))
344                     {
345                       g_object_unref (proc);
346                       goto cleanup;
347                     }
348
349                   g_object_unref (proc);
350
351                   real_file = g_strdup (tmp_file);
352                 }
353             }
354
355           if (json_stripblanks)
356             {
357               /* As above, this is not fatal: pretty-printed JSON is still
358                * valid JSON
359                */
360               if (jsonformat == NULL)
361                 {
362                   static gboolean jsonformat_warned = FALSE;
363
364                   if (!jsonformat_warned)
365                     {
366                       /* Translators: the first %s is a gresource XML attribute,
367                        * the second %s is an environment variable, and the third
368                        * %s is a command line tool
369                        */
370                       char *warn = g_strdup_printf (_("%s preprocessing requested, but %s is not set, and %s is not in PATH"),
371                                                     "json-stripblanks",
372                                                     "JSON_GLIB_FORMAT",
373                                                     "json-glib-format");
374                       g_printerr ("%s\n", warn);
375                       g_free (warn);
376
377                       /* Only warn once */
378                       jsonformat_warned = TRUE;
379                     }
380                 }
381               else
382                 {
383                   GSubprocess *proc;
384                   int fd;
385
386                   fd = g_file_open_tmp ("resource-XXXXXXXX", &tmp_file, error);
387                   if (fd < 0)
388                     goto cleanup;
389
390                   close (fd);
391
392                   proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE, error,
393                                            jsonformat, "--output", tmp_file, real_file, NULL);
394                   g_free (real_file);
395                   real_file = NULL;
396
397                   if (!proc)
398                     goto cleanup;
399
400                   if (!g_subprocess_wait_check (proc, NULL, error))
401                     {
402                       g_object_unref (proc);
403                       goto cleanup;
404                     }
405
406                   g_object_unref (proc);
407
408                   real_file = g_strdup (tmp_file);
409                 }
410             }
411
412           if (to_pixdata)
413             {
414               GSubprocess *proc;
415               int fd;
416
417               /* This is a fatal error: if to-pixdata is used it means that
418                * the code loading the GResource expects a specific data format
419                */
420               if (gdk_pixbuf_pixdata == NULL)
421                 {
422                   /* Translators: the first %s is a gresource XML attribute,
423                    * the second %s is an environment variable, and the third
424                    * %s is a command line tool
425                    */
426                   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
427                                _("%s preprocessing requested, but %s is not set, and %s is not in PATH"),
428                                "to-pixdata",
429                                "GDK_PIXBUF_PIXDATA",
430                                "gdk-pixbuf-pixdata");
431                   goto cleanup;
432                 }
433
434               fd = g_file_open_tmp ("resource-XXXXXXXX", &tmp_file, error);
435               if (fd < 0)
436                 goto cleanup;
437
438               close (fd);
439
440               proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE, error,
441                                        gdk_pixbuf_pixdata, real_file, tmp_file, NULL);
442               g_free (real_file);
443               real_file = NULL;
444
445               if (!g_subprocess_wait_check (proc, NULL, error))
446                 {
447                   g_object_unref (proc);
448                   goto cleanup;
449                 }
450
451               g_object_unref (proc);
452
453               real_file = g_strdup (tmp_file);
454             }
455         }
456
457       if (!g_file_get_contents (real_file, &data->content, &data->size, &my_error))
458         {
459           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
460                        _("Error reading file %s: %s"),
461                        real_file, my_error->message);
462           g_clear_error (&my_error);
463           goto cleanup;
464         }
465       /* Include zero termination in content_size for uncompressed files (but not in size) */
466       data->content_size = data->size + 1;
467
468       if (state->compressed)
469         {
470           GOutputStream *out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
471           GZlibCompressor *compressor =
472             g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB, 9);
473           GOutputStream *out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor));
474
475           if (!g_output_stream_write_all (out2, data->content, data->size,
476                                           NULL, NULL, NULL) ||
477               !g_output_stream_close (out2, NULL, NULL))
478             {
479               g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
480                            _("Error compressing file %s"),
481                            real_file);
482               goto cleanup;
483             }
484
485           g_free (data->content);
486           data->content_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (out));
487           data->content = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out));
488
489           g_object_unref (compressor);
490           g_object_unref (out);
491           g_object_unref (out2);
492
493           data->flags |= G_RESOURCE_FLAGS_COMPRESSED;
494         }
495
496 done:
497       g_hash_table_insert (state->table, key, data);
498       data = NULL;
499
500     cleanup:
501       /* Cleanup */
502
503       g_free (state->alias);
504       state->alias = NULL;
505       g_string_free (state->string, TRUE);
506       state->string = NULL;
507       g_free (state->preproc_options);
508       state->preproc_options = NULL;
509
510       g_free (real_file);
511
512       if (tmp_file)
513         {
514           unlink (tmp_file);
515           g_free (tmp_file);
516         }
517
518       if (data != NULL)
519         file_data_free (data);
520     }
521 }
522
523 static void
524 text (GMarkupParseContext  *context,
525       const gchar          *text,
526       gsize                 text_len,
527       gpointer              user_data,
528       GError              **error)
529 {
530   ParseState *state = user_data;
531   gsize i;
532
533   for (i = 0; i < text_len; i++)
534     if (!g_ascii_isspace (text[i]))
535       {
536         if (state->string)
537           g_string_append_len (state->string, text, text_len);
538
539         else
540           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
541                        _("text may not appear inside <%s>"),
542                        g_markup_parse_context_get_element (context));
543
544         break;
545       }
546 }
547
548 static GHashTable *
549 parse_resource_file (const gchar *filename,
550                      gboolean     collect_data,
551                      GHashTable  *files)
552 {
553   GMarkupParser parser = { start_element, end_element, text };
554   ParseState state = { 0, };
555   GMarkupParseContext *context;
556   GError *error = NULL;
557   gchar *contents;
558   GHashTable *table = NULL;
559   gsize size;
560
561   if (!g_file_get_contents (filename, &contents, &size, &error))
562     {
563       g_printerr ("%s\n", error->message);
564       g_clear_error (&error);
565       return NULL;
566     }
567
568   state.collect_data = collect_data;
569   state.table = g_hash_table_ref (files);
570
571   context = g_markup_parse_context_new (&parser,
572                                         G_MARKUP_TREAT_CDATA_AS_TEXT |
573                                         G_MARKUP_PREFIX_ERROR_POSITION,
574                                         &state, NULL);
575
576   if (!g_markup_parse_context_parse (context, contents, size, &error) ||
577       !g_markup_parse_context_end_parse (context, &error))
578     {
579       g_printerr ("%s: %s.\n", filename, error->message);
580       g_clear_error (&error);
581     }
582   else
583     {
584       GHashTableIter iter;
585       const char *key;
586       char *mykey;
587       gsize key_len;
588       FileData *data;
589       GVariant *v_data;
590       GVariantBuilder builder;
591       GvdbItem *item;
592
593       table = gvdb_hash_table_new (NULL, NULL);
594
595       g_hash_table_iter_init (&iter, state.table);
596       while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&data))
597         {
598           key_len = strlen (key);
599           mykey = g_strdup (key);
600
601           item = gvdb_hash_table_insert (table, key);
602           gvdb_item_set_parent (item,
603                                 get_parent (table, mykey, key_len));
604
605           g_free (mykey);
606
607           g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuay)"));
608
609           g_variant_builder_add (&builder, "u", data->size); /* Size */
610           g_variant_builder_add (&builder, "u", data->flags); /* Flags */
611
612           v_data = g_variant_new_from_data (G_VARIANT_TYPE("ay"),
613                                             data->content, data->content_size, TRUE,
614                                             g_free, data->content);
615           g_variant_builder_add_value (&builder, v_data);
616           data->content = NULL; /* Take ownership */
617
618           gvdb_item_set_value (item,
619                                g_variant_builder_end (&builder));
620         }
621     }
622
623   g_hash_table_unref (state.table);
624   g_markup_parse_context_free (context);
625   g_free (contents);
626
627   return table;
628 }
629
630 static gboolean
631 write_to_file (GHashTable   *table,
632                const gchar  *filename,
633                GError      **error)
634 {
635   gboolean success;
636
637   success = gvdb_table_write_contents (table, filename,
638                                        G_BYTE_ORDER != G_LITTLE_ENDIAN,
639                                        error);
640
641   return success;
642 }
643
644 static gboolean
645 extension_in_set (const char *str,
646                   ...)
647 {
648   va_list list;
649   const char *ext, *value;
650   gboolean rv = FALSE;
651
652   ext = strrchr (str, '.');
653   if (ext == NULL)
654     return FALSE;
655
656   ext++;
657   va_start (list, str);
658   while ((value = va_arg (list, const char *)) != NULL)
659     {
660       if (g_ascii_strcasecmp (ext, value) != 0)
661         continue;
662
663       rv = TRUE;
664       break;
665     }
666
667   va_end (list);
668   return rv;
669 }
670
671 /*
672  * We must escape any characters that `make` finds significant.
673  * This is largely a duplicate of the logic in gcc's `mkdeps.c:munge()`.
674  */
675 static char *
676 escape_makefile_string (const char *string)
677 {
678   GString *str;
679   const char *p, *q;
680
681   str = g_string_sized_new (strlen (string) + 1);
682   for (p = string; *p != '\0'; ++p)
683     {
684       switch (*p)
685         {
686         case ' ':
687         case '\t':
688           /* GNU make uses a weird quoting scheme for white space.
689              A space or tab preceded by 2N+1 backslashes represents
690              N backslashes followed by space; a space or tab
691              preceded by 2N backslashes represents N backslashes at
692              the end of a file name; and backslashes in other
693              contexts should not be doubled.  */
694           for (q = p - 1; string <= q && *q == '\\';  q--)
695             g_string_append_c (str, '\\');
696           g_string_append_c (str, '\\');
697           break;
698
699         case '$':
700           g_string_append_c (str, '$');
701           break;
702
703         case '#':
704           g_string_append_c (str, '\\');
705           break;
706         }
707       g_string_append_c (str, *p);
708     }
709
710   return g_string_free (str, FALSE);
711 }
712
713 int
714 main (int argc, char **argv)
715 {
716   GError *error;
717   GHashTable *table;
718   GHashTable *files;
719   gchar *srcfile;
720   gboolean show_version_and_exit = FALSE;
721   gchar *target = NULL;
722   gchar *binary_target = NULL;
723   gboolean generate_automatic = FALSE;
724   gboolean generate_source = FALSE;
725   gboolean generate_header = FALSE;
726   gboolean manual_register = FALSE;
727   gboolean internal = FALSE;
728   gboolean external_data = FALSE;
729   gboolean generate_dependencies = FALSE;
730   gboolean generate_phony_targets = FALSE;
731   char *dependency_file = NULL;
732   char *c_name = NULL;
733   char *c_name_no_underscores;
734   const char *linkage = "extern";
735   GOptionContext *context;
736   GOptionEntry entries[] = {
737     { "version", 0, 0, G_OPTION_ARG_NONE, &show_version_and_exit, N_("Show program version and exit"), NULL },
738     { "target", 0, 0, G_OPTION_ARG_FILENAME, &target, N_("Name of the output file"), N_("FILE") },
739     { "sourcedir", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &sourcedirs, N_("The directories to load files referenced in FILE from (default: current directory)"), N_("DIRECTORY") },
740     { "generate", 0, 0, G_OPTION_ARG_NONE, &generate_automatic, N_("Generate output in the format selected for by the target filename extension"), NULL },
741     { "generate-header", 0, 0, G_OPTION_ARG_NONE, &generate_header, N_("Generate source header"), NULL },
742     { "generate-source", 0, 0, G_OPTION_ARG_NONE, &generate_source, N_("Generate source code used to link in the resource file into your code"), NULL },
743     { "generate-dependencies", 0, 0, G_OPTION_ARG_NONE, &generate_dependencies, N_("Generate dependency list"), NULL },
744     { "dependency-file", 0, 0, G_OPTION_ARG_FILENAME, &dependency_file, N_("Name of the dependency file to generate"), N_("FILE") },
745     { "generate-phony-targets", 0, 0, G_OPTION_ARG_NONE, &generate_phony_targets, N_("Include phony targets in the generated dependency file"), NULL },
746     { "manual-register", 0, 0, G_OPTION_ARG_NONE, &manual_register, N_("Don’t automatically create and register resource"), NULL },
747     { "internal", 0, 0, G_OPTION_ARG_NONE, &internal, N_("Don’t export functions; declare them G_GNUC_INTERNAL"), NULL },
748     { "external-data", 0, 0, G_OPTION_ARG_NONE, &external_data, N_("Don’t embed resource data in the C file; assume it's linked externally instead"), NULL },
749     { "c-name", 0, 0, G_OPTION_ARG_STRING, &c_name, N_("C identifier name used for the generated source code"), NULL },
750     { NULL }
751   };
752
753 #ifdef G_OS_WIN32
754   gchar *tmp;
755 #endif
756
757   setlocale (LC_ALL, "");
758   textdomain (GETTEXT_PACKAGE);
759
760 #ifdef G_OS_WIN32
761   tmp = _glib_get_locale_dir ();
762   bindtextdomain (GETTEXT_PACKAGE, tmp);
763   g_free (tmp);
764 #else
765   bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
766 #endif
767
768 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
769   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
770 #endif
771
772   context = g_option_context_new (N_("FILE"));
773   g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
774   g_option_context_set_summary (context,
775     N_("Compile a resource specification into a resource file.\n"
776        "Resource specification files have the extension .gresource.xml,\n"
777        "and the resource file have the extension called .gresource."));
778   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
779
780   error = NULL;
781   if (!g_option_context_parse (context, &argc, &argv, &error))
782     {
783       g_printerr ("%s\n", error->message);
784       return 1;
785     }
786
787   g_option_context_free (context);
788
789   if (show_version_and_exit)
790     {
791       g_print (PACKAGE_VERSION "\n");
792       return 0;
793     }
794
795   if (argc != 2)
796     {
797       g_printerr (_("You should give exactly one file name\n"));
798       g_free (c_name);
799       return 1;
800     }
801
802   if (internal)
803     linkage = "G_GNUC_INTERNAL";
804
805   srcfile = argv[1];
806
807   xmllint = g_strdup (g_getenv ("XMLLINT"));
808   if (xmllint == NULL)
809     xmllint = g_find_program_in_path ("xmllint");
810
811   jsonformat = g_strdup (g_getenv ("JSON_GLIB_FORMAT"));
812   if (jsonformat == NULL)
813     jsonformat = g_find_program_in_path ("json-glib-format");
814
815   gdk_pixbuf_pixdata = g_strdup (g_getenv ("GDK_PIXBUF_PIXDATA"));
816   if (gdk_pixbuf_pixdata == NULL)
817     gdk_pixbuf_pixdata = g_find_program_in_path ("gdk-pixbuf-pixdata");
818
819   if (target == NULL)
820     {
821       char *dirname = g_path_get_dirname (srcfile);
822       char *base = g_path_get_basename (srcfile);
823       char *target_basename;
824       if (g_str_has_suffix (base, ".xml"))
825         base[strlen(base) - strlen (".xml")] = 0;
826
827       if (generate_source)
828         {
829           if (g_str_has_suffix (base, ".gresource"))
830             base[strlen(base) - strlen (".gresource")] = 0;
831           target_basename = g_strconcat (base, ".c", NULL);
832         }
833       else if (generate_header)
834         {
835           if (g_str_has_suffix (base, ".gresource"))
836             base[strlen(base) - strlen (".gresource")] = 0;
837           target_basename = g_strconcat (base, ".h", NULL);
838         }
839       else
840         {
841           if (g_str_has_suffix (base, ".gresource"))
842             target_basename = g_strdup (base);
843           else
844             target_basename = g_strconcat (base, ".gresource", NULL);
845         }
846
847       target = g_build_filename (dirname, target_basename, NULL);
848       g_free (target_basename);
849       g_free (dirname);
850       g_free (base);
851     }
852   else if (generate_automatic)
853     {
854       if (extension_in_set (target, "c", "cc", "cpp", "cxx", "c++", NULL))
855         generate_source = TRUE;
856       else if (extension_in_set (target, "h", "hh", "hpp", "hxx", "h++", NULL))
857         generate_header = TRUE;
858       else if (extension_in_set (target, "gresource", NULL))
859         { }
860     }
861
862   files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)file_data_free);
863
864   if ((table = parse_resource_file (srcfile, !generate_dependencies, files)) == NULL)
865     {
866       g_free (target);
867       g_free (c_name);
868       g_hash_table_unref (files);
869       return 1;
870     }
871
872   /* This can be used in the same invocation
873      as other generate commands */
874   if (dependency_file != NULL)
875     {
876       /* Generate a .d file that describes the dependencies for
877        * build tools, gcc -M -MF style */
878       GString *dep_string;
879       GHashTableIter iter;
880       gpointer key, data;
881       FileData *file_data;
882       char *escaped;
883
884       g_hash_table_iter_init (&iter, files);
885
886       dep_string = g_string_new (NULL);
887       escaped = escape_makefile_string (srcfile);
888       g_string_printf (dep_string, "%s:", escaped);
889       g_free (escaped);
890
891       /* First rule: foo.xml: resource1 resource2.. */
892       while (g_hash_table_iter_next (&iter, &key, &data))
893         {
894           file_data = data;
895           if (!g_str_equal (file_data->filename, srcfile))
896             {
897               escaped = escape_makefile_string (file_data->filename);
898               g_string_append_printf (dep_string, " %s", escaped);
899               g_free (escaped);
900             }
901         }
902
903       g_string_append (dep_string, "\n");
904
905       /* Optionally include phony targets as it silences `make` but
906        * isn't supported on `ninja` at the moment. See also: `gcc -MP`
907        */
908       if (generate_phony_targets)
909         {
910                                         g_string_append (dep_string, "\n");
911
912           /* One rule for every resource: resourceN: */
913           g_hash_table_iter_init (&iter, files);
914           while (g_hash_table_iter_next (&iter, &key, &data))
915             {
916               file_data = data;
917               if (!g_str_equal (file_data->filename, srcfile))
918                 {
919                   escaped = escape_makefile_string (file_data->filename);
920                   g_string_append_printf (dep_string, "%s:\n\n", escaped);
921                   g_free (escaped);
922                 }
923             }
924         }
925
926       if (g_str_equal (dependency_file, "-"))
927         {
928           g_print ("%s\n", dep_string->str);
929         }
930       else
931         {
932           if (!g_file_set_contents (dependency_file, dep_string->str, dep_string->len, &error))
933             {
934               g_printerr ("Error writing dependency file: %s\n", error->message);
935               g_string_free (dep_string, TRUE);
936               g_free (dependency_file);
937               g_error_free (error);
938               g_hash_table_unref (files);
939               return 1;
940             }
941         }
942
943       g_string_free (dep_string, TRUE);
944       g_free (dependency_file);
945     }
946
947   if (generate_dependencies)
948     {
949       GHashTableIter iter;
950       gpointer key, data;
951       FileData *file_data;
952
953       g_hash_table_iter_init (&iter, files);
954
955       /* Generate list of files for direct use as dependencies in a Makefile */
956       while (g_hash_table_iter_next (&iter, &key, &data))
957         {
958           file_data = data;
959           g_print ("%s\n", file_data->filename);
960         }
961     }
962   else if (generate_source || generate_header)
963     {
964       if (generate_source)
965         {
966           int fd = g_file_open_tmp (NULL, &binary_target, NULL);
967           if (fd == -1)
968             {
969               g_printerr ("Can't open temp file\n");
970               g_free (c_name);
971               g_hash_table_unref (files);
972               return 1;
973             }
974           close (fd);
975         }
976
977       if (c_name == NULL)
978         {
979           char *base = g_path_get_basename (srcfile);
980           GString *s;
981           char *dot;
982           int i;
983
984           /* Remove extensions */
985           dot = strchr (base, '.');
986           if (dot)
987             *dot = 0;
988
989           s = g_string_new ("");
990
991           for (i = 0; base[i] != 0; i++)
992             {
993               const char *first = G_CSET_A_2_Z G_CSET_a_2_z "_";
994               const char *rest = G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_";
995               if (strchr ((i == 0) ? first : rest, base[i]) != NULL)
996                 g_string_append_c (s, base[i]);
997               else if (base[i] == '-')
998                 g_string_append_c (s, '_');
999
1000             }
1001
1002           c_name = g_string_free (s, FALSE);
1003         }
1004     }
1005   else
1006     binary_target = g_strdup (target);
1007
1008   c_name_no_underscores = c_name;
1009   while (c_name_no_underscores && *c_name_no_underscores == '_')
1010     c_name_no_underscores++;
1011
1012   if (binary_target != NULL &&
1013       !write_to_file (table, binary_target, &error))
1014     {
1015       g_printerr ("%s\n", error->message);
1016       g_free (target);
1017       g_free (c_name);
1018       g_hash_table_unref (files);
1019       return 1;
1020     }
1021
1022   if (generate_header)
1023     {
1024       FILE *file;
1025
1026       file = fopen (target, "w");
1027       if (file == NULL)
1028         {
1029           g_printerr ("can't write to file %s", target);
1030           g_free (c_name);
1031           g_hash_table_unref (files);
1032           return 1;
1033         }
1034
1035       g_fprintf (file,
1036                "#ifndef __RESOURCE_%s_H__\n"
1037                "#define __RESOURCE_%s_H__\n"
1038                "\n"
1039                "#include <gio/gio.h>\n"
1040                "\n"
1041                "%s GResource *%s_get_resource (void);\n",
1042                c_name, c_name, linkage, c_name);
1043
1044       if (manual_register)
1045         g_fprintf (file,
1046                  "\n"
1047                  "%s void %s_register_resource (void);\n"
1048                  "%s void %s_unregister_resource (void);\n"
1049                  "\n",
1050                  linkage, c_name, linkage, c_name);
1051
1052       g_fprintf (file,
1053                "#endif\n");
1054
1055       fclose (file);
1056     }
1057   else if (generate_source)
1058     {
1059       FILE *file;
1060       guint8 *data;
1061       gsize data_size;
1062       gsize i;
1063
1064       if (!g_file_get_contents (binary_target, (char **)&data,
1065                                 &data_size, NULL))
1066         {
1067           g_printerr ("can't read back temporary file");
1068           g_free (c_name);
1069           g_hash_table_unref (files);
1070           return 1;
1071         }
1072       g_unlink (binary_target);
1073
1074       file = fopen (target, "w");
1075       if (file == NULL)
1076         {
1077           g_printerr ("can't write to file %s", target);
1078           g_free (c_name);
1079           g_hash_table_unref (files);
1080           return 1;
1081         }
1082
1083       g_fprintf (file,
1084                "#include <gio/gio.h>\n"
1085                "\n"
1086                "#if defined (__ELF__) && ( __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))\n"
1087                "# define SECTION __attribute__ ((section (\".gresource.%s\"), aligned (8)))\n"
1088                "#else\n"
1089                "# define SECTION\n"
1090                "#endif\n"
1091                "\n",
1092                c_name_no_underscores);
1093
1094       if (external_data)
1095         {
1096           g_fprintf (file,
1097                      "extern const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;}  %s_resource_data;"
1098                      "\n",
1099                      data_size, c_name);
1100         }
1101       else
1102         {
1103           /* For Visual Studio builds: Avoid surpassing the 65535-character limit for a string, GitLab issue #1580 */
1104           g_fprintf (file, "#ifdef _MSC_VER\n");
1105           g_fprintf (file,
1106                      "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;}  %s_resource_data = { {\n",
1107                      data_size + 1 /* nul terminator */, c_name);
1108
1109           for (i = 0; i < data_size; i++)
1110             {
1111               if (i % 16 == 0)
1112                 g_fprintf (file, "  ");
1113               g_fprintf (file, "0%3.3o", (int)data[i]);
1114               if (i != data_size - 1)
1115                 g_fprintf (file, ", ");
1116               if (i % 16 == 15 || i == data_size - 1)
1117                 g_fprintf (file, "\n");
1118             }
1119
1120           g_fprintf (file, "} };\n");
1121
1122           /* For other compilers, use the long string approach */
1123           g_fprintf (file, "#else /* _MSC_VER */\n");
1124           g_fprintf (file,
1125                      "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;}  %s_resource_data = {\n  \"",
1126                      data_size + 1 /* nul terminator */, c_name);
1127
1128           for (i = 0; i < data_size; i++)
1129             {
1130               g_fprintf (file, "\\%3.3o", (int)data[i]);
1131               if (i % 16 == 15)
1132                 g_fprintf (file, "\"\n  \"");
1133             }
1134
1135           g_fprintf (file, "\" };\n");
1136           g_fprintf (file, "#endif /* !_MSC_VER */\n");
1137         }
1138
1139       g_fprintf (file,
1140                "\n"
1141                "static GStaticResource static_resource = { %s_resource_data.data, sizeof (%s_resource_data.data)%s, NULL, NULL, NULL };\n"
1142                "%s GResource *%s_get_resource (void);\n"
1143                "GResource *%s_get_resource (void)\n"
1144                "{\n"
1145                "  return g_static_resource_get_resource (&static_resource);\n"
1146                "}\n",
1147                c_name, c_name, (external_data ? "" : " - 1 /* nul terminator */"), linkage, c_name, c_name);
1148
1149
1150       if (manual_register)
1151         {
1152           g_fprintf (file,
1153                    "\n"
1154                    "%s void %s_unregister_resource (void);\n"
1155                    "void %s_unregister_resource (void)\n"
1156                    "{\n"
1157                    "  g_static_resource_fini (&static_resource);\n"
1158                    "}\n"
1159                    "\n"
1160                    "%s void %s_register_resource (void);\n"
1161                    "void %s_register_resource (void)\n"
1162                    "{\n"
1163                    "  g_static_resource_init (&static_resource);\n"
1164                    "}\n",
1165                    linkage, c_name, c_name, linkage, c_name, c_name);
1166         }
1167       else
1168         {
1169           g_fprintf (file, "%s", gconstructor_code);
1170           g_fprintf (file,
1171                    "\n"
1172                    "#ifdef G_HAS_CONSTRUCTORS\n"
1173                    "\n"
1174                    "#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA\n"
1175                    "#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(resource_constructor)\n"
1176                    "#endif\n"
1177                    "G_DEFINE_CONSTRUCTOR(resource_constructor)\n"
1178                    "#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA\n"
1179                    "#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(resource_destructor)\n"
1180                    "#endif\n"
1181                    "G_DEFINE_DESTRUCTOR(resource_destructor)\n"
1182                    "\n"
1183                    "#else\n"
1184                    "#warning \"Constructor not supported on this compiler, linking in resources will not work\"\n"
1185                    "#endif\n"
1186                    "\n"
1187                    "static void resource_constructor (void)\n"
1188                    "{\n"
1189                    "  g_static_resource_init (&static_resource);\n"
1190                    "}\n"
1191                    "\n"
1192                    "static void resource_destructor (void)\n"
1193                    "{\n"
1194                    "  g_static_resource_fini (&static_resource);\n"
1195                    "}\n");
1196         }
1197
1198       fclose (file);
1199
1200       g_free (data);
1201     }
1202
1203   g_free (binary_target);
1204   g_free (target);
1205   g_hash_table_destroy (table);
1206   g_free (xmllint);
1207   g_free (jsonformat);
1208   g_free (c_name);
1209   g_hash_table_unref (files);
1210
1211   return 0;
1212 }