blob: 6558aecf410e5ee5184d9bd02a5f4dc58cb03322 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper tool that is built and run during a build to pull strings from
// the GRD files and generate the InfoPlist.strings files needed for
// Mac OS X app bundles.
#import <Foundation/Foundation.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <memory>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/icu_util.h"
#include "base/i18n/message_formatter.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/time/time.h"
#include "chrome/grit/chromium_strings.h"
#include "ui/base/resource/data_pack.h"
namespace {
NSString* ApplicationVersionString(const char* version_file_path) {
NSError* error = nil;
NSString* path_string = [NSString stringWithUTF8String:version_file_path];
NSString* version_file =
[NSString stringWithContentsOfFile:path_string
encoding:NSUTF8StringEncoding
error:&error];
if (!version_file || error) {
fprintf(stderr, "Failed to load version file: %s\n",
[[error description] UTF8String]);
return nil;
}
int major = 0, minor = 0, build = 0, patch = 0;
NSScanner* scanner = [NSScanner scannerWithString:version_file];
if ([scanner scanString:@"MAJOR=" intoString:nil] &&
[scanner scanInt:&major] &&
[scanner scanString:@"MINOR=" intoString:nil] &&
[scanner scanInt:&minor] &&
[scanner scanString:@"BUILD=" intoString:nil] &&
[scanner scanInt:&build] &&
[scanner scanString:@"PATCH=" intoString:nil] &&
[scanner scanInt:&patch]) {
return [NSString stringWithFormat:@"%d.%d.%d.%d",
major, minor, build, patch];
}
fprintf(stderr, "Failed to parse version file\n");
return nil;
}
ui::DataPack* LoadResourceDataPack(const char* dir_path,
const char* branding_strings_name,
const char* locale_name) {
ui::DataPack* resource_pack = NULL;
NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak",
dir_path, branding_strings_name, locale_name];
if (resource_path) {
base::FilePath resources_pak_path([resource_path fileSystemRepresentation]);
resources_pak_path = base::MakeAbsoluteFilePath(resources_pak_path);
resource_pack = new ui::DataPack(ui::SCALE_FACTOR_100P);
bool success = resource_pack->LoadFromPath(resources_pak_path);
if (!success) {
delete resource_pack;
resource_pack = NULL;
}
}
return resource_pack;
}
NSString* LoadStringFromDataPack(ui::DataPack* data_pack,
const char* data_pack_lang,
uint32_t resource_id,
const char* resource_id_str) {
NSString* result = nil;
base::StringPiece data;
if (data_pack->GetStringPiece(resource_id, &data)) {
// Data pack encodes strings as either UTF8 or UTF16.
if (data_pack->GetTextEncodingType() == ui::DataPack::UTF8) {
result =
[[[NSString alloc] initWithBytes:data.data()
length:data.length()
encoding:NSUTF8StringEncoding]
autorelease];
} else if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
result =
[[[NSString alloc] initWithBytes:data.data()
length:data.length()
encoding:NSUTF16LittleEndianStringEncoding]
autorelease];
} else {
fprintf(stderr, "ERROR: requested string %s from binary data pack\n",
resource_id_str);
exit(1);
}
}
if (!result) {
fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
resource_id_str, data_pack_lang);
exit(1);
}
return result;
}
// Escape quotes, newlines, etc so there are no errors when the strings file
// is parsed.
NSString* EscapeForStringsFileValue(NSString* str) {
NSMutableString* worker = [NSMutableString stringWithString:str];
// Since this is a build tool, we don't really worry about making this
// the most efficient code.
// Backslash first since we need to do it before we put in all the others
[worker replaceOccurrencesOfString:@"\\"
withString:@"\\\\"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
// Now the rest of them.
[worker replaceOccurrencesOfString:@"\n"
withString:@"\\n"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\r"
withString:@"\\r"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\t"
withString:@"\\t"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\""
withString:@"\\\""
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
return [[worker copy] autorelease];
}
// The valid types for the -t arg
const char kAppType_Main[] = "main"; // Main app
const char kAppType_Helper[] = "helper"; // Helper app
} // namespace
int main(int argc, char* const argv[]) {
base::mac::ScopedNSAutoreleasePool autorelease_pool;
const char* version_file_path = NULL;
const char* grit_output_dir = NULL;
const char* branding_strings_name = NULL;
const char* output_dir = NULL;
const char* app_type = kAppType_Main;
// Process the args
int ch;
while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
switch (ch) {
case 't':
app_type = optarg;
break;
case 'v':
version_file_path = optarg;
break;
case 'g':
grit_output_dir = optarg;
break;
case 'b':
branding_strings_name = optarg;
break;
case 'o':
output_dir = optarg;
break;
default:
fprintf(stderr, "ERROR: bad command line arg\n");
exit(1);
break;
}
}
argc -= optind;
argv += optind;
#define CHECK_ARG(a, b) \
do { \
if ((a)) { \
fprintf(stderr, "ERROR: " b "\n"); \
exit(1); \
} \
} while (false)
// Check our args
CHECK_ARG(!version_file_path, "Missing VERSION file path");
CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
CHECK_ARG(argc == 0, "Missing language list");
CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
strcmp(app_type, kAppType_Helper) != 0),
"Unknown app type");
char* const* lang_list = argv;
int lang_list_count = argc;
base::i18n::InitializeICU();
// Parse the version file and build our string
NSString* version_string = ApplicationVersionString(version_file_path);
if (!version_string) {
fprintf(stderr, "ERROR: failed to get a version string");
exit(1);
}
NSFileManager* fm = [NSFileManager defaultManager];
for (int loop = 0; loop < lang_list_count; ++loop) {
const char* cur_lang = lang_list[loop];
// Open the branded string pak file
std::unique_ptr<ui::DataPack> branded_data_pack(
LoadResourceDataPack(grit_output_dir, branding_strings_name, cur_lang));
if (branded_data_pack.get() == NULL) {
fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
cur_lang);
exit(1);
}
uint32_t name_id = IDS_PRODUCT_NAME;
const char* name_id_str = "IDS_PRODUCT_NAME";
uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
if (strcmp(app_type, kAppType_Helper) == 0) {
name_id = IDS_HELPER_NAME;
name_id_str = "IDS_HELPER_NAME";
short_name_id = IDS_SHORT_HELPER_NAME;
short_name_id_str = "IDS_SHORT_HELPER_NAME";
}
// Fetch the strings
NSString* name =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
name_id, name_id_str);
NSString* short_name =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
short_name_id, short_name_id_str);
NSString* copyright_format =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
IDS_ABOUT_VERSION_COPYRIGHT,
"IDS_ABOUT_VERSION_COPYRIGHT");
NSString* address_book_prompt_description =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION,
"IDS_AUTOFILL_ADDRESS_BOOK_PROMPT_DESCRIPTION");
NSString* copyright = base::SysUTF16ToNSString(
base::i18n::MessageFormatter::FormatWithNumberedArgs(
base::SysNSStringToUTF16(copyright_format), base::Time::Now()));
// For now, assume this is ok for all languages. If we need to, this could
// be moved into generated_resources.grd and fetched.
NSString *get_info = [NSString stringWithFormat:@"%@ %@, %@",
name, version_string, copyright];
// Generate the InfoPlist.strings file contents
NSString* strings_file_contents_string =
[NSString stringWithFormat:
@"CFBundleDisplayName = \"%@\";\n"
@"CFBundleGetInfoString = \"%@\";\n"
@"CFBundleName = \"%@\";\n"
@"NSContactsUsageDescription = \"%@\";\n"
@"NSHumanReadableCopyright = \"%@\";\n",
EscapeForStringsFileValue(name),
EscapeForStringsFileValue(get_info),
EscapeForStringsFileValue(short_name),
EscapeForStringsFileValue(address_book_prompt_description),
EscapeForStringsFileValue(copyright)];
// We set up Xcode projects expecting strings files to be UTF8, so make
// sure we write the data in that form. When Xcode copies them it will
// put them final runtime encoding.
NSData* strings_file_contents_utf8 =
[strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
if ([strings_file_contents_utf8 length] == 0) {
fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
"file for language: %s\n", cur_lang);
exit(1);
}
// For Cocoa to find the locale at runtime, it needs to use '_' instead of
// '-' (http://crbug.com/20441). Also, 'en-US' should be represented
// simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
if ([cur_lang_ns isEqualToString:@"en-US"]) {
cur_lang_ns = @"en";
}
cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
withString:@"_"];
// Make sure the lproj we write to exists
NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
NSString *output_path =
[[NSString stringWithUTF8String:output_dir]
stringByAppendingPathComponent:lproj_name];
NSError* error = nil;
if (![fm fileExistsAtPath:output_path] &&
![fm createDirectoryAtPath:output_path
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
[output_path UTF8String]);
exit(1);
}
// Write out the file
output_path =
[output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
if (![strings_file_contents_utf8 writeToFile:output_path
atomically:YES]) {
fprintf(stderr, "ERROR: Failed to write out '%s'\n",
[output_path UTF8String]);
exit(1);
}
}
return 0;
}