| #!/usr/bin/python |
| # -*- coding: utf-8 -*- |
| # Copyright 2010-2014, Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Script building Mozc. |
| |
| Typical usage: |
| |
| % python build_mozc.py gyp |
| % python build_mozc.py build_tools -c Release |
| % python build_mozc.py build base/base.gyp:base |
| """ |
| |
| __author__ = "komatsu" |
| |
| import glob |
| import logging |
| import optparse |
| import os |
| import re |
| import sys |
| |
| from build_tools import mozc_version |
| from build_tools.mozc_version import GenerateVersionFile |
| from build_tools.test_tools import test_launcher |
| from build_tools.util import CheckFileOrDie |
| from build_tools.util import ColoredLoggingFilter |
| from build_tools.util import ColoredText |
| from build_tools.util import CopyFile |
| from build_tools.util import FindFileFromPath |
| from build_tools.util import GetNumberOfProcessors |
| from build_tools.util import GetRelPath |
| from build_tools.util import IsLinux |
| from build_tools.util import IsMac |
| from build_tools.util import IsWindows |
| from build_tools.util import PrintErrorAndExit |
| from build_tools.util import RemoveDirectoryRecursively |
| from build_tools.util import RemoveFile |
| from build_tools.util import RunOrDie |
| from build_tools.util import RunOrDieError |
| |
| if not IsWindows(): |
| # android_util depends on fcntl module which doesn't exist in Windows. |
| # pylint: disable=g-import-not-at-top |
| from build_tools.android_util import Emulator |
| |
| SRC_DIR = '.' |
| # We need to obtain the absolute path of this script before we |
| # call MoveToTopLevelSourceDirectory(), which may change the |
| # current directory. |
| # Note that if any import above has already changed the current |
| # directory, this code cannot work anymore. |
| ABS_SCRIPT_PATH = os.path.abspath(__file__) |
| |
| sys.path.append(SRC_DIR) |
| |
| |
| def GetMozcVersion(): |
| """Returns MozcVersion instance.""" |
| # TODO(matsuzakit): Caching might be better. |
| return mozc_version.MozcVersion('%s/mozc_version.txt' % SRC_DIR) |
| |
| |
| def GetBuildShortBaseName(options, target_platform): |
| """Returns the build base directory. |
| |
| Determination priority is; |
| 1. options.build_base |
| 2. target_platform (typically in mozc_version.txt) |
| 3. options.target_platform |
| |
| Args: |
| options: Options which might have build_base and/or target_platform |
| target_platform: Target platform (typically read from mozc_version.txt) |
| This can be None. |
| Returns: |
| Build base directory. |
| Raises: |
| RuntimeError: if target_platform is not supported. |
| """ |
| if options.build_base: |
| return options.build_base |
| # For some reason, xcodebuild does not accept absolute path names for |
| # the -project parameter. Convert the original_directory_name to a |
| # relative path from the build top level directory. |
| if target_platform is None: |
| target_platform = options.target_platform |
| build_base = '' |
| |
| generator = GetGeneratorName(options.ensure_value('gyp_generator', None)) |
| if target_platform == 'Windows': |
| if generator == 'ninja': |
| build_base = 'out_win' |
| else: |
| build_base = 'out_win_msvs' |
| elif target_platform == 'Mac': |
| build_base = 'out_mac' |
| elif target_platform == 'Linux': |
| build_base = 'out_linux' |
| elif target_platform == 'Android': |
| build_base = 'out_android' |
| elif target_platform == 'NaCl': |
| build_base = 'out_nacl' |
| else: |
| raise RuntimeError('Unsupported platform: %s' % target_platform) |
| |
| return build_base |
| |
| |
| def GetBuildBaseName(options, target_platform): |
| build_base = GetBuildShortBaseName(options, target_platform) |
| |
| # On Linux, seems there is no way to specify the build_base directory |
| # inside common.gypi |
| if IsWindows() or IsMac(): |
| build_base = os.path.join(GetTopLevelSourceDirectoryName(), build_base) |
| |
| return build_base |
| |
| |
| def GetGeneratorName(generator): |
| """Gets the generator name based on the platform.""" |
| if generator: |
| return generator |
| elif IsMac(): |
| return 'xcode' |
| else: |
| return 'ninja' |
| |
| |
| def GetPkgConfigCommand(): |
| """Get the pkg-config command name. |
| |
| Returns: |
| pkg-config config command name. |
| """ |
| return os.environ.get('PKG_CONFIG', 'pkg-config') |
| |
| |
| # TODO(team): Move this to build_tools/util.py |
| def PkgExists(*packages): |
| """Return if the specified package exists or not. |
| |
| Args: |
| *packages: packages to be examined. |
| |
| Returns: |
| True if the specified package exists. |
| """ |
| try: |
| command_line = [GetPkgConfigCommand(), '--exists'] |
| command_line.extend(packages) |
| RunOrDie(command_line) |
| return True |
| except RunOrDieError: |
| logging.info('%s failed', ' '.join(command_line)) |
| return False |
| |
| |
| def GetGypFileNames(options): |
| """Gets the list of gyp file names.""" |
| gyp_file_names = [] |
| exclude_top_dirs = [] |
| mozc_top_level_names = glob.glob('%s/*' % SRC_DIR) |
| |
| # Exclude gyp files for Android unless the target platform is Android so as |
| # not to generate unnecessary files under android directory when you are |
| # building Mozc for other platforms. |
| if options.target_platform != 'Android': |
| exclude_top_dirs.append('android') |
| |
| mozc_top_level_names = [x for x in mozc_top_level_names if |
| os.path.basename(x) not in exclude_top_dirs] |
| |
| for name in mozc_top_level_names: |
| gyp_file_names.extend(glob.glob(name + '/*.gyp')) |
| gyp_file_names.extend(glob.glob('%s/build_tools/*/*.gyp' % SRC_DIR)) |
| # Include subdirectories of data/test/session/scenario |
| gyp_file_names.extend(glob.glob('%s/data/test/session/scenario/*.gyp' % |
| SRC_DIR)) |
| gyp_file_names.extend(glob.glob('%s/data/test/session/scenario/*/*.gyp' % |
| SRC_DIR)) |
| # Include subdirectories of data_manager |
| gyp_file_names.extend(glob.glob('%s/data_manager/*/*.gyp' % SRC_DIR)) |
| # Include subdirectory of dictionary |
| gyp_file_names.extend(glob.glob('%s/dictionary/*/*.gyp' % SRC_DIR)) |
| # Include subdirectory of rewriter |
| gyp_file_names.extend(glob.glob('%s/rewriter/*/*.gyp' % SRC_DIR)) |
| # Include subdirectory of win32 and breakpad for Windows |
| if options.target_platform == 'Windows': |
| gyp_file_names.extend(glob.glob('%s/win32/*/*.gyp' % SRC_DIR)) |
| elif options.target_platform == 'Linux': |
| gyp_file_names.extend(glob.glob('%s/unix/*/*.gyp' % SRC_DIR)) |
| # Add ibus.gyp if ibus version is >=1.4.1. |
| if not PkgExists('ibus-1.0 >= 1.4.1'): |
| logging.info('removing ibus.gyp.') |
| gyp_file_names.remove('%s/unix/ibus/ibus.gyp' % SRC_DIR) |
| elif options.target_platform == 'NaCl': |
| # Add chrome NaCl Mozc gyp scripts. |
| gyp_file_names.append('%s/chrome/nacl/nacl_extension.gyp' % SRC_DIR) |
| gyp_file_names.sort() |
| return gyp_file_names |
| |
| |
| def GetTopLevelSourceDirectoryName(): |
| """Gets the top level source directory name.""" |
| script_file_directory_name = os.path.dirname(ABS_SCRIPT_PATH) |
| if SRC_DIR == '.': |
| return script_file_directory_name |
| num_components = len(SRC_DIR.split('/')) |
| return os.path.join(script_file_directory_name, *(['..'] * num_components)) |
| |
| def GetAdditionalThirdPartyDir(): |
| """Returns the addtitional third party dir.""" |
| additional_third_party_path = 'third_party' |
| return os.path.abspath(os.path.join(GetTopLevelSourceDirectoryName(), |
| additional_third_party_path)) |
| |
| def GetAndroidHome(options): |
| """Gets the root directory of the SDK from options, ANDROID_HOME or PATH.""" |
| |
| if options.android_home: |
| logging.info('Android home is set from ' |
| '--android_home option: %s', options.android_home) |
| return options.android_home |
| else: |
| # If --android_home is not set, find the SDK directory from |
| # ANDROID_HOME or PATH. |
| if os.environ.get('ANDROID_HOME'): |
| logging.info('Android home is set from ANDROID_HOME: %s', |
| os.environ['ANDROID_HOME']) |
| return os.environ['ANDROID_HOME'] |
| for command_name in ['android', # for ANDROID_HOME/tools |
| 'adb']: # for ANDROID_HOME/platform-tools |
| command_path = FindFileFromPath(command_name) |
| if command_path: |
| android_home = os.path.abspath(os.path.join( |
| os.path.dirname(command_path), '..')) |
| logging.info('Android home is set from PATH: %s', android_home) |
| return android_home |
| return None |
| |
| |
| def GetAndroidNdkHome(options): |
| if options.android_ndk_home: |
| logging.info('Android NDK home is set from ' |
| '--android_ndk_home option: %s', options.android_ndk_home) |
| return options.android_ndk_home |
| else: |
| command_path = FindFileFromPath('ndk-build') |
| if command_path: |
| android_ndk_home = os.path.abspath(os.path.dirname(command_path)) |
| logging.info('Android NDK home is set from PATH: %s', android_ndk_home) |
| return android_ndk_home |
| else: |
| return None |
| |
| |
| def MoveToTopLevelSourceDirectory(): |
| """Moves to the build top level directory.""" |
| os.chdir(GetTopLevelSourceDirectoryName()) |
| |
| |
| def GetBuildScriptDirectoryName(): |
| """Gets the directory name of this script.""" |
| return os.path.dirname(ABS_SCRIPT_PATH) |
| |
| |
| def AddCommonOptions(parser): |
| """Adds the options common among the commands.""" |
| parser.add_option('--build_base', dest='build_base', |
| help='Specifies the base directory of the built binaries.') |
| parser.add_option('--verbose', '-v', dest='verbose', |
| action='store_true', default=False, |
| help='show verbose message.') |
| return parser |
| |
| |
| def AddGeneratorOption(parser): |
| """Generator option is used by gyp and clean.""" |
| options = {'dest': 'gyp_generator', |
| 'help': 'Specifies the generator for GYP.'} |
| if IsWindows(): |
| options['default'] = 'ninja' |
| parser.add_option('--gyp_generator', **options) |
| return parser |
| |
| |
| def AddTargetPlatformOption(parser): |
| # Linux environment can build for Linux, Android and NaCl. |
| # --target_platform option enables this script to know which build |
| # should be done. If you want NaCl build, specify "NaCl". |
| # If you want Android build, specify "Android". |
| if IsLinux(): |
| default_target = 'Linux' |
| elif IsWindows(): |
| default_target = 'Windows' |
| elif IsMac(): |
| default_target = 'Mac' |
| parser.add_option('--target_platform', dest='target_platform', |
| default=default_target, |
| help=('Linux environment can build for Linux, Android and ' |
| 'NaCl. This option enables this script to know ' |
| 'which build should be done. ' |
| 'If you want Android build, specify "Android". ' |
| 'If you want NaCl build, specify "NaCl".')) |
| |
| |
| def GetDefaultWixPath(): |
| """Returns the default Wix directory..""" |
| abs_path = '' |
| return abs_path |
| |
| |
| def ParseGypOptions(args=None, values=None): |
| """Parses command line options for the gyp command.""" |
| parser = optparse.OptionParser(usage='Usage: %prog gyp [options]') |
| AddCommonOptions(parser) |
| AddGeneratorOption(parser) |
| default_branding = 'Mozc' |
| parser.add_option('--branding', dest='branding', default=default_branding, |
| help='Specifies the branding. [default: %default]') |
| parser.add_option('--gypdir', dest='gypdir', |
| help='Specifies the location of GYP to be used.') |
| parser.add_option('--noqt', action='store_true', dest='noqt', default=False) |
| parser.add_option('--qtdir', dest='qtdir', |
| default=os.getenv('QTDIR', None), |
| help='Qt base directory to be used.') |
| parser.add_option('--version_file', dest='version_file', |
| help='use the specified version template file', |
| default='mozc_version_template.txt') |
| AddTargetPlatformOption(parser) |
| |
| # Mac and Linux |
| warn_as_error_default = False |
| parser.add_option('--warn_as_error', action='store_true', |
| dest='warn_as_error', default=warn_as_error_default, |
| help='Treat compiler warning as error. This option is used ' |
| 'on Mac and Linux.') |
| |
| # Mac |
| parser.add_option('--mac_dir', dest='mac_dir', |
| help='A path to the root directory of third party ' |
| 'libraries for Mac build which will be passed to gyp ' |
| 'files.') |
| |
| # Linux |
| parser.add_option('--server_dir', dest='server_dir', |
| default='', |
| help='A path to the directory where the server executable' |
| 'is installed. This option is used only on Linux.') |
| |
| # Android |
| parser.add_option('--android_arch', dest='android_arch', |
| type='choice', |
| choices=('arm', 'x86', 'mips', 'arm64', 'x86_64', 'mips64'), |
| default='arm', |
| help='[Android build only] Android architecture ' |
| '(arm, x86, mips)') |
| parser.add_option('--android_stl', dest='android_stl', |
| type='choice', |
| choices=('stlport', 'gnustl', 'libcxx'), |
| default='stlport', |
| help='[Android build only] Standard C++ library') |
| parser.add_option('--android_compiler', dest='android_compiler', |
| type='choice', |
| choices=('gcc', 'clang'), |
| default='gcc', |
| help='[Android build only] Compiler') |
| parser.add_option('--android_application_id', dest='android_application_id', |
| default='org.mozc.android.inputmethod.japanese', |
| help='[Android build only] Android\'s application id' |
| ' (==package ID). ' |
| 'If set, On Android2.1 preference screen works ' |
| 'incorrectly and some Java test cases failes because of ' |
| 'test framework\'s update.') |
| parser.add_option('--android_home', dest='android_home', default=None, |
| help='[Android build only] A path to the Android SDK Home. ' |
| 'If not specified, automatically detected from ANDROID_HOME' |
| ' or PATH.') |
| parser.add_option('--android_ndk_home', dest='android_ndk_home', default=None, |
| help='[Android build only] A path to the Android NDK Home. ' |
| 'If not specified, automatically detected from PATH.') |
| |
| # NaCl |
| parser.add_option('--nacl_sdk_root', dest='nacl_sdk_root', default='', |
| help='A path to the root directory of Native Client SDK. ' |
| 'This is used when NaCl module build.') |
| |
| def AddFeatureOption(option_parser, feature_name, macro_name, |
| option_name): |
| """Defines options like '--enable_foober' and '--disable_foober'. |
| |
| This function defines options like --enable_foober and --disable_foobar |
| based on given parameters. |
| |
| Args: |
| option_parser: An option parser to which options should be added. |
| feature_name: A name of the feature. Will be used for option's help. |
| macro_name: A macro name which will be defined when this feature is |
| enabled. Will be used for option's help. |
| Note that the macro is not automatically set. |
| Do this by yourself (typically on common.gypi). |
| option_name: A base name of the option. If 'foobar' is specified, |
| --enable_foobar and --disable_foobar will be defined. |
| |
| Raises: |
| ValueError: An error occurred when any name parameter is empty. |
| """ |
| if not feature_name: |
| raise ValueError('"feature_name" should be specified') |
| if not option_name: |
| raise ValueError('"option_name" should be specified') |
| if not macro_name: |
| raise ValueError('"macro_name" should be specified') |
| params = {'feature_name': feature_name, |
| 'option_name': option_name, |
| 'macro_name': macro_name} |
| help_template = ('Intentionally enable or disable %(feature_name)s ' |
| 'feature with the %(macro_name)s macro defined in code. ' |
| '--enable_%(option_name)s enables it, and ' |
| '--disable_%(option_name)s disables it. If both options ' |
| 'are not set, enables the %(feature_name)s feature ' |
| 'according to the target environment and branding.') |
| option_parser.add_option('--enable_%(option_name)s' % params, |
| action='store_true', |
| dest=('enable_%(option_name)s' % params), |
| help=help_template % params) |
| option_parser.add_option('--disable_%(option_name)s' % params, |
| action='store_false', |
| dest=('enable_%(option_name)s' % params), |
| help=help_template % params) |
| |
| AddFeatureOption(parser, feature_name='cloud handwriting', |
| macro_name='ENABLE_CLOUD_HANDWRITING', |
| option_name='cloud_handwriting') |
| AddFeatureOption(parser, feature_name='http client', |
| macro_name='MOZC_ENABLE_HTTP_CLIENT', |
| option_name='http_client') |
| AddFeatureOption(parser, feature_name='mode_indicator', |
| macro_name='MOZC_ENABLE_MODE_INDICATOR', |
| option_name='mode_indicator') |
| |
| # TODO(yukawa): Remove this option when Zinnia can be built on Windows with |
| # enabling Unicode. |
| use_zinnia_default = True |
| if IsWindows(): |
| # Zinnia on Windows cannot be enabled because of compile error. |
| use_zinnia_default = False |
| parser.add_option('--use_zinnia', dest='use_zinnia', |
| default=use_zinnia_default, |
| help='Use Zinnia if specified.') |
| |
| if IsWindows(): |
| parser.add_option('--wix_dir', dest='wix_dir', |
| default=GetDefaultWixPath(), |
| help='A path to the binary directory of wix.') |
| |
| # For internal Windows builds, gyp is expected to generate solution files |
| # for Visual Studio 2010, which is a default compiler for Mozc. However, |
| # you can specify the target version explicitly via 'msvs_version' option |
| # as follows. |
| parser.add_option('--msvs_version', dest='msvs_version', default='2010', |
| help='Specifies the target MSVS version.') |
| |
| return parser.parse_args(args, values) |
| |
| |
| def ExpandMetaTarget(options, meta_target_name): |
| """Returns a list of build targets with expanding meta target name. |
| |
| If the specified name is 'package', returns a list of build targets for |
| building production package. |
| |
| If the specified name is not a meta target name, returns it as a list. |
| |
| Args: |
| options: option object. |
| meta_target_name: meta target name to be expanded. |
| |
| Returns: |
| A list of build targets with meta target names expanded. |
| """ |
| if meta_target_name != 'package': |
| return [meta_target_name] |
| |
| target_platform = GetMozcVersion().GetTargetPlatform() |
| |
| if target_platform == 'Android': |
| targets = [SRC_DIR + '/android/android.gyp:apk'] |
| elif target_platform == 'Linux': |
| targets = [SRC_DIR + '/server/server.gyp:mozc_server', |
| SRC_DIR + '/renderer/renderer.gyp:mozc_renderer', |
| SRC_DIR + '/gui/gui.gyp:mozc_tool'] |
| if PkgExists('ibus-1.0 >= 1.4.1'): |
| targets.append(SRC_DIR + '/unix/ibus/ibus.gyp:ibus_mozc') |
| elif target_platform == 'Mac': |
| targets = [SRC_DIR + '/mac/mac.gyp:DiskImage'] |
| elif target_platform == 'Windows': |
| targets = ['out_win/%s:mozc_win32_build32' % options.configuration] |
| build_dir = os.path.abspath(os.path.join( |
| GetBuildBaseName(options, target_platform), |
| '%sDynamic' % options.configuration)) |
| qtcore_dll = os.path.join(build_dir, 'QtCore4.dll') |
| qtcored_dll = os.path.join(build_dir, 'QtCored4.dll') |
| if os.path.exists(qtcore_dll) or os.path.exists(qtcored_dll): |
| # This means that Mozc is configured to use DLL versioin of Qt. |
| # Let's build Mozc with DLL version of C++ runtime for the compatibility. |
| targets += ['out_win/%sDynamic:mozc_win32_build32_dynamic' |
| % options.configuration] |
| targets.append('out_win/%s_x64:mozc_win32_build64' % options.configuration) |
| elif target_platform == 'NaCl': |
| targets = [SRC_DIR + '/chrome/nacl/nacl_extension.gyp:nacl_mozc'] |
| |
| return targets |
| |
| |
| def ParseBuildOptions(args=None, values=None): |
| """Parses command line options for the build command.""" |
| parser = optparse.OptionParser(usage='Usage: %prog build [options]') |
| AddCommonOptions(parser) |
| if IsLinux(): |
| default_build_concurrency = GetNumberOfProcessors() * 2 |
| parser.add_option('--jobs', '-j', dest='jobs', |
| default=('%d' % default_build_concurrency), |
| metavar='N', help='run build jobs in parallel') |
| parser.add_option('--configuration', '-c', dest='configuration', |
| default='Debug', help='specify the build configuration.') |
| |
| (options, args) = parser.parse_args(args, values) |
| |
| # Debug_Android and Release_Android have been deprecated. |
| # As migration process, throw exception with description. |
| # TODO(matsuzakit): Remove this block later. |
| if (options.configuration == 'Debug_Android' |
| or options.configuration == 'Release_Android'): |
| raise RuntimeError('%s has been deprecated. Use Debug or Release instead.' |
| % options.configuration) |
| |
| targets = [] |
| for arg in args: |
| targets.extend(ExpandMetaTarget(options, arg)) |
| return (options, targets) |
| |
| |
| def ParseRunTestsOptions(args=None, values=None): |
| """Parses command line options for the runtests command.""" |
| parser = optparse.OptionParser( |
| usage='Usage: %prog runtests [options] [test_targets] [-- build options]') |
| AddCommonOptions(parser) |
| if IsLinux(): |
| default_build_concurrency = GetNumberOfProcessors() * 2 |
| parser.add_option('--jobs', '-j', dest='jobs', |
| default=('%d' % default_build_concurrency), |
| metavar='N', help='run build jobs in parallel') |
| default_test_jobs = GetNumberOfProcessors() * 2 |
| parser.add_option('--test_jobs', dest='test_jobs', type='int', |
| default=default_test_jobs, |
| metavar='N', help='run test jobs in parallel') |
| parser.add_option('--test_size', dest='test_size', default='small') |
| parser.add_option('--configuration', '-c', dest='configuration', |
| default='Debug', help='specify the build configuration.') |
| parser.add_option('--android_home', dest='android_home', default=None, |
| help='[Android build only] A path to the Android SDK Home. ' |
| 'If not specified, automatically detected from ANDROID_HOME' |
| ' or PATH.') |
| parser.add_option('--android_device', dest='android_device', |
| help='[Android build only] specify which emulator/device ' |
| 'you test on. ' |
| 'If not specified emulators are launched and used.') |
| |
| return parser.parse_args(args, values) |
| |
| |
| def ParseCleanOptions(args=None, values=None): |
| """Parses command line options for the clean command.""" |
| parser = optparse.OptionParser( |
| usage='Usage: %prog clean [-- build options]') |
| AddCommonOptions(parser) |
| AddGeneratorOption(parser) |
| AddTargetPlatformOption(parser) |
| |
| return parser.parse_args(args, values) |
| |
| |
| |
| |
| def AddPythonPathToEnvironmentFilesForWindows(out_dir): |
| """Add PYTHONPATH to environment files for Ninja.""" |
| python_path_root = GetTopLevelSourceDirectoryName() |
| python_path = os.path.abspath(python_path_root) |
| original_python_paths = os.environ.get('PYTHONPATH', '') |
| if original_python_paths: |
| python_path = os.pathsep.join([original_python_paths, python_path]) |
| nul = chr(0) |
| for d in os.listdir(out_dir): |
| abs_dir = os.path.abspath(os.path.join(out_dir, d)) |
| with open(os.path.join(abs_dir, 'environment.x86'), 'rb') as x86_file: |
| x86_content = (x86_file.read()[:-1] + 'PYTHONPATH' + '=' + |
| python_path + nul + nul) |
| with open(os.path.join(abs_dir, 'environment.x86'), 'wb') as x86_file: |
| x86_file.write(x86_content) |
| with open(os.path.join(abs_dir, 'environment.x64'), 'rb') as x64_file: |
| x64_content = (x64_file.read()[:-1] + 'PYTHONPATH' + '=' + |
| python_path + nul + nul) |
| with open(os.path.join(abs_dir, 'environment.x64'), 'wb') as x64_file: |
| x64_file.write(x64_content) |
| |
| |
| def GypMain(options, unused_args, _): |
| """The main function for the 'gyp' command.""" |
| # Generate a version definition file. |
| logging.info('Generating version definition file...') |
| template_path = '%s/%s' % (SRC_DIR, options.version_file) |
| version_path = '%s/mozc_version.txt' % SRC_DIR |
| GenerateVersionFile(template_path, version_path, options.target_platform, |
| options.android_application_id, |
| options.android_arch) |
| version = GetMozcVersion() |
| target_platform = version.GetTargetPlatform() |
| logging.info('Version string is %s', version.GetVersionString()) |
| |
| # Back up the original 'PYTHONPATH' environment variable in case we change |
| # it in this function. |
| original_python_path = os.environ.get('PYTHONPATH', '') |
| |
| # 'gypdir' option allows distributors/packages to specify the location of |
| # the system-installed version of gyp to conform their packaging policy. |
| if options.gypdir: |
| # When distributors/packages choose to use system-installed gyp, it is |
| # their responsibility to make sure if 'gyp' command works well on |
| # their environment. |
| gyp_dir = os.path.abspath(options.gypdir) |
| gyp_command = [os.path.join(gyp_dir, 'gyp')] |
| else: |
| # Use third_party/gyp/gyp unless 'gypdir' option is specified. This is |
| # the default and official way to build Mozc. In this case, you need to |
| # consider the case where a Python package named 'gyp' has already been |
| # installed in the target machine. So we update PYTHONPATH so that |
| # third_party/gyp/gyp_main.py can find its own library modules (they are |
| # installed under 'third_party/gyp/pylib') rather than system-installed |
| # GYP modules. |
| gyp_dir = os.path.abspath(os.path.join( |
| GetTopLevelSourceDirectoryName(), 'third_party', 'gyp')) |
| gyp_main_file = os.path.join(gyp_dir, 'gyp_main.py') |
| # Make sure |gyp_main_file| exists. |
| if not os.path.exists(gyp_main_file): |
| message = ( |
| 'GYP does not exist at %s. Please run "gclient" to download GYP. ' |
| 'If you want to use system-installed GYP, use --gypdir option to ' |
| 'specify its location. e.g., ' |
| '"python build_mozc.py gyp --gypdir=/usr/bin"' % gyp_main_file) |
| PrintErrorAndExit(message) |
| gyp_command = [sys.executable, gyp_main_file] |
| os.environ['PYTHONPATH'] = os.pathsep.join( |
| [os.path.join(gyp_dir, 'pylib'), original_python_path]) |
| # Following script tests if 'import gyp' is now available and its |
| # location is expected one. By using this script, make sure |
| # 'import gyp' actually loads 'third_party/gyp/pylib/gyp'. |
| gyp_check_script = os.path.join( |
| GetBuildScriptDirectoryName(), 'build_tools', |
| 'ensure_gyp_module_path.py') |
| expected_gyp_module_path = os.path.join(gyp_dir, 'pylib', 'gyp') |
| RunOrDie([sys.executable, gyp_check_script, |
| '--expected=%s' % expected_gyp_module_path]) |
| |
| # Determine the generator name. |
| generator = GetGeneratorName(options.gyp_generator) |
| os.environ['GYP_GENERATORS'] = generator |
| logging.info('Build tool: %s', generator) |
| |
| # Get and show the list of .gyp file names. |
| gyp_file_names = GetGypFileNames(options) |
| logging.debug('GYP files:') |
| for file_name in gyp_file_names: |
| logging.debug('- %s', file_name) |
| |
| # Build GYP command line. |
| logging.info('Building GYP command line...') |
| gyp_options = ['--depth=.', '--include=%s/gyp/common.gypi' % SRC_DIR] |
| |
| |
| mozc_root = os.path.abspath(GetTopLevelSourceDirectoryName()) |
| gyp_options.extend(['-D', 'abs_depth=%s' % mozc_root]) |
| gyp_options.extend(['-D', ('additional_third_party_dir=%s' |
| % GetAdditionalThirdPartyDir())]) |
| |
| gyp_options.extend(['-D', 'python_executable=%s' % sys.executable]) |
| |
| gyp_options.extend(gyp_file_names) |
| |
| if options.branding: |
| gyp_options.extend(['-D', 'branding=%s' % options.branding]) |
| |
| if options.noqt or target_platform in ['Android', 'NaCl']: |
| gyp_options.extend(['-D', 'use_qt=NO']) |
| else: |
| if options.target_platform == 'Linux' and not options.qtdir: |
| # Check if Qt libraries are installed. |
| system_qt_found = PkgExists('QtCore >= 4.0', 'QtCore < 5.0', |
| 'QtGui >= 4.0', 'QtGui < 5.0') |
| if not system_qt_found: |
| PrintErrorAndExit('Qt4 is required to build GUI Tool. ' |
| 'Specify --noqt to skip building GUI Tool.') |
| gyp_options.extend(['-D', 'use_qt=YES']) |
| if options.qtdir: |
| gyp_options.extend(['-D', 'qt_dir=%s' % os.path.abspath(options.qtdir)]) |
| else: |
| gyp_options.extend(['-D', 'qt_dir=']) |
| |
| if options.target_platform == 'Windows' and options.wix_dir: |
| gyp_options.extend(['-D', 'use_wix=YES']) |
| gyp_options.extend(['-D', 'wix_dir=%s' % options.wix_dir]) |
| else: |
| gyp_options.extend(['-D', 'use_wix=NO']) |
| |
| android_home = GetAndroidHome(options) |
| android_ndk_home = GetAndroidNdkHome(options) |
| if target_platform == 'Android': |
| if not android_home or not os.path.isdir(android_home): |
| raise ValueError( |
| 'Android Home was not found. ' |
| 'Use --android_home option or make the home direcotry ' |
| 'be included in PATH environment variable.') |
| if not android_ndk_home or not os.path.isdir(android_ndk_home): |
| raise ValueError( |
| 'Android NDK Home was not found. ' |
| 'Use --android_ndk_home option or make the home direcotry ' |
| 'be included in PATH environment variable.') |
| |
| gyp_options.extend(['-D', 'android_home=%s' % android_home]) |
| gyp_options.extend(['-D', 'android_arch=%s' % options.android_arch]) |
| if options.android_compiler == 'clang' and options.android_stl != 'libcxx': |
| raise ValueError( |
| 'Only libc++ is supported with Clang. Use --android_stl=libcxx') |
| gyp_options.extend(['-D', 'android_stl=%s' % options.android_stl]) |
| gyp_options.extend(['-D', 'android_compiler=%s' % options.android_compiler]) |
| gyp_options.extend(['-D', 'android_ndk_home=%s' % android_ndk_home]) |
| gyp_options.extend(['-D', 'android_application_id=%s' % |
| options.android_application_id]) |
| gyp_options.extend(['-D', 'build_base=%s' % |
| GetBuildBaseName(options, target_platform)]) |
| gyp_options.extend(['-D', 'build_short_base=%s' % |
| GetBuildShortBaseName(options, target_platform)]) |
| |
| disable_unittest_if_not_available = True |
| if disable_unittest_if_not_available: |
| required_modules = ['gmock', 'gtest'] |
| for module in required_modules: |
| module_dir = os.path.abspath( |
| os.path.join(GetTopLevelSourceDirectoryName(), 'third_party', module)) |
| if not os.path.exists(module_dir): |
| logging.warning('%s not found. Disabling unittest.', module) |
| gyp_options.extend(['-D', 'enable_unittest=0']) |
| break |
| |
| if options.warn_as_error: |
| gyp_options.extend(['-D', 'warn_as_error=1']) |
| else: |
| gyp_options.extend(['-D', 'warn_as_error=0']) |
| |
| # mac_dir should be started with '<(DEPTH)', otherwise some |
| # operations in XCode fails. So if the mac_dir option is an |
| # absolute path, it will be changed to a relative path. |
| mac_dir = options.mac_dir or '../mac' |
| if os.path.isabs(mac_dir): |
| mac_dir = GetRelPath(mac_dir, GetTopLevelSourceDirectoryName()) |
| mac_dir = os.path.join('<(DEPTH)', mac_dir) |
| |
| gyp_options.extend(['-D', 'mac_dir=%s' % mac_dir]) |
| |
| if version.IsDevChannel(): |
| gyp_options.extend(['-D', 'channel_dev=1']) |
| |
| def SetCommandLineForFeature(option_name, windows=False, mac=False, |
| linux=False, android=False, nacl=False): |
| """Updates an option like '--enable_foober' and add a -D argument for gyp. |
| |
| This function ensures an option like '--enable_foober' exists and it has a |
| default boolean for each platform based on givem parameters. This |
| function also sets a '-D' command line option for gyp as |
| '-D enable_foober=0' or '-D enable_foober=1' depending on the actual value |
| of the target option. |
| |
| Args: |
| option_name: A base name of the option. If 'foobar' is given, |
| '--enable_foober' option will be checked. |
| windows: A boolean which replesents the default value of the target |
| option on Windows platform. |
| mac: A boolean which replesents the default value of the target option |
| on MacOS X platform. |
| linux: A boolean which replesents the default value of the target option |
| on Linux platform. |
| android: A boolean which replesents the default value of the target |
| option on Android platform. |
| nacl: A boolean which replesents the default value of the target |
| option on NaCl. |
| |
| Raises: |
| ValueError: An error occurred when 'option_name' is empty. |
| """ |
| if not option_name: |
| raise ValueError('"option_name" should be specified') |
| |
| default_enabled = False |
| default_enabled = {'Windows': windows, |
| 'Mac': mac, |
| 'Linux': linux, |
| 'Android': android, |
| 'NaCl': nacl}[options.target_platform] |
| enable_option_name = 'enable_%s' % option_name |
| enabled = options.ensure_value(enable_option_name, default_enabled) |
| gyp_options.extend(['-D', |
| '%s=%s' % (enable_option_name, 1 if enabled else 0)]) |
| |
| is_official = (options.branding == 'GoogleJapaneseInput') |
| is_official_dev = (is_official and version.IsDevChannel()) |
| |
| SetCommandLineForFeature(option_name='cloud_handwriting', |
| linux=is_official_dev, |
| windows=is_official_dev, |
| mac=is_official_dev) |
| SetCommandLineForFeature(option_name='http_client', |
| linux=is_official, |
| windows=is_official, |
| mac=is_official, |
| android=is_official, |
| # System dictionary is read with HttpClient in NaCl. |
| nacl=True) |
| SetCommandLineForFeature(option_name='mode_indicator', |
| windows=True) |
| |
| gyp_options.extend(['-D', 'target_platform=%s' % options.target_platform]) |
| |
| if IsWindows(): |
| gyp_options.extend(['-G', 'msvs_version=%s' % options.msvs_version]) |
| |
| # On Windows, we need to determine if <qtdir>/lib/Qt*.lib are import |
| # libraries for Qt DLLs or statically-linked libraries. Here we assume that |
| # Qt*.lib are import libraries when <qtdir>/lib/QtCore.prl contains |
| # 'QT_SHARED' in the definition of 'QMAKE_PRL_DEFINES'. |
| use_dynamically_linked_qt = False |
| if IsWindows() and not options.noqt and options.qtdir: |
| qtcore_prl_path = os.path.join( |
| os.path.abspath(options.qtdir), 'lib', 'QtCore.prl') |
| with open(qtcore_prl_path) as qtcore_prl_file: |
| for line in qtcore_prl_file: |
| if ('QMAKE_PRL_DEFINES' in line) and ('QT_SHARED' in line): |
| use_dynamically_linked_qt = True |
| break |
| |
| if use_dynamically_linked_qt: |
| gyp_options.extend(['-D', 'use_dynamically_linked_qt=1']) |
| else: |
| gyp_options.extend(['-D', 'use_dynamically_linked_qt=0']) |
| |
| if options.use_zinnia and target_platform not in ['Android', 'NaCl']: |
| gyp_options.extend(['-D', 'use_zinnia=YES']) |
| else: |
| gyp_options.extend(['-D', 'use_zinnia=NO']) |
| |
| if (options.target_platform == 'Linux' and |
| '%s/unix/ibus/ibus.gyp' % SRC_DIR in gyp_file_names): |
| gyp_options.extend(['-D', 'use_libibus=1']) |
| |
| |
| # Dictionary configuration |
| if target_platform == 'Android': |
| gyp_options.extend(['-D', 'dictionary=small']) |
| gyp_options.extend(['-D', 'use_separate_collocation_data=0']) |
| gyp_options.extend(['-D', 'use_separate_connection_data=1']) |
| gyp_options.extend(['-D', 'use_separate_dictionary=1']) |
| gyp_options.extend(['-D', 'use_1byte_cost_for_connection_data=1']) |
| gyp_options.extend(['-D', 'use_packed_dictionary=0']) |
| elif target_platform == 'NaCl': |
| gyp_options.extend(['-D', 'dictionary=desktop']) |
| gyp_options.extend(['-D', 'use_separate_collocation_data=0']) |
| gyp_options.extend(['-D', 'use_separate_connection_data=0']) |
| gyp_options.extend(['-D', 'use_separate_dictionary=0']) |
| gyp_options.extend(['-D', 'use_1byte_cost_for_connection_data=0']) |
| gyp_options.extend(['-D', 'use_packed_dictionary=1']) |
| else: |
| gyp_options.extend(['-D', 'dictionary=desktop']) |
| gyp_options.extend(['-D', 'use_separate_collocation_data=0']) |
| gyp_options.extend(['-D', 'use_separate_connection_data=0']) |
| gyp_options.extend(['-D', 'use_separate_dictionary=0']) |
| gyp_options.extend(['-D', 'use_1byte_cost_for_connection_data=0']) |
| gyp_options.extend(['-D', 'use_packed_dictionary=0']) |
| |
| # Specifying pkg-config command. Some environment (such like |
| # cross-platform ChromeOS build) requires us to call a different |
| # command for pkg-config. Here we catch the environment variable |
| # and use the specified command instead of actual pkg-config |
| # command. |
| if IsLinux(): |
| gyp_options.extend(['-D', 'pkg_config_command=%s' % GetPkgConfigCommand()]) |
| else: |
| gyp_options.extend(['-D', 'pkg_config_command=']) |
| |
| if target_platform == 'NaCl': |
| if options.nacl_sdk_root: |
| nacl_sdk_root = os.path.abspath(options.nacl_sdk_root) |
| else: |
| nacl_sdk_root = os.path.abspath(os.path.join(GetAdditionalThirdPartyDir(), |
| 'nacl_sdk', 'pepper_27')) |
| if not os.path.isdir(nacl_sdk_root): |
| PrintErrorAndExit('The nacl_sdk_root directory (%s) does not exist.' |
| % options.nacl_sdk_root) |
| gyp_options.extend(['-D', 'nacl_sdk_root=%s' % nacl_sdk_root]) |
| |
| if options.server_dir: |
| gyp_options.extend([ |
| '-D', 'server_dir=%s' % os.path.abspath(options.server_dir)]) |
| |
| # TODO(yukawa): Use ninja on OSX. |
| if generator == 'ninja': |
| gyp_options.extend(['--generator-output=.']) |
| short_basename = GetBuildShortBaseName(options, target_platform) |
| gyp_options.extend(['-G', 'output_dir=%s' % short_basename]) |
| |
| # Enable cross-compile |
| # TODO(yukawa): Enable GYP_CROSSCOMPILE for Windows. |
| if not IsWindows(): |
| os.environ['GYP_CROSSCOMPILE'] = '1' |
| |
| # Run GYP. |
| logging.info('Running GYP...') |
| RunOrDie(gyp_command + gyp_options) |
| |
| # Restore PYTHONPATH just in case. |
| if not original_python_path: |
| os.environ['PYTHONPATH'] = original_python_path |
| |
| # Done! |
| logging.info('Done') |
| |
| # For internal Ninja build on Windows, set up environment files |
| if IsWindows() and generator == 'ninja': |
| out_dir = os.path.join(GetTopLevelSourceDirectoryName(), 'out_win') |
| AddPythonPathToEnvironmentFilesForWindows(out_dir) |
| |
| # When Windows build is configured to use DLL version of Qt, copy Qt's DLLs |
| # and debug symbols into Mozc's build directory. This is important because: |
| # - We can easily back up all the artifacts if relevant product binaries and |
| # debug symbols are placed at the same place. |
| # - Some post-build tools such as bind.exe can easily look up the dependent |
| # DLLs (QtCore4.dll and QtQui4.dll in this case). |
| # Perhaps the following code can also be implemented in gyp, but we atopt |
| # this ad hock workaround as a first step. |
| # TODO(yukawa): Implement the following logic in gyp, if magically possible. |
| if use_dynamically_linked_qt: |
| abs_qtdir = os.path.abspath(options.qtdir) |
| abs_qt_bin_dir = os.path.join(abs_qtdir, 'bin') |
| abs_qt_lib_dir = os.path.join(abs_qtdir, 'lib') |
| abs_out_win_dir = GetBuildBaseName(options, target_platform) |
| copy_script = os.path.join( |
| GetBuildScriptDirectoryName(), 'build_tools', 'copy_dll_and_symbol.py') |
| copy_modes = [ |
| {'configuration': 'DebugDynamic', 'basenames': 'QtCored4;QtGuid4'}, |
| {'configuration': 'ReleaseDynamic', 'basenames': 'QtCore4;QtGui4'}] |
| for mode in copy_modes: |
| copy_commands = [ |
| copy_script, |
| '--basenames', mode['basenames'], |
| '--dll_paths', abs_qt_bin_dir, |
| '--pdb_paths', os.pathsep.join([abs_qt_bin_dir, abs_qt_lib_dir]), |
| '--target_dir', os.path.join(abs_out_win_dir, mode['configuration']), |
| ] |
| RunOrDie(copy_commands) |
| |
| |
| |
| |
| def BuildToolsMain(options, unused_args, original_directory_name): |
| """The main function for 'build_tools' command.""" |
| if not IsWindows(): |
| logging.info('build_tools is deprecated on this platform.') |
| return |
| |
| build_tools_dir = os.path.join(GetRelPath(os.getcwd(), |
| original_directory_name), |
| SRC_DIR, 'build_tools') |
| build_tools_targets = [ |
| 'out_win/%s:build_tools' % options.configuration, |
| ] |
| |
| for build_tools_target in build_tools_targets: |
| BuildMain(options, [build_tools_target], original_directory_name) |
| |
| |
| def CanonicalTargetToGypFileAndTargetName(target): |
| """Parses the target string.""" |
| if ':' not in target: |
| PrintErrorAndExit('Invalid target: %s' % target) |
| (gyp_file_name, target_name) = target.split(':') |
| return (gyp_file_name, target_name) |
| |
| |
| def BuildOnLinux(options, targets, unused_original_directory_name): |
| """Build the targets on Linux.""" |
| target_names = [] |
| for target in targets: |
| (unused_gyp_file_name, target_name) = ( |
| CanonicalTargetToGypFileAndTargetName(target)) |
| target_names.append(target_name) |
| |
| ninja = 'ninja' |
| |
| if hasattr(options, 'android_device'): |
| # Only for android testing. |
| os.environ['ANDROID_DEVICES'] = options.android_device |
| |
| short_basename = GetBuildShortBaseName(options, |
| GetMozcVersion().GetTargetPlatform()) |
| make_command = ninja |
| build_args = ['-j %s' % options.jobs, |
| '-C', '%s/%s' % (short_basename, options.configuration)] |
| RunOrDie([make_command] + build_args + target_names) |
| |
| |
| def BuildOnMac(options, targets, original_directory_name): |
| """Build the targets on Mac.""" |
| original_directory_relpath = GetRelPath(original_directory_name, os.getcwd()) |
| for target in targets: |
| (gyp_file_name, target_name) = CanonicalTargetToGypFileAndTargetName(target) |
| gyp_file_name = os.path.join(original_directory_relpath, gyp_file_name) |
| CheckFileOrDie(gyp_file_name) |
| (xcode_base_name, _) = os.path.splitext(gyp_file_name) |
| RunOrDie(['xcodebuild', |
| '-project', '%s.xcodeproj' % xcode_base_name, |
| '-configuration', options.configuration, |
| '-target', target_name, |
| '-parallelizeTargets', |
| 'BUILD_WITH_GYP=1']) |
| |
| |
| def BuildOnWindows(targets): |
| """Build the target on Windows.""" |
| ninja = 'ninja.exe' |
| |
| for target in targets: |
| tokens = target.split(':') |
| target_dir = tokens[0] |
| tokens.pop(0) |
| RunOrDie([ninja, '-C', target_dir] + tokens) |
| |
| |
| def BuildMain(options, targets, original_directory_name): |
| """The main function for the 'build' command.""" |
| if not targets: |
| PrintErrorAndExit('No build target is specified.') |
| |
| # Add the mozc root directory to PYTHONPATH. |
| original_python_path = os.environ.get('PYTHONPATH', '') |
| mozc_root = os.path.abspath(GetTopLevelSourceDirectoryName()) |
| python_path = os.pathsep.join([original_python_path, mozc_root]) |
| os.environ['PYTHONPATH'] = python_path |
| |
| if IsMac(): |
| BuildOnMac(options, targets, original_directory_name) |
| elif IsLinux(): |
| BuildOnLinux(options, targets, original_directory_name) |
| elif IsWindows(): |
| BuildOnWindows(targets) |
| else: |
| logging.error('Unsupported platform: %s', os.name) |
| return |
| |
| # Revert python path. |
| os.environ['PYTHONPATH'] = original_python_path |
| |
| |
| def RunTests(build_base, configuration, parallel_num): |
| """Run built tests actually. |
| |
| Args: |
| build_base: the base directory ('out_linux', 'out_mac', etc.) |
| configuration: build configuration ('Release' or 'Debug') |
| parallel_num: allows specified jobs at once. |
| |
| Raises: |
| RunOrDieError: One or more tests have failed. |
| """ |
| # TODO(nona): move this function to build_tools/test_tools |
| base_path = os.path.join(build_base, configuration) |
| |
| options = [] |
| |
| # Specify the log_dir directory. |
| # base_path looks like out_mac/Debug. |
| options.append('--log_dir=%s' % base_path) |
| |
| failed_tests = [] |
| # This is a silly algorithm: it runs *all* tests built in the target |
| # directory. Therefore, if you build multiple tests without |
| # cleaning, the second runtests runs every test. |
| # TODO(mukai): parses gyp files and get the target binaries, if possible. |
| executable_suffix = '' |
| if IsWindows(): |
| executable_suffix = '.exe' |
| test_binaries = glob.glob( |
| os.path.join(base_path, '*_test' + executable_suffix)) |
| |
| # Prepare gtest_report directory. |
| gtest_report_dir = os.path.abspath(os.path.join(base_path, 'gtest_report')) |
| if os.path.exists(gtest_report_dir): |
| # Clear existing gtest reports. |
| RemoveDirectoryRecursively(gtest_report_dir) |
| os.makedirs(gtest_report_dir) |
| |
| failed_tests = [] |
| |
| # Create default test reports in case any test process crashes and cannot |
| # leave test result as a XML report. |
| # TODO(yukawa): Move this template to test_tools/gtest_report.py. |
| xml_template = ( |
| '<?xml version="1.0" encoding="UTF-8"?>\n' |
| ' <testsuite name="%s" tests="1" errors="1">\n' |
| ' <testcase name="No reporting XML">\n' |
| ' <error message="No reporting XML has been generated. ' |
| 'Process crash?" />\n' |
| ' </testcase>\n' |
| '</testsuite>\n') |
| for binary in test_binaries: |
| binary_filename = os.path.basename(binary) |
| xml_path = os.path.join(gtest_report_dir, '%s.xml' % binary_filename) |
| with open(xml_path, 'w') as f: |
| f.write(xml_template % binary_filename) |
| |
| if parallel_num == 1: |
| for binary in test_binaries: |
| logging.info('running %s...', binary) |
| try: |
| binary_filename = os.path.basename(binary) |
| tmp_xml_path = os.path.join(gtest_report_dir, |
| '%s.xml.running' % binary_filename) |
| test_options = options[:] |
| test_options.extend(['--gunit_output=xml:%s' % tmp_xml_path, |
| '--gtest_output=xml:%s' % tmp_xml_path]) |
| RunOrDie([binary] + test_options) |
| xml_path = os.path.join(gtest_report_dir, '%s.xml' % binary_filename) |
| CopyFile(tmp_xml_path, xml_path) |
| RemoveFile(tmp_xml_path) |
| except RunOrDieError, e: |
| logging.error(e) |
| failed_tests.append(binary) |
| else: |
| launcher = test_launcher.TestLauncher(gtest_report_dir) |
| for binary in test_binaries: |
| launcher.AddTestCommand([binary] + options) |
| failed_tests = launcher.Execute(parallel_num) |
| if failed_tests: |
| error_text = ColoredText('following tests failed', logging.ERROR) |
| raise RunOrDieError('\n'.join([error_text] + failed_tests)) |
| |
| |
| def RunTestsOnAndroid(options, build_args, original_directory_name): |
| """Run a test suite for the Android version.""" |
| try: |
| emulators = [] |
| serialnumbers = [] |
| if options.android_device: |
| serialnumbers.append(options.android_device) |
| else: |
| # Temporary AVDs are created beneath android_sdk_home. |
| android_sdk_home = os.path.join( |
| GetBuildBaseName(options, GetMozcVersion().GetTargetPlatform()), |
| options.configuration, |
| 'android_sdk_home') |
| android_home = GetAndroidHome(options) |
| |
| android_arch = GetMozcVersion().GetAndroidArch() |
| if android_arch == 'arm': |
| avd_configs = [ |
| {'--name': 'Nexus5-ApiL-arm-WVGA800', |
| '--target': 'android-21', |
| '--abi': 'default/armeabi-v7a', |
| '--device': 'Nexus 5', |
| '--skin': 'WVGA800'}, |
| {'--name': 'Nexus10-ApiL-arm-WXGA800', |
| '--target': 'android-21', |
| '--abi': 'default/armeabi-v7a', |
| '--device': 'Nexus 10', |
| '--skin': 'WXGA800'},] |
| elif android_arch == 'x86': |
| avd_configs = [ |
| {'--name': 'Nexus5-ApiL-x86-WVGA800', |
| '--target': 'android-21', |
| '--abi': 'default/x86', |
| '--device': 'Nexus 5', |
| '--skin': 'WVGA800'}, |
| {'--name': 'Nexus10-ApiL-x86-WXGA800', |
| '--target': 'android-21', |
| '--abi': 'default/x86', |
| '--device': 'Nexus 10', |
| '--skin': 'WXGA800'},] |
| elif android_arch == 'mips': |
| avd_configs = [ |
| {'--name': 'NexusS-Api15-mips-WVGA800', |
| '--target': 'android-15', |
| '--abi': 'default/mips', |
| '--device': 'Nexus S', |
| '--skin': 'WVGA800'}, |
| # As of 2014-05-27, the latest target of MIPS is 17. |
| {'--name': 'Nexus5-Api17-mips-WVGA800', |
| '--target': 'android-17', |
| '--abi': 'default/mips', |
| '--device': 'Nexus 5', |
| '--skin': 'WVGA800'}, |
| {'--name': 'Nexus10-Api17-mips-WXGA800', |
| '--target': 'android-17', |
| '--abi': 'default/mips', |
| '--device': 'Nexus 10', |
| '--skin': 'WXGA800'},] |
| else: |
| avd_configs = [] |
| |
| emulators = Emulator.LaunchAll(android_sdk_home, avd_configs, |
| android_home) |
| serialnumbers.extend([emulator.serial for emulator in emulators]) |
| |
| # Run native and Java tests. |
| android_gyp = os.path.join(SRC_DIR, 'android', 'android.gyp') |
| # run_java_test must be executed after run_native_test |
| # because package manager, which is mandatory to run Java tests, |
| # requires minutes to get ready. |
| for target in ('run_native_test', 'run_java_test'): |
| (build_options, build_targets) = ParseBuildOptions( |
| build_args + ['%s:%s' % (android_gyp, target)]) |
| # Injects android_device attribute to build_options. |
| # The attribute will be used as Makefile parameter. |
| # Q: Why setattr? |
| # A: BuildMain is invoked from build command and the command |
| # does not need 'android_device' option. |
| # So ParseBuildOption method doesn't accept --android_device |
| # argument. Thus build_options doesn't have android_device attribute |
| # but we need the option here becuase there is no other way to specifiy |
| # Makefile parameter. |
| setattr(build_options, 'android_device', ','.join(serialnumbers)) |
| logging.info('build_options=%s', build_options) |
| BuildMain(build_options, build_targets, original_directory_name) |
| finally: |
| # Terminate the emulators. |
| for emulator in emulators: |
| emulator.Terminate() |
| |
| |
| def RunTestsOnNaCl(targets, build_args, original_directory_name): |
| """Run a test suite for the NaCl version.""" |
| # Currently we can only run the limited test set which is defined as |
| # nacl_test_targets in nacl_extension.gyp. |
| if targets: |
| PrintErrorAndExit('Targets [%s] are not supported.' % ', '.join(targets)) |
| nacl_gyp = os.path.join(SRC_DIR, 'chrome', 'nacl', 'nacl_extension.gyp') |
| (build_options, build_targets) = ParseBuildOptions( |
| build_args + [nacl_gyp + ':run_nacl_test']) |
| # Run the test suite in NaCl. |
| BuildMain(build_options, build_targets, original_directory_name) |
| |
| |
| def RunTestsMain(options, args, original_directory_name): |
| """The main function for 'runtests' command.""" |
| # extracting test targets and build flags. To avoid parsing build |
| # options from ParseRunTestsOptions(), user has to specify the test |
| # targets at first, and then add '--' and build flags. Although |
| # optparse removes the '--' argument itself, the first element of |
| # build options has to start with '-' character because they are |
| # flags. |
| # ex: runtests foo.gyp:foo_test bar.gyp:bar_test -- -c Release |
| # => ['foo.gyp:foo_test', 'bar.gyp:bar_test', '-c', 'Release'] |
| # here 'foo.gyp:foo_test' and 'bar.gyp:bar_test' are test targets |
| # and '-c' and 'Release' are build options. |
| targets = [] |
| build_options = [] |
| for i in xrange(len(args)): |
| if args[i].startswith('-'): |
| # starting with build options |
| build_options = args[i:] |
| break |
| target = args[i] |
| # If a directory name is specified as a target, it builds |
| # *_all_test target instead. |
| if args[i].endswith('/'): |
| matched = re.search(r'([^/]+)/$', args[i]) |
| if matched: |
| dirname = matched.group(1) |
| target = '%s%s.gyp:%s_all_test' % (args[i], dirname, dirname) |
| targets.append(target) |
| |
| # configuration and build_base flags are shared among runtests options and |
| # build options. |
| if 'jobs' in vars(options).keys(): |
| build_options.extend(['-j', options.jobs]) |
| if options.configuration: |
| build_options.extend(['-c', options.configuration]) |
| if options.build_base: |
| build_options.extend(['--build_base', options.build_base]) |
| |
| target_platform = GetMozcVersion().GetTargetPlatform() |
| if target_platform == 'NaCl': |
| return RunTestsOnNaCl(targets, build_options, original_directory_name) |
| |
| if not targets: |
| # TODO(yukawa): Change the notation rule of 'targets' to reduce the gap |
| # between Ninja and make. |
| if target_platform == 'Windows': |
| targets.append('out_win/%s:unittests' % options.configuration) |
| else: |
| targets.append('%s/gyp/tests.gyp:unittests' % SRC_DIR) |
| |
| # Build the test targets |
| (build_opts, build_args) = ParseBuildOptions(build_options + targets) |
| BuildMain(build_opts, build_args, original_directory_name) |
| |
| # Run tests actually |
| if target_platform == 'Android': |
| RunTestsOnAndroid(options, build_options, original_directory_name) |
| else: |
| RunTests(GetBuildBaseName(options, target_platform), options.configuration, |
| options.test_jobs) |
| |
| def CleanBuildFilesAndDirectories(options, unused_args): |
| """Cleans build files and directories.""" |
| # File and directory names to be removed. |
| file_names = [] |
| directory_names = [] |
| |
| # Collect stuff in the gyp directories. |
| gyp_directory_names = [os.path.dirname(f) for f in GetGypFileNames(options)] |
| for gyp_directory_name in gyp_directory_names: |
| if IsWindows(): |
| for pattern in ['*.ncb', '*.rules', '*.props', '*.sdf', '*.sln', '*.suo', |
| '*.targets', '*.vcproj', '*.vcproj.*.user', '*.vcxproj', |
| '*.vcxproj.filters', '*.vcxproj.user', 'gen_*_files.xml']: |
| file_names.extend(glob.glob(os.path.join(gyp_directory_name, |
| pattern))) |
| for build_type in ['Debug', 'Release']: |
| directory_names.append(os.path.join(gyp_directory_name, |
| build_type)) |
| elif IsMac(): |
| directory_names.extend(glob.glob(os.path.join(gyp_directory_name, |
| '*.xcodeproj'))) |
| file_names.append('%s/mozc_version.txt' % SRC_DIR) |
| |
| target_platform = GetBuildBaseName(options, |
| GetMozcVersion().GetTargetPlatform()) |
| if target_platform: |
| directory_names.append(target_platform) |
| |
| if IsLinux(): |
| # Remove auto-generated files. |
| file_names.append(os.path.join(SRC_DIR, 'android', 'AndroidManifest.xml')) |
| file_names.append(os.path.join( |
| SRC_DIR, 'android', 'tests', 'AndroidManifest.xml')) |
| directory_names.append(os.path.join(SRC_DIR, 'android', 'assets')) |
| # Delete files/dirs generated by Android SDK/NDK. |
| android_library_projects = [ |
| '', |
| 'protobuf', |
| 'static_resources/resources_oss', |
| 'tests', |
| ] |
| android_generated_dirs = ['bin', 'gen', 'obj', 'libs', 'gen_for_adt'] |
| for project in android_library_projects: |
| for directory in android_generated_dirs: |
| directory_names.append( |
| os.path.join(SRC_DIR, 'android', project, directory)) |
| # In addition, remove resources/res/raw directory, which contains |
| # generated .pic files. |
| directory_names.append( |
| os.path.join(SRC_DIR, 'android', 'static_resources/resources_oss', |
| 'res', 'raw')) |
| |
| # Remove files. |
| for file_name in file_names: |
| RemoveFile(file_name) |
| # Remove directories. |
| for directory_name in directory_names: |
| RemoveDirectoryRecursively(directory_name) |
| |
| |
| def CleanMain(options, args, _): |
| """The main function for the 'clean' command.""" |
| CleanBuildFilesAndDirectories(options, args) |
| |
| |
| def ShowHelpAndExit(): |
| """Shows the help message.""" |
| print 'Usage: build_mozc.py COMMAND [ARGS]' |
| print 'Commands: ' |
| print ' gyp Generate project files.' |
| print ' build Build the specified target.' |
| print ' build_tools Build tools used by the build command.' |
| print ' runtests Build all tests and run them.' |
| print ' clean Clean all the build files and directories.' |
| print '' |
| print 'See also the comment in the script for typical usage.' |
| sys.exit(1) |
| |
| |
| def main(): |
| logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') |
| logging.getLogger().addFilter(ColoredLoggingFilter()) |
| |
| if len(sys.argv) < 2: |
| ShowHelpAndExit() |
| |
| # Remember the original current directory name. |
| original_directory_name = os.getcwd() |
| # Move to the top level source directory only once since os.chdir |
| # affects functions in os.path and that causes troublesome errors. |
| MoveToTopLevelSourceDirectory() |
| |
| command = sys.argv[1] |
| args = sys.argv[2:] |
| |
| command_to_procedure = { |
| 'gyp': (ParseGypOptions, GypMain), |
| 'build_tools': (ParseBuildOptions, BuildToolsMain), |
| 'build': (ParseBuildOptions, BuildMain), |
| 'runtests': (ParseRunTestsOptions, RunTestsMain), |
| 'clean': (ParseCleanOptions, CleanMain)} |
| |
| procedure = command_to_procedure.get(command, None) |
| if procedure is None: |
| logging.error('Unknown command: %s', command) |
| ShowHelpAndExit() |
| |
| # Parse options. |
| (cmd_opts, cmd_args) = procedure[0](args) |
| |
| # Set log level to DEBUG if required. |
| if cmd_opts.verbose: |
| logging.getLogger().setLevel(logging.DEBUG) |
| |
| # Execute build. |
| procedure[1](cmd_opts, cmd_args, original_directory_name) |
| |
| |
| if __name__ == '__main__': |
| main() |