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