- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / tools / mac_helpers / infoplist_strings_util.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Helper tool that is built and run during a build to pull strings from
6 // the GRD files and generate the InfoPlist.strings files needed for
7 // Mac OS X app bundles.
8
9 #import <Foundation/Foundation.h>
10
11 #include <stdio.h>
12 #include <unistd.h>
13
14 #include "base/file_util.h"
15 #include "base/files/file_path.h"
16 #include "base/mac/scoped_nsautorelease_pool.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/strings/string_piece.h"
19 #include "base/strings/string_util.h"
20 #include "grit/chromium_strings.h"
21 #include "ui/base/resource/data_pack.h"
22
23 namespace {
24
25 NSString* ApplicationVersionString(const char* version_file_path) {
26   NSError* error = nil;
27   NSString* path_string = [NSString stringWithUTF8String:version_file_path];
28   NSString* version_file =
29       [NSString stringWithContentsOfFile:path_string
30                                 encoding:NSUTF8StringEncoding
31                                    error:&error];
32   if (!version_file || error) {
33     fprintf(stderr, "Failed to load version file: %s\n",
34             [[error description] UTF8String]);
35     return nil;
36   }
37
38   int major = 0, minor = 0, build = 0, patch = 0;
39   NSScanner* scanner = [NSScanner scannerWithString:version_file];
40   if ([scanner scanString:@"MAJOR=" intoString:nil] &&
41       [scanner scanInt:&major] &&
42       [scanner scanString:@"MINOR=" intoString:nil] &&
43       [scanner scanInt:&minor] &&
44       [scanner scanString:@"BUILD=" intoString:nil] &&
45       [scanner scanInt:&build] &&
46       [scanner scanString:@"PATCH=" intoString:nil] &&
47       [scanner scanInt:&patch]) {
48     return [NSString stringWithFormat:@"%d.%d.%d.%d",
49             major, minor, build, patch];
50   }
51   fprintf(stderr, "Failed to parse version file\n");
52   return nil;
53 }
54
55 ui::DataPack* LoadResourceDataPack(const char* dir_path,
56                                    const char* branding_strings_name,
57                                    const char* locale_name) {
58   ui::DataPack* resource_pack = NULL;
59
60   NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak",
61                              dir_path, branding_strings_name, locale_name];
62   if (resource_path) {
63     base::FilePath resources_pak_path([resource_path fileSystemRepresentation]);
64     resources_pak_path = base::MakeAbsoluteFilePath(resources_pak_path);
65     resource_pack = new ui::DataPack(ui::SCALE_FACTOR_100P);
66     bool success = resource_pack->LoadFromPath(resources_pak_path);
67     if (!success) {
68       delete resource_pack;
69       resource_pack = NULL;
70     }
71   }
72
73   return resource_pack;
74 }
75
76 NSString* LoadStringFromDataPack(ui::DataPack* data_pack,
77                                  const char* data_pack_lang,
78                                  uint32_t resource_id,
79                                  const char* resource_id_str) {
80   NSString* result = nil;
81   base::StringPiece data;
82   if (data_pack->GetStringPiece(resource_id, &data)) {
83     // Data pack encodes strings as either UTF8 or UTF16.
84     if (data_pack->GetTextEncodingType() == ui::DataPack::UTF8) {
85       result =
86           [[[NSString alloc] initWithBytes:data.data()
87                                     length:data.length()
88                                   encoding:NSUTF8StringEncoding]
89            autorelease];
90     } else if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
91       result =
92           [[[NSString alloc] initWithBytes:data.data()
93                                     length:data.length()
94                                   encoding:NSUTF16LittleEndianStringEncoding]
95            autorelease];
96     } else {
97       fprintf(stderr, "ERROR: requested string %s from binary data pack\n",
98               resource_id_str);
99       exit(1);
100     }
101   }
102   if (!result) {
103     fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
104             resource_id_str, data_pack_lang);
105     exit(1);
106   }
107   return result;
108 }
109
110 // Escape quotes, newlines, etc so there are no errors when the strings file
111 // is parsed.
112 NSString* EscapeForStringsFileValue(NSString* str) {
113   NSMutableString* worker = [NSMutableString stringWithString:str];
114
115   // Since this is a build tool, we don't really worry about making this
116   // the most efficient code.
117
118   // Backslash first since we need to do it before we put in all the others
119   [worker replaceOccurrencesOfString:@"\\"
120                           withString:@"\\\\"
121                              options:NSLiteralSearch
122                                range:NSMakeRange(0, [worker length])];
123   // Now the rest of them.
124   [worker replaceOccurrencesOfString:@"\n"
125                           withString:@"\\n"
126                              options:NSLiteralSearch
127                                range:NSMakeRange(0, [worker length])];
128   [worker replaceOccurrencesOfString:@"\r"
129                           withString:@"\\r"
130                              options:NSLiteralSearch
131                                range:NSMakeRange(0, [worker length])];
132   [worker replaceOccurrencesOfString:@"\t"
133                           withString:@"\\t"
134                              options:NSLiteralSearch
135                                range:NSMakeRange(0, [worker length])];
136   [worker replaceOccurrencesOfString:@"\""
137                           withString:@"\\\""
138                              options:NSLiteralSearch
139                                range:NSMakeRange(0, [worker length])];
140
141   return [[worker copy] autorelease];
142 }
143
144 // The valid types for the -t arg
145 const char* kAppType_Main = "main";  // Main app
146 const char* kAppType_Helper = "helper";  // Helper app
147
148 }  // namespace
149
150 int main(int argc, char* const argv[]) {
151   base::mac::ScopedNSAutoreleasePool autorelease_pool;
152
153   const char* version_file_path = NULL;
154   const char* grit_output_dir = NULL;
155   const char* branding_strings_name = NULL;
156   const char* output_dir = NULL;
157   const char* app_type = kAppType_Main;
158
159   // Process the args
160   int ch;
161   while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
162     switch (ch) {
163       case 't':
164         app_type = optarg;
165         break;
166       case 'v':
167         version_file_path = optarg;
168         break;
169       case 'g':
170         grit_output_dir = optarg;
171         break;
172       case 'b':
173         branding_strings_name = optarg;
174         break;
175       case 'o':
176         output_dir = optarg;
177         break;
178       default:
179         fprintf(stderr, "ERROR: bad command line arg\n");
180         exit(1);
181         break;
182     }
183   }
184   argc -= optind;
185   argv += optind;
186
187 #define CHECK_ARG(a, b) \
188   do { \
189     if ((a)) { \
190       fprintf(stderr, "ERROR: " b "\n"); \
191       exit(1); \
192     } \
193   } while (false)
194
195   // Check our args
196   CHECK_ARG(!version_file_path, "Missing VERSION file path");
197   CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
198   CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
199   CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
200   CHECK_ARG(argc == 0, "Missing language list");
201   CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
202              strcmp(app_type, kAppType_Helper) != 0),
203             "Unknown app type");
204
205   char* const* lang_list = argv;
206   int lang_list_count = argc;
207
208   // Parse the version file and build our string
209   NSString* version_string = ApplicationVersionString(version_file_path);
210   if (!version_string) {
211     fprintf(stderr, "ERROR: failed to get a version string");
212     exit(1);
213   }
214
215   NSFileManager* fm = [NSFileManager defaultManager];
216
217   for (int loop = 0; loop < lang_list_count; ++loop) {
218     const char* cur_lang = lang_list[loop];
219
220     // Open the branded string pak file
221     scoped_ptr<ui::DataPack> branded_data_pack(
222         LoadResourceDataPack(grit_output_dir,
223                              branding_strings_name,
224                              cur_lang));
225     if (branded_data_pack.get() == NULL) {
226       fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
227               cur_lang);
228       exit(1);
229     }
230
231     uint32_t name_id = IDS_PRODUCT_NAME;
232     const char* name_id_str = "IDS_PRODUCT_NAME";
233     uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
234     const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
235     if (strcmp(app_type, kAppType_Helper) == 0) {
236       name_id = IDS_HELPER_NAME;
237       name_id_str = "IDS_HELPER_NAME";
238       short_name_id = IDS_SHORT_HELPER_NAME;
239       short_name_id_str = "IDS_SHORT_HELPER_NAME";
240     }
241
242     // Fetch the strings
243     NSString* name =
244           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
245                                  name_id, name_id_str);
246     NSString* short_name =
247           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
248                                  short_name_id, short_name_id_str);
249     NSString* copyright =
250         LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
251                                IDS_ABOUT_VERSION_COPYRIGHT,
252                                "IDS_ABOUT_VERSION_COPYRIGHT");
253
254     // For now, assume this is ok for all languages. If we need to, this could
255     // be moved into generated_resources.grd and fetched.
256     NSString *get_info = [NSString stringWithFormat:@"%@ %@, %@",
257                           name, version_string, copyright];
258
259     // Generate the InfoPlist.strings file contents
260     NSString* strings_file_contents_string =
261         [NSString stringWithFormat:
262           @"CFBundleDisplayName = \"%@\";\n"
263           @"CFBundleGetInfoString = \"%@\";\n"
264           @"CFBundleName = \"%@\";\n"
265           @"NSHumanReadableCopyright = \"%@\";\n",
266           EscapeForStringsFileValue(name),
267           EscapeForStringsFileValue(get_info),
268           EscapeForStringsFileValue(short_name),
269           EscapeForStringsFileValue(copyright)];
270
271     // We set up Xcode projects expecting strings files to be UTF8, so make
272     // sure we write the data in that form.  When Xcode copies them it will
273     // put them final runtime encoding.
274     NSData* strings_file_contents_utf8 =
275         [strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
276
277     if ([strings_file_contents_utf8 length] == 0) {
278       fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
279               "file for language: %s\n", cur_lang);
280       exit(1);
281     }
282
283     // For Cocoa to find the locale at runtime, it needs to use '_' instead of
284     // '-' (http://crbug.com/20441).  Also, 'en-US' should be represented
285     // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
286     NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
287     if ([cur_lang_ns isEqualToString:@"en-US"]) {
288       cur_lang_ns = @"en";
289     }
290     cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
291                                                          withString:@"_"];
292     // Make sure the lproj we write to exists
293     NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
294     NSString *output_path =
295         [[NSString stringWithUTF8String:output_dir]
296          stringByAppendingPathComponent:lproj_name];
297     NSError* error = nil;
298     if (![fm fileExistsAtPath:output_path] &&
299         ![fm createDirectoryAtPath:output_path
300         withIntermediateDirectories:YES
301                         attributes:nil
302                              error:&error]) {
303       fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
304               [output_path UTF8String]);
305       exit(1);
306     }
307
308     // Write out the file
309     output_path =
310         [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
311     if (![strings_file_contents_utf8 writeToFile:output_path
312                                       atomically:YES]) {
313       fprintf(stderr, "ERROR: Failed to write out '%s'\n",
314               [output_path UTF8String]);
315       exit(1);
316     }
317   }
318   return 0;
319 }