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