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/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"
28 NSString* ApplicationVersionString(const char* version_file_path) {
30 NSString* path_string = [NSString stringWithUTF8String:version_file_path];
31 NSString* version_file =
32 [NSString stringWithContentsOfFile:path_string
33 encoding:NSUTF8StringEncoding
35 if (!version_file || error) {
36 fprintf(stderr, "Failed to load version file: %s\n",
37 [[error description] UTF8String]);
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];
54 fprintf(stderr, "Failed to parse version file\n");
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];
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);
79 NSString* LoadStringFromDataPack(ui::DataPack* data_pack,
80 const char* data_pack_lang,
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) {
89 [[[NSString alloc] initWithBytes:data.data()
91 encoding:NSUTF8StringEncoding]
93 } else if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
95 [[[NSString alloc] initWithBytes:data.data()
97 encoding:NSUTF16LittleEndianStringEncoding]
100 fprintf(stderr, "ERROR: requested string %s from binary data pack\n",
106 fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
107 resource_id_str, data_pack_lang);
113 // Escape quotes, newlines, etc so there are no errors when the strings file
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:@"\\"
124 options:NSLiteralSearch
125 range:NSMakeRange(0, [worker length])];
126 // Now the rest of them.
127 [worker replaceOccurrencesOfString:@"\n"
129 options:NSLiteralSearch
130 range:NSMakeRange(0, [worker length])];
131 [worker replaceOccurrencesOfString:@"\r"
133 options:NSLiteralSearch
134 range:NSMakeRange(0, [worker length])];
135 [worker replaceOccurrencesOfString:@"\t"
137 options:NSLiteralSearch
138 range:NSMakeRange(0, [worker length])];
139 [worker replaceOccurrencesOfString:@"\""
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
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;
164 while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
170 version_file_path = optarg;
173 grit_output_dir = optarg;
176 branding_strings_name = optarg;
182 fprintf(stderr, "ERROR: bad command line arg\n");
190 #define CHECK_ARG(a, b) \
193 fprintf(stderr, "ERROR: " b "\n"); \
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),
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");
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,
228 if (branded_data_pack.get() == NULL) {
229 fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
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";
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),
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);
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"]) {
308 cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
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
321 fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
322 [output_path UTF8String]);
326 // Write out the file
328 [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
329 if (![strings_file_contents_utf8 writeToFile:output_path
331 fprintf(stderr, "ERROR: Failed to write out '%s'\n",
332 [output_path UTF8String]);