diff options
-rwxr-xr-x | build/mac/tweak_app_infoplist | 14 | ||||
-rw-r--r-- | chrome/app/app-Info.plist | 50 | ||||
-rw-r--r-- | chrome/app/breakpad_mac.mm | 3 | ||||
-rw-r--r-- | chrome/app/chrome_dll_main.cc | 15 | ||||
-rw-r--r-- | chrome/app/chromium_strings.grd | 11 | ||||
-rw-r--r-- | chrome/app/google_chrome_strings.grd | 11 | ||||
-rw-r--r-- | chrome/app/helper-Info.plist | 6 | ||||
-rwxr-xr-x | chrome/app/make_mac_app_symlinks | 9 | ||||
-rwxr-xr-x | chrome/app/nuke_mac_resources_link | 19 | ||||
-rwxr-xr-x | chrome/app/tweak_mac_lproj_folders | 11 | ||||
-rw-r--r-- | chrome/chrome.gyp | 122 | ||||
-rw-r--r-- | chrome/common/chrome_paths_internal.h | 9 | ||||
-rw-r--r-- | chrome/common/chrome_paths_mac.mm | 27 | ||||
-rw-r--r-- | chrome/tools/build/apply_locales.py | 27 | ||||
-rw-r--r-- | chrome/tools/mac_helpers/infoplist_strings_util.mm | 296 |
15 files changed, 580 insertions, 50 deletions
diff --git a/build/mac/tweak_app_infoplist b/build/mac/tweak_app_infoplist index 6af78e4..8110423 100755 --- a/build/mac/tweak_app_infoplist +++ b/build/mac/tweak_app_infoplist @@ -62,11 +62,9 @@ fi TOP="${SRCROOT}/.." BUILD_BRANDING=$1 -BRAND_SCRIPT="${TOP}/build/branding_value.sh" set -x -APP_NAME=$("${BRAND_SCRIPT}" "${BUILD_BRANDING}" PRODUCT_FULLNAME) SRC_APP_PATH="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}" if [ "${USE_SVN}" = "1" ] ; then @@ -103,14 +101,6 @@ fi . "${TOP}/chrome/VERSION" FULL_VERSION="${MAJOR}.${MINOR}.${BUILD}.${PATCH}" -# Fetch the copyright. -COPYRIGHT_STRING=$("${BRAND_SCRIPT}" "${BUILD_BRANDING}" COPYRIGHT) -# Map (c) or (C) to the copyright symbol. -COPYRIGHT_STRING=$(echo "${COPYRIGHT_STRING}" | sed -e $'s/([cC])/\302\251/g') - -# Build the full copyright string -LONG_COPYRIGHT="${APP_NAME} ${FULL_VERSION}, ${COPYRIGHT_STRING}" - # I really hate how "defaults" doesn't take a real pathname but instead insists # on appending ".plist" to everything. INFO_PLIST_PATH="Contents/Info.plist" @@ -134,8 +124,6 @@ fi # Add public version info so "Get Info" works defaults write "${TMP_INFO_PLIST_DEFAULTS}" \ - CFBundleGetInfoString -string "${LONG_COPYRIGHT}" -defaults write "${TMP_INFO_PLIST_DEFAULTS}" \ CFBundleShortVersionString -string "${FULL_VERSION}" # Honor the 429496.72.95 limit. The maximum comes from splitting 2^32 - 1 into # 6, 2, 2 digits. The limitation was present in Tiger, but it could have been @@ -146,8 +134,6 @@ defaults write "${TMP_INFO_PLIST_DEFAULTS}" \ # unique that meetings what LS wants. defaults write "${TMP_INFO_PLIST_DEFAULTS}" \ CFBundleVersion -string "${BUILD}.${PATCH}" -defaults write "${TMP_INFO_PLIST_DEFAULTS}" \ - NSHumanReadableCopyright -string "${COPYRIGHT_STRING}" # Add or remove the Breakpad keys. if [ "${USE_BREAKPAD}" = "1" ] ; then diff --git a/chrome/app/app-Info.plist b/chrome/app/app-Info.plist index 1341cdc..1e65fda 100644 --- a/chrome/app/app-Info.plist +++ b/chrome/app/app-Info.plist @@ -3,27 +3,9 @@ <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> - <string>English</string> - <key>UTExportedTypeDeclarations</key> - <array> - <dict> - <key>UTTypeIdentifier</key> - <string>org.chromium.extension</string> - <key>UTTypeDescription</key> - <string>Chromium Extra</string> - <key>UTTypeConformsTo</key> - <array> - <string>public.data</string> - </array> - <key>UTTypeTagSpecification</key> - <dict> - <key>public.filename-extension</key> - <array> - <string>crx</string> - </array> - </dict> - </dict> - </array> + <string>en-US</string> + <key>CFBundleDisplayName</key> + <string>${EXECUTABLE_NAME}</string> <key>CFBundleDocumentTypes</key> <array> <dict> @@ -134,12 +116,12 @@ <string>Viewer</string> </dict> <dict> + <key>CFBundleTypeRole</key> + <string>Viewer</string> <key>LSItemContentTypes</key> <array> <string>org.chromium.extension</string> </array> - <key>CFBundleTypeRole</key> - <string>Viewer</string> </dict> </array> <key>CFBundleExecutable</key> @@ -188,11 +170,33 @@ <string>0.1</string> <key>LSFileQuarantineEnabled</key> <true/> + <key>LSHasLocalizedDisplayName</key> + <string>1</string> <key>LSMinimumSystemVersion</key> <string>10.5.0</string> <key>NSMainNibFile</key> <string>MainMenu</string> <key>NSPrincipalClass</key> <string>NSApplication</string> + <key>UTExportedTypeDeclarations</key> + <array> + <dict> + <key>UTTypeConformsTo</key> + <array> + <string>public.data</string> + </array> + <key>UTTypeDescription</key> + <string>Chromium Extra</string> + <key>UTTypeIdentifier</key> + <string>org.chromium.extension</string> + <key>UTTypeTagSpecification</key> + <dict> + <key>public.filename-extension</key> + <array> + <string>crx</string> + </array> + </dict> + </dict> + </array> </dict> </plist> diff --git a/chrome/app/breakpad_mac.mm b/chrome/app/breakpad_mac.mm index 142a46e..6acb3f4 100644 --- a/chrome/app/breakpad_mac.mm +++ b/chrome/app/breakpad_mac.mm @@ -10,6 +10,7 @@ #import "base/basictypes.h" #include "base/command_line.h" #import "base/logging.h" +#include "base/mac_util.h" #import "base/scoped_nsautorelease_pool.h" #include "base/sys_string_conversions.h" #import "breakpad/src/client/mac/Framework/Breakpad.h" @@ -44,7 +45,7 @@ void InitCrashReporter() { // may not have access to the disk or to the same data as the browser // process, so the browser passes the consent preference to them on the // command line. - NSBundle* main_bundle = [NSBundle mainBundle]; + NSBundle* main_bundle = mac_util::MainAppBundle(); NSDictionary* info_dictionary = [main_bundle infoDictionary]; bool is_browser = [[info_dictionary objectForKey:@"LSUIElement"] isEqualToString:@"1"] ? false : true; diff --git a/chrome/app/chrome_dll_main.cc b/chrome/app/chrome_dll_main.cc index d2af7a9..64f5ce53 100644 --- a/chrome/app/chrome_dll_main.cc +++ b/chrome/app/chrome_dll_main.cc @@ -49,6 +49,8 @@ #include "base/win_util.h" #endif #if defined(OS_MACOSX) +#include "base/mac_util.h" +#include "chrome/common/chrome_paths_internal.h" #include "chrome/app/breakpad_mac.h" #endif #if defined(OS_LINUX) @@ -335,6 +337,17 @@ int ChromeMain(int argc, const char** argv) { #endif const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); + std::wstring process_type = + parsed_command_line.GetSwitchValue(switches::kProcessType); + +#if defined(OS_MACOSX) + // If process_type is not empty, this is the helper. Set the main app bundle + // so code can fetch Mac resources. + if (!process_type.empty()) { + FilePath main_path(chrome::GetBrowserBundlePath()); + mac_util::SetOverrideAppBundlePath(main_path); + } +#endif // OS_MACOSX #if defined(OS_WIN) // Must do this before any other usage of command line! @@ -373,8 +386,6 @@ int ChromeMain(int argc, const char** argv) { #endif // OS_POSIX int browser_pid; - std::wstring process_type = - parsed_command_line.GetSwitchValue(switches::kProcessType); if (process_type.empty()) { browser_pid = base::GetCurrentProcId(); } else { diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd index 2127282..b61d19a 100644 --- a/chrome/app/chromium_strings.grd +++ b/chrome/app/chromium_strings.grd @@ -349,6 +349,17 @@ be available for now. --> <message name="IDS_DEFAULT_BROWSER_INFOBAR_SHORT_TEXT" desc="More compact text to show in the default browser query infobar."> Chromium isn't your default browser. </message> + <if expr="os == 'darwin'"> + <message name="IDS_SHORT_PRODUCT_NAME" desc="The application's short name, used for the Mac's application menu, activity monitor, etc. This should be less than 16 characters. Example: Chrome, not Google Chrome."> + Chromium + </message> + <message name="IDS_HELPER_NAME" desc="The helper application's name. Should contain the Chrome application name (IDS_PRODUCT_NAME). Example: Google Chrome Helper."> + Chromium Helper + </message> + <message name="IDS_SHORT_HELPER_NAME" desc="The helper application's short name, used for the Mac's application menu, activity monitor, etc. Example: Chrome Helper, not Google Chrome Helper."> + Chromium Helper + </message> + </if> </messages> </release> </grit> diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd index bec59f6..5548e28 100644 --- a/chrome/app/google_chrome_strings.grd +++ b/chrome/app/google_chrome_strings.grd @@ -397,6 +397,17 @@ Chrome supports. --> <message name="IDS_DEFAULT_BROWSER_INFOBAR_SHORT_TEXT" desc="More compact text to show in the default browser query infobar."> Google Chrome isn't your default browser. </message> + <if expr="os == 'darwin'"> + <message name="IDS_SHORT_PRODUCT_NAME" desc="The application's short name, used for the Mac's application menu, activity monitor, etc. This should be less than 16 characters. Example: Chrome, not Google Chrome."> + Google Chrome + </message> + <message name="IDS_HELPER_NAME" desc="The helper application's name. Should contain the Chrome application name (IDS_PRODUCT_NAME). Example: Google Chrome Helper."> + Google Chrome Helper + </message> + <message name="IDS_SHORT_HELPER_NAME" desc="The helper application's short name, used for the Mac's application menu, activity monitor, etc. Example: Chrome Helper, not Google Chrome Helper."> + Chrome Helper + </message> + </if> </messages> </release> </grit> diff --git a/chrome/app/helper-Info.plist b/chrome/app/helper-Info.plist index 9aeacff..72f92b7 100644 --- a/chrome/app/helper-Info.plist +++ b/chrome/app/helper-Info.plist @@ -3,7 +3,9 @@ <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> - <string>English</string> + <string>en-US</string> + <key>CFBundleDisplayName</key> + <string>${EXECUTABLE_NAME}</string> <key>CFBundleExecutable</key> <string>${EXECUTABLE_NAME}</string> <key>CFBundleIdentifier</key> @@ -20,6 +22,8 @@ <string>0.1</string> <key>LSFileQuarantineEnabled</key> <true/> + <key>LSHasLocalizedDisplayName</key> + <string>1</string> <key>LSMinimumSystemVersion</key> <string>10.5.0</string> <key>LSUIElement</key> diff --git a/chrome/app/make_mac_app_symlinks b/chrome/app/make_mac_app_symlinks index 5f3c040..66353a95 100755 --- a/chrome/app/make_mac_app_symlinks +++ b/chrome/app/make_mac_app_symlinks @@ -5,14 +5,13 @@ # found in the LICENSE file. # This script is intended to run from a "postbuild" action from within Xcode. -# It sets up symbolic links for an app bundle's Resources and Frameworks -# directories. This is intended to be used for app bundles that live within -# the Resources directory of a larger app bundle, when the sub-app's -# Resources and Frameworks directories should point to the enclosing app's. +# It sets up symbolic links for an app bundle's Frameworks directory. This is +# intended to be used for app bundles that live within the Resources directory +# of a larger app bundle, when the sub-app's Frameworks directory should point +# to the enclosing app's. set -e CONTENTS_PATH="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/Contents" -ln -fhs ../.. "${CONTENTS_PATH}/Resources" ln -fhs ../../../Frameworks "${CONTENTS_PATH}/Frameworks" diff --git a/chrome/app/nuke_mac_resources_link b/chrome/app/nuke_mac_resources_link new file mode 100755 index 0000000..98c5c75 --- /dev/null +++ b/chrome/app/nuke_mac_resources_link @@ -0,0 +1,19 @@ +#!/bin/sh + +# Copyright (c) 2009 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. + +# TODO: Remove this script and the action that invokes it after 09/01/09 + +# The helper's Resources directory used to be a link, but now that we need +# real resources, we have this script deletes the link as a stop gap for the +# build bots and developers' builds. + +set -e + +RESOURCES_PATH="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/Contents/Resources" + +if [ -L "${RESOURCES_PATH}" ] ; then + rm -f "${RESOURCES_PATH}" +fi diff --git a/chrome/app/tweak_mac_lproj_folders b/chrome/app/tweak_mac_lproj_folders index d15c858..78dba5b 100755 --- a/chrome/app/tweak_mac_lproj_folders +++ b/chrome/app/tweak_mac_lproj_folders @@ -19,4 +19,15 @@ cd "${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/Contents/Resources" rm -rf "zh.lproj" "en.lproj" # Provide an link for en.lproj that points to en-US.lproj. + +# The standard OS language list includes English (en) and not a specific +# "flavor" such as U.S. English (en-US). When checking for a language match, +# the OS will remove subtags if no exact match is found, so that en will be +# used if the user's preferred language list only contains British English +# (en-GB) and no specific en-GB translation is available. +# +# To ensure that users with the default language list, which contains English +# without any regional subtag, as well as users with a specific variant of +# English with a regional subtag, will have access to an English-localized +# application, a symbolic link from en.lproj to en-US.lproj is provided. ln -fhs "en-US.lproj" "en.lproj" diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 69d534c..1b59fd8 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -3144,6 +3144,7 @@ # chrome/app/app-Info.plist has: # CFBundleIdentifier of CHROMIUM_BUNDLE_ID # CFBundleName of CHROMIUM_SHORT_NAME + # CFBundleSignature of CHROMIUM_CREATOR # Xcode then replaces these values with the branded values we set # as settings on the target. 'CHROMIUM_BUNDLE_ID': '<(mac_bundle_id)', @@ -3155,6 +3156,7 @@ ], 'dependencies': [ 'helper_app', + 'infoplist_strings_tool', # Bring in pdfsqueeze and run it on all pdfs '../build/temp_gyp/pdfsqueeze.gyp:pdfsqueeze', ], @@ -3172,6 +3174,51 @@ 'message': 'Running pdfsqueeze on <(RULE_INPUT_PATH)', }, ], + 'actions': [ + { + # Generate the InfoPlist.strings file + 'action_name': 'Generating InfoPlist.strings files', + 'variables': { + 'tool_path': '<(PRODUCT_DIR)/infoplist_strings_tool', + 'version_file_path': 'VERSION', + # Unique dir to write to so the [lang].lproj/InfoPlist.strings + # for the main app and the helper app don't name collide. + 'output_path': '<(INTERMEDIATE_DIR)/app_infoplist_strings', + }, + 'conditions': [ + [ 'branding == "Chrome"', { + 'variables': { + 'branding_name': 'google_chrome_strings', + }, + }, { # else branding!="Chrome" + 'variables': { + 'branding_name': 'chromium_strings', + }, + }], + ], + 'inputs': [ + '<(tool_path)', + '<(version_file_path)', + # TODO: remove this helper when we have loops in GYP + '>!@(tools/build/apply_locales.py \'<(grit_out_dir)/<(branding_name)_ZZLOCALE.pak\' <(locales))', + ], + 'outputs': [ + # TODO: remove this helper when we have loops in GYP + '>!@(tools/build/apply_locales.py \'<(output_path)/ZZLOCALE.lproj/InfoPlist.strings\' <(locales))', + ], + 'action': [ + '<(tool_path)', + '-b', '<(branding_name)', + '-v', '<(version_file_path)', + '-g', '<(grit_out_dir)', + '-o', '<(output_path)', + '-t', 'main', + '<@(locales)', + ], + 'message': 'Generating the language InfoPlist.strings files', + 'process_outputs_as_mac_bundle_resources': 1, + }, + ], 'copies': [ { 'destination': '<(PRODUCT_DIR)/<(mac_product_name).app/Contents/Frameworks', @@ -4692,6 +4739,7 @@ 'dependencies': [ 'chrome_dll', 'interpose_dependency_shim', + 'infoplist_strings_tool', ], 'sources': [ # chrome_exe_main.mm's main() is the entry point for the "chrome" @@ -4729,6 +4777,58 @@ ], }, ], + 'actions': [ + { + # TODO: remove this action and the script it runs after 09/01/09 + 'action_name': 'Remove old resources symlink', + 'inputs': [], + 'outputs': [], + 'action': [ 'app/nuke_mac_resources_link' ], + }, + { + # Generate the InfoPlist.strings file + 'action_name': 'Generating InfoPlist.strings files', + 'variables': { + 'tool_path': '<(PRODUCT_DIR)/infoplist_strings_tool', + 'version_file_path': 'VERSION', + # Unique dir to write to so the [lang].lproj/InfoPlist.strings + # for the main app and the helper app don't name collide. + 'output_path': '<(INTERMEDIATE_DIR)/helper_infoplist_strings', + }, + 'conditions': [ + [ 'branding == "Chrome"', { + 'variables': { + 'branding_name': 'google_chrome_strings', + }, + }, { # else branding!="Chrome" + 'variables': { + 'branding_name': 'chromium_strings', + }, + }], + ], + 'inputs': [ + '<(tool_path)', + '<(version_file_path)', + # TODO: remove this helper when we have loops in GYP + '>!@(tools/build/apply_locales.py \'<(grit_out_dir)/<(branding_name)_ZZLOCALE.pak\' <(locales))', + ], + 'outputs': [ + # TODO: remove this helper when we have loops in GYP + '>!@(tools/build/apply_locales.py \'<(output_path)/ZZLOCALE.lproj/InfoPlist.strings\' <(locales))', + ], + 'action': [ + '<(tool_path)', + '-b', '<(branding_name)', + '-v', '<(version_file_path)', + '-g', '<(grit_out_dir)', + '-o', '<(output_path)', + '-t', 'helper', + '<@(locales)', + ], + 'message': 'Generating the language InfoPlist.strings files', + 'process_outputs_as_mac_bundle_resources': 1, + }, + ], 'postbuilds': [ { 'postbuild_name': 'Make Symbolic Links', @@ -4748,6 +4848,10 @@ '-s0', '<(branding)'], }, + { + 'postbuild_name': 'Tweak Mac lproj folders', + 'action': ['app/tweak_mac_lproj_folders'], + }, ], 'conditions': [ ['mac_breakpad==1', { @@ -4831,7 +4935,21 @@ 'DYLIB_INSTALL_NAME_BASE': '@executable_path', }, }, - ] + { + 'target_name': 'infoplist_strings_tool', + 'type': 'executable', + 'dependencies': [ + 'chrome_strings', + '../base/base.gyp:base', + ], + 'include_dirs': [ + '<(grit_out_dir)', + ], + 'sources': [ + 'tools/mac_helpers/infoplist_strings_util.mm', + ], + }, + ], # targets }, { # else: OS != "mac" 'targets': [ { @@ -5139,7 +5257,7 @@ }], ], }, - ] + ], }], ['OS=="win"', { 'targets': [ diff --git a/chrome/common/chrome_paths_internal.h b/chrome/common/chrome_paths_internal.h index f1f0fa9..54b9870 100644 --- a/chrome/common/chrome_paths_internal.h +++ b/chrome/common/chrome_paths_internal.h @@ -7,7 +7,7 @@ #include "build/build_config.h" -class FilePath; +#include "base/file_path.h" namespace chrome { @@ -29,6 +29,13 @@ bool GetUserDownloadsDirectory(FilePath* result); // The path to the user's desktop. bool GetUserDesktop(FilePath* result); +#if defined(OS_MACOSX) +// Retrieves the browser bundle path. It is only valid to call this from a +// helper process, as it makes assumptions about the location of the enclosing +// bundle on disk. +FilePath GetBrowserBundlePath(); +#endif // defined(OS_MACOSX) + } // namespace chrome diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm index 618dd4f..82dbb3c 100644 --- a/chrome/common/chrome_paths_mac.mm +++ b/chrome/common/chrome_paths_mac.mm @@ -7,7 +7,6 @@ #import <Cocoa/Cocoa.h> #import "base/base_paths.h" -#import "base/file_path.h" #import "base/logging.h" #import "base/path_service.h" @@ -72,4 +71,30 @@ bool GetUserDesktop(FilePath* result) { return success; } +FilePath GetBrowserBundlePath() { + NSBundle* running_app_bundle = [NSBundle mainBundle]; + NSString* running_app_bundle_path = [running_app_bundle bundlePath]; + DCHECK(running_app_bundle_path) << "failed to get the main bundle path"; + + // Are we the helper or the browser (main bundle)? + if (![[[running_app_bundle infoDictionary] + objectForKey:@"LSUIElement"] boolValue]) { + // We aren't a LSUIElement, so this must be the browser, return it's path. + return FilePath([running_app_bundle_path fileSystemRepresentation]); + } + + // Helper lives at ...app/Contents/Resources/...Helper.app + NSArray* components = [running_app_bundle_path pathComponents]; + DCHECK_GE([components count], static_cast<NSUInteger>(4)) + << "too few path components for this bundle to be within another bundle"; + components = + [components subarrayWithRange:NSMakeRange(0, [components count] - 3)]; + + NSString* browser_path = [NSString pathWithComponents:components]; + DCHECK([[browser_path pathExtension] isEqualToString:@"app"]) + << "we weren't within another app?"; + + return FilePath([browser_path fileSystemRepresentation]); +} + } // namespace chrome diff --git a/chrome/tools/build/apply_locales.py b/chrome/tools/build/apply_locales.py new file mode 100644 index 0000000..43d064a --- /dev/null +++ b/chrome/tools/build/apply_locales.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# Copyright (c) 2009 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. + +# TODO: remove this script when GYP has for loops + +import sys + +def main(argv): + if len(argv) < 3: + print 'ERROR: need string and list of locales' + return 1 + + str_template = argv[1] + locales = argv[2:] + + results = [] + for locale in locales: + results.append(str_template.replace('ZZLOCALE', locale)) + + # Quote each element so filename spaces don't mess up GYP's attempt to parse + # it into a list. + print " ".join(['"%s"' % x for x in results]) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chrome/tools/mac_helpers/infoplist_strings_util.mm b/chrome/tools/mac_helpers/infoplist_strings_util.mm new file mode 100644 index 0000000..70fbca6 --- /dev/null +++ b/chrome/tools/mac_helpers/infoplist_strings_util.mm @@ -0,0 +1,296 @@ +// Copyright (c) 2009 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 <stdio.h> +#include <unistd.h> + +#include "base/data_pack.h" +#include "base/file_path.h" +#include "base/scoped_nsautorelease_pool.h" +#include "base/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "grit/chromium_strings.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; +} + +base::DataPack* LoadResourceDataPack(const char* dir_path, + const char* branding_strings_name, + const char* locale_name) { + base::DataPack* resource_pack = NULL; + + NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak", + dir_path, branding_strings_name, locale_name]; + if (resource_path) { + FilePath resources_pak_path([resource_path fileSystemRepresentation]); + resource_pack = new base::DataPack; + bool success = resource_pack->Load(resources_pak_path); + if (!success) { + delete resource_pack; + resource_pack = NULL; + } + } + + return resource_pack; +} + +NSString* LoadStringFromDataPack(base::DataPack* data_pack, + const char* data_pack_lang, + uint32_t resource_id, + const char* resource_id_str) { + NSString* result = nil; + StringPiece data; + if (data_pack->Get(resource_id, &data)) { + // Data pack encodes strings as UTF16. + result = + [[[NSString alloc] initWithBytes:data.data() + length:data.length() + encoding:NSUTF16LittleEndianStringEncoding] + autorelease]; + } + 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::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; + + // 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 + scoped_ptr<base::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_SHORT_PRODUCT_NAME; + const char* short_name_id_str = "IDS_SHORT_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 = + LoadStringFromDataPack(branded_data_pack.get(), cur_lang, + IDS_ABOUT_VERSION_COPYRIGHT, + "IDS_ABOUT_VERSION_COPYRIGHT"); + + // 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" + @"NSHumanReadableCopyright = \"%@\";\n", + EscapeForStringsFileValue(name), + EscapeForStringsFileValue(get_info), + EscapeForStringsFileValue(short_name), + 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); + } + + // Make sure the lproj we write to exists + NSString *output_path = + [[NSString stringWithUTF8String:output_dir] + stringByAppendingPathComponent: + [NSString stringWithFormat:@"%s.lproj", cur_lang]]; + 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; +} |