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>
14 #include "base/file_path.h"
15 #include "base/mac/scoped_nsautorelease_pool.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/string_piece.h"
18 #include "base/string_util.h"
19 #include "grit/chromium_strings.h"
20 #include "ui/base/resource/data_pack.h"
24 NSString* ApplicationVersionString(const char* version_file_path) {
26 NSString* path_string = [NSString stringWithUTF8String:version_file_path];
27 NSString* version_file =
28 [NSString stringWithContentsOfFile:path_string
29 encoding:NSUTF8StringEncoding
31 if (!version_file || error) {
32 fprintf(stderr, "Failed to load version file: %s\n",
33 [[error description] UTF8String]);
37 int major = 0, minor = 0, build = 0, patch = 0;
38 NSScanner* scanner = [NSScanner scannerWithString:version_file];
39 if ([scanner scanString:@"MAJOR=" intoString:nil] &&
40 [scanner scanInt:&major] &&
41 [scanner scanString:@"MINOR=" intoString:nil] &&
42 [scanner scanInt:&minor] &&
43 [scanner scanString:@"BUILD=" intoString:nil] &&
44 [scanner scanInt:&build] &&
45 [scanner scanString:@"PATCH=" intoString:nil] &&
46 [scanner scanInt:&patch]) {
47 return [NSString stringWithFormat:@"%d.%d.%d.%d",
48 major, minor, build, patch];
50 fprintf(stderr, "Failed to parse version file\n");
54 ui::DataPack* LoadResourceDataPack(const char* dir_path,
55 const char* branding_strings_name,
56 const char* locale_name) {
57 ui::DataPack* resource_pack = NULL;
59 NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak",
60 dir_path, branding_strings_name, locale_name];
62 FilePath resources_pak_path([resource_path fileSystemRepresentation]);
63 resource_pack = new ui::DataPack(ui::SCALE_FACTOR_100P);
64 bool success = resource_pack->LoadFromPath(resources_pak_path);
74 NSString* LoadStringFromDataPack(ui::DataPack* data_pack,
75 const char* data_pack_lang,
77 const char* resource_id_str) {
78 NSString* result = nil;
79 base::StringPiece data;
80 if (data_pack->GetStringPiece(resource_id, &data)) {
81 // Data pack encodes strings as either UTF8 or UTF16.
82 if (data_pack->GetTextEncodingType() == ui::DataPack::UTF8) {
84 [[[NSString alloc] initWithBytes:data.data()
86 encoding:NSUTF8StringEncoding]
88 } else if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
90 [[[NSString alloc] initWithBytes:data.data()
92 encoding:NSUTF16LittleEndianStringEncoding]
95 fprintf(stderr, "ERROR: requested string %s from binary data pack\n",
101 fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
102 resource_id_str, data_pack_lang);
108 // Escape quotes, newlines, etc so there are no errors when the strings file
110 NSString* EscapeForStringsFileValue(NSString* str) {
111 NSMutableString* worker = [NSMutableString stringWithString:str];
113 // Since this is a build tool, we don't really worry about making this
114 // the most efficient code.
116 // Backslash first since we need to do it before we put in all the others
117 [worker replaceOccurrencesOfString:@"\\"
119 options:NSLiteralSearch
120 range:NSMakeRange(0, [worker length])];
121 // Now the rest of them.
122 [worker replaceOccurrencesOfString:@"\n"
124 options:NSLiteralSearch
125 range:NSMakeRange(0, [worker length])];
126 [worker replaceOccurrencesOfString:@"\r"
128 options:NSLiteralSearch
129 range:NSMakeRange(0, [worker length])];
130 [worker replaceOccurrencesOfString:@"\t"
132 options:NSLiteralSearch
133 range:NSMakeRange(0, [worker length])];
134 [worker replaceOccurrencesOfString:@"\""
136 options:NSLiteralSearch
137 range:NSMakeRange(0, [worker length])];
139 return [[worker copy] autorelease];
142 // The valid types for the -t arg
143 const char* kAppType_Main = "main"; // Main app
144 const char* kAppType_Helper = "helper"; // Helper app
148 int main(int argc, char* const argv[]) {
149 base::mac::ScopedNSAutoreleasePool autorelease_pool;
151 const char* version_file_path = NULL;
152 const char* grit_output_dir = NULL;
153 const char* branding_strings_name = NULL;
154 const char* output_dir = NULL;
155 const char* app_type = kAppType_Main;
159 while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
165 version_file_path = optarg;
168 grit_output_dir = optarg;
171 branding_strings_name = optarg;
177 fprintf(stderr, "ERROR: bad command line arg\n");
185 #define CHECK_ARG(a, b) \
188 fprintf(stderr, "ERROR: " b "\n"); \
194 CHECK_ARG(!version_file_path, "Missing VERSION file path");
195 CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
196 CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
197 CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
198 CHECK_ARG(argc == 0, "Missing language list");
199 CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
200 strcmp(app_type, kAppType_Helper) != 0),
203 char* const* lang_list = argv;
204 int lang_list_count = argc;
206 // Parse the version file and build our string
207 NSString* version_string = ApplicationVersionString(version_file_path);
208 if (!version_string) {
209 fprintf(stderr, "ERROR: failed to get a version string");
213 NSFileManager* fm = [NSFileManager defaultManager];
215 for (int loop = 0; loop < lang_list_count; ++loop) {
216 const char* cur_lang = lang_list[loop];
218 // Open the branded string pak file
219 scoped_ptr<ui::DataPack> branded_data_pack(
220 LoadResourceDataPack(grit_output_dir,
221 branding_strings_name,
223 if (branded_data_pack.get() == NULL) {
224 fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
229 uint32_t name_id = IDS_PRODUCT_NAME;
230 const char* name_id_str = "IDS_PRODUCT_NAME";
231 uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
232 const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
233 if (strcmp(app_type, kAppType_Helper) == 0) {
234 name_id = IDS_HELPER_NAME;
235 name_id_str = "IDS_HELPER_NAME";
236 short_name_id = IDS_SHORT_HELPER_NAME;
237 short_name_id_str = "IDS_SHORT_HELPER_NAME";
242 LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
243 name_id, name_id_str);
244 NSString* short_name =
245 LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
246 short_name_id, short_name_id_str);
247 NSString* copyright =
248 LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
249 IDS_ABOUT_VERSION_COPYRIGHT,
250 "IDS_ABOUT_VERSION_COPYRIGHT");
252 // For now, assume this is ok for all languages. If we need to, this could
253 // be moved into generated_resources.grd and fetched.
254 NSString *get_info = [NSString stringWithFormat:@"%@ %@, %@",
255 name, version_string, copyright];
257 // Generate the InfoPlist.strings file contents
258 NSString* strings_file_contents_string =
259 [NSString stringWithFormat:
260 @"CFBundleDisplayName = \"%@\";\n"
261 @"CFBundleGetInfoString = \"%@\";\n"
262 @"CFBundleName = \"%@\";\n"
263 @"NSHumanReadableCopyright = \"%@\";\n",
264 EscapeForStringsFileValue(name),
265 EscapeForStringsFileValue(get_info),
266 EscapeForStringsFileValue(short_name),
267 EscapeForStringsFileValue(copyright)];
269 // We set up Xcode projects expecting strings files to be UTF8, so make
270 // sure we write the data in that form. When Xcode copies them it will
271 // put them final runtime encoding.
272 NSData* strings_file_contents_utf8 =
273 [strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
275 if ([strings_file_contents_utf8 length] == 0) {
276 fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
277 "file for language: %s\n", cur_lang);
281 // For Cocoa to find the locale at runtime, it needs to use '_' instead of
282 // '-' (http://crbug.com/20441). Also, 'en-US' should be represented
283 // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
284 NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
285 if ([cur_lang_ns isEqualToString:@"en-US"]) {
288 cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
290 // Make sure the lproj we write to exists
291 NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
292 NSString *output_path =
293 [[NSString stringWithUTF8String:output_dir]
294 stringByAppendingPathComponent:lproj_name];
295 NSError* error = nil;
296 if (![fm fileExistsAtPath:output_path] &&
297 ![fm createDirectoryAtPath:output_path
298 withIntermediateDirectories:YES
301 fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
302 [output_path UTF8String]);
306 // Write out the file
308 [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
309 if (![strings_file_contents_utf8 writeToFile:output_path
311 fprintf(stderr, "ERROR: Failed to write out '%s'\n",
312 [output_path UTF8String]);