| # Copyright 2010, 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 optparse |
| import os |
| import re |
| import shutil |
| import subprocess |
| import sys |
| |
| SRC_DIR = '.' |
| EXTRA_SRC_DIR = '..' |
| |
| sys.path.append(SRC_DIR) |
| |
| from build_tools import mozc_version |
| |
| |
| def IsWindows(): |
| """Returns true if the platform is Windows.""" |
| return os.name == 'nt' |
| |
| |
| def IsMac(): |
| """Returns true if the platform is Mac.""" |
| return os.name == 'posix' and os.uname()[0] == 'Darwin' |
| |
| |
| def IsLinux(): |
| """Returns true if the platform is Linux.""" |
| return os.name == 'posix' and os.uname()[0] == 'Linux' |
| |
| |
| def GetGeneratorName(): |
| """Gets the generator name based on the platform.""" |
| generator = 'make' |
| if IsWindows(): |
| generator = 'msvs' |
| elif IsMac(): |
| generator = 'xcode' |
| return generator |
| |
| |
| def GenerateVersionFile(version_template_path, version_path): |
| """Reads the version template file and stores it into version_path. |
| |
| This doesn't update the "version_path" if nothing will be changed to |
| reduce unnecessary build caused by file timestamp. |
| |
| Args: |
| version_template_path: a file name which contains the template of version. |
| version_path: a file name to be stored the official version. |
| """ |
| version = mozc_version.MozcVersion(version_template_path, expand_daily=True) |
| version_definition = version.GetVersionInFormat( |
| 'MAJOR=@MAJOR@\nMINOR=@MINOR@\nBUILD=@BUILD@\nREVISION=@REVISION@\n') |
| old_content = '' |
| if os.path.exists(version_path): |
| # if the target file already exists, need to check the necessity of update. |
| old_content = open(version_path).read() |
| |
| if version_definition != old_content: |
| open(version_path, 'w').write(version_definition) |
| |
| |
| def GetVersionFileNames(): |
| """Gets the (template of version file, version file) pair.""" |
| template_path = '%s/mozc_version_template.txt' % SRC_DIR |
| version_path = '%s/mozc_version.txt' % SRC_DIR |
| return (template_path, version_path) |
| |
| |
| def GetGypFileNames(): |
| """Gets the list of gyp file names.""" |
| gyp_file_names = [] |
| mozc_top_level_names = glob.glob('%s/*' % SRC_DIR) |
| # Exclude the gyp directory where we have special gyp files like |
| # breakpad.gyp that we should exclude. |
| mozc_top_level_names = [x for x in mozc_top_level_names if |
| os.path.basename(x) != 'gyp'] |
| 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 subdirectory of dictionary |
| gyp_file_names.append( |
| '%s/dictionary/file/dictionary_file.gyp' % SRC_DIR) |
| gyp_file_names.append( |
| '%s/dictionary/system/system_dictionary.gyp' % SRC_DIR) |
| # Include subdirectory of win32 and breakpad for Windows |
| if IsWindows(): |
| gyp_file_names.extend(glob.glob('%s/win32/*/*.gyp' % SRC_DIR)) |
| gyp_file_names.extend(glob.glob('third_party/breakpad/*.gyp')) |
| gyp_file_names.append('third_party/mozc/sandbox/sandbox.gyp') |
| elif IsLinux(): |
| gyp_file_names.extend(glob.glob('%s/unix/*/*.gyp' % SRC_DIR)) |
| gyp_file_names.extend(glob.glob('third_party/rx/*.gyp')) |
| gyp_file_names.sort() |
| return gyp_file_names |
| |
| |
| def RemoveFile(file_name): |
| """Removes the specified file.""" |
| if not os.path.isfile(file_name): |
| return # Do nothing if not exist. |
| if IsWindows(): |
| # Read-only files cannot be deleted on Windows. |
| os.chmod(file_name, 0700) |
| print 'Removing file: %s' % file_name |
| os.unlink(file_name) |
| |
| |
| def CopyFile(source, destination): |
| """Copies a file to the destination. Remove an old version if needed.""" |
| if os.path.isfile(destination): # Remove the old one if exists. |
| RemoveFile(destination) |
| print 'Copying file to: %s' % destination |
| shutil.copy(source, destination) |
| |
| |
| def RecursiveRemoveDirectory(directory): |
| """Removes the specified directory recursively.""" |
| if os.path.isdir(directory): |
| print 'Removing directory: %s' % directory |
| if IsWindows(): |
| # Use RD because shutil.rmtree fails when the directory is readonly. |
| RunOrDie(['CMD.exe', '/C', 'RD', '/S', '/Q', |
| os.path.normpath(directory)]) |
| else: |
| shutil.rmtree(directory, ignore_errors=True) |
| |
| |
| def CleanBuildFilesAndDirectories(): |
| """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()] |
| for gyp_directory_name in gyp_directory_names: |
| if IsWindows(): |
| for pattern in ['*.rules', '*.sln', '*.vcproj']: |
| file_names.extend(glob.glob(os.path.join(gyp_directory_name, |
| pattern))) |
| for build_type in ['Debug', 'Optimize', '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'))) |
| elif IsLinux(): |
| file_names.extend(glob.glob(os.path.join(gyp_directory_name, |
| '*.target.mk'))) |
| file_names.append('%s/mozc_version.txt' % SRC_DIR) |
| file_names.append('third_party/rx/rx.gyp') |
| # Collect stuff in the top-level directory. |
| directory_names.append('mozc_build_tools') |
| if IsMac(): |
| directory_names.append('xcodebuild') |
| elif IsLinux(): |
| file_names.append('Makefile') |
| directory_names.append('out') |
| elif IsWindows(): |
| file_names.append('third_party/breakpad/breakpad.gyp') |
| directory_names.append('out_win') |
| # Remove files. |
| for file_name in file_names: |
| RemoveFile(file_name) |
| # Remove directories. |
| for directory_name in directory_names: |
| RecursiveRemoveDirectory(directory_name) |
| |
| |
| def GetTopLevelSourceDirectoryName(): |
| """Gets the top level source directory name.""" |
| if SRC_DIR == '.': |
| return SRC_DIR |
| script_file_directory_name = os.path.dirname(sys.argv[0]) |
| num_components = len(SRC_DIR.split('/')) |
| dots = ['..'] * num_components |
| return os.path.join(script_file_directory_name, '/'.join(dots)) |
| |
| |
| def MoveToTopLevelSourceDirectory(): |
| """Moves to the build top level directory.""" |
| os.chdir(GetTopLevelSourceDirectoryName()) |
| |
| |
| def GetGypSvnUrl(deps_file_name): |
| """Get the GYP SVN URL from DEPS file.""" |
| contents = file(deps_file_name).read() |
| match = re.search(r'"(http://gyp\.googlecode\.com.*?)"', contents) |
| if match: |
| return match.group(1) |
| else: |
| PrintErrorAndExit('GYP URL not found in %s:' % deps_file_name) |
| |
| |
| def GypMain(deps_file_name): |
| options = ParseGypOptions() |
| """The main function for the 'gyp' command.""" |
| # Copy rx.gyp to the third party directory. |
| CopyFile('%s/gyp/rx.gyp' % SRC_DIR, |
| 'third_party/rx/rx.gyp') |
| # Copy breakpad.gyp to the third party directory, if necessary. |
| if IsWindows(): |
| CopyFile('%s/gyp/breakpad.gyp' % SRC_DIR, |
| 'third_party/breakpad/breakpad.gyp') |
| |
| # Determine the generator name. |
| generator = GetGeneratorName() |
| os.environ['GYP_GENERATORS'] = generator |
| print 'Build tool: %s' % generator |
| |
| # Get and show the list of .gyp file names. |
| gyp_file_names = GetGypFileNames() |
| print 'GYP files:' |
| for file_name in gyp_file_names: |
| print '- %s' % file_name |
| # We use the one in mozc_build_tools/gyp |
| gyp_script = 'mozc_build_tools/gyp/gyp' |
| # If we don't have a copy of gyp, download it. |
| if not os.path.isfile(gyp_script): |
| # SVN creates mozc_build_tools directory if it's not present. |
| gyp_svn_url = GetGypSvnUrl(deps_file_name) |
| RunOrDie(['svn', 'checkout', gyp_svn_url, 'mozc_build_tools/gyp']) |
| # Run GYP. |
| print 'Running GYP...' |
| command_line = [sys.executable, gyp_script, |
| '--no-circular-check', |
| '--depth=.', |
| '--include=%s/gyp/common.gypi' % SRC_DIR] |
| if options.onepass: |
| command_line.extend(['-D', 'two_pass_build=0']) |
| command_line.extend(gyp_file_names) |
| |
| if options.branding: |
| command_line.extend(['-D', 'branding=%s' % options.branding]) |
| RunOrDie(command_line) |
| |
| |
| # Done! |
| print 'Done' |
| |
| |
| def CleanMain(): |
| """The main function for the 'clean' command.""" |
| CleanBuildFilesAndDirectories() |
| |
| |
| class RunOrDieError(StandardError): |
| """The exception class for RunOrDie.""" |
| |
| def __init__(self, message): |
| StandardError.__init__(self, message) |
| |
| |
| def RunOrDie(argv): |
| """Run the command, or die if it failed.""" |
| |
| # Rest are the target program name and the parameters, but we special |
| # case if the target program name ends with '.py' |
| if argv[0].endswith('.py'): |
| argv.insert(0, sys.executable) # Inject the python interpreter path. |
| # We don't capture stdout and stderr from Popen. The output will just |
| # be emitted to a terminal or console. |
| print 'Running: ' + ' '.join(argv) |
| process = subprocess.Popen(argv) |
| |
| if process.wait() != 0: |
| raise RunOrDieError('\n'.join(['', |
| '==========', |
| ' ERROR: %s' % ' '.join(argv), |
| '=========='])) |
| |
| |
| def PrintErrorAndExit(error_message): |
| """Prints the error message and exists.""" |
| print error_message |
| sys.exit(1) |
| |
| |
| def ParseGypOptions(): |
| """Parse command line options for the gyp command.""" |
| parser = optparse.OptionParser(usage='Usage: %prog gyp [options]') |
| parser.add_option('--onepass', '-1', dest='onepass', action='store_true', |
| default=False, help='build mozc in one pass. ' + |
| 'Not recommended for Debug build.') |
| parser.add_option('--branding', dest='branding', default='Mozc') |
| (options, unused_args) = parser.parse_args() |
| return options |
| |
| |
| def ParseBuildOptions(): |
| """Parse command line options for the build command.""" |
| parser = optparse.OptionParser(usage='Usage: %prog build [options]') |
| parser.add_option('--jobs', '-j', dest='jobs', default='4', metavar='N', |
| help='run jobs in parallel') |
| parser.add_option('--configuration', '-c', dest='configuration', |
| default='Debug', help='specify the build configuration.') |
| if IsWindows(): |
| parser.add_option('--platform', '-p', dest='platform', |
| default='Win32', |
| help='specify the target plaform: [Win32|x64]') |
| # default Qt dir to support the current build procedure for Debian. |
| default_qtdir = '/usr/local/Trolltech/Qt-4.5.2' |
| if IsWindows(): |
| default_qtdir = None |
| parser.add_option('--qtdir', dest='qtdir', |
| default=os.getenv('QTDIR', default_qtdir), |
| help='Qt base directory to be used.') |
| |
| (options, args) = parser.parse_args() |
| |
| targets = args |
| if not targets: |
| PrintErrorAndExit('No build target is specified.') |
| |
| return (options, args) |
| |
| |
| def ParseTarget(target): |
| """Parses the target string.""" |
| if not ':' in target: |
| PrintErrorAndExit('Invalid target: ' + target) |
| (gyp_file_name, target_name) = target.split(':') |
| return (gyp_file_name, target_name) |
| |
| |
| def BuildOnLinux(options, targets): |
| """Build the targets on Linux.""" |
| target_names = [] |
| for target in targets: |
| (unused_gyp_file_name, target_name) = ParseTarget(target) |
| target_names.append(target_name) |
| |
| make_command = os.getenv('BUILD_COMMAND', 'make') |
| # flags for building in Chrome OS chroot environment |
| envvars = [ |
| 'CFLAGS', |
| 'CXXFLAGS', |
| 'CXX', |
| 'CC', |
| 'AR', |
| 'AS', |
| 'RANLIB', |
| 'LD', |
| ] |
| for envvar in envvars: |
| if envvar in os.environ: |
| os.environ[envvar] = os.getenv(envvar) |
| |
| RunOrDie([make_command, '-j%s' % options.jobs, |
| 'BUILDTYPE=%s' % options.configuration] + |
| target_names) |
| |
| |
| def CheckFileOrDie(file_name): |
| """Check the file exists or dies if not.""" |
| if not os.path.isfile(file_name): |
| PrintErrorAndExit('No such file: ' + file_name) |
| |
| |
| def GetRelpath(path, start): |
| """Return a relative path to |path| from |start|.""" |
| # NOTE: Python 2.6 provides os.path.relpath, which has almost the same |
| # functionality as this function. Since Python 2.6 is not the internal |
| # official version, we reimplement it. |
| path_list = os.path.abspath(os.path.normpath(path)).split(os.sep) |
| start_list = os.path.abspath(os.path.normpath(start)).split(os.sep) |
| |
| common_prefix_count = 0 |
| for i in range(0, min(len(path_list), len(start_list))): |
| if path_list[i] != start_list[i]: |
| break |
| common_prefix_count += 1 |
| |
| return os.sep.join(['..'] * (len(start_list) - common_prefix_count) + |
| path_list[common_prefix_count:]) |
| |
| |
| def BuildOnMac(options, targets, original_directory_name): |
| """Build the targets on Mac.""" |
| # 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. |
| original_directory_relpath = GetRelpath(original_directory_name, os.getcwd()) |
| sym_root = os.path.join(os.getcwd(), 'xcodebuild') |
| for target in targets: |
| (gyp_file_name, target_name) = ParseTarget(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', |
| 'SYMROOT=%s' % sym_root]) |
| |
| |
| def BuildOnWindows(options, targets, original_directory_name): |
| """Build the target on Windowsw.""" |
| # TODO(yukawa): make a python module to set up environment for vcbuild. |
| |
| # TODO(yukawa): Locate the directory of the vcbuild.exe as follows. |
| # 1. Get the clsid corresponding to 'VisualStudio.VCProjectEngine.8.0' |
| # 2. Get the directory of the DLL corresponding to retrieved clsid |
| program_files_path = os.getenv('ProgramFiles(x86)', |
| os.getenv('ProgramFiles')) |
| rel_paths = ['Microsoft Visual Studio 8/VC/vcpackages', |
| 'Microsoft SDKs/Windows/v6.0/VC/Bin'] |
| abs_vcbuild_dir = '' |
| for rel_path in rel_paths: |
| search_dir = os.path.join(program_files_path, rel_path) |
| if os.path.exists(os.path.join(search_dir, 'vcbuild.exe')): |
| abs_vcbuild_dir = os.path.abspath(search_dir) |
| break |
| CheckFileOrDie(os.path.join(abs_vcbuild_dir, 'vcbuild.exe')) |
| |
| if os.getenv('PATH'): |
| os.environ['PATH'] = os.pathsep.join([abs_vcbuild_dir, os.getenv('PATH')]) |
| else: |
| os.environ['PATH'] = abs_vcbuild_dir |
| |
| rel_paths = ['%s/third_party/platformsdk/v6_1/files/Bin' % EXTRA_SRC_DIR, |
| '%s/third_party/code_signing' % EXTRA_SRC_DIR, |
| '%s/third_party/vc_80/files/common7/IDE' % EXTRA_SRC_DIR, |
| '%s/third_party/vc_80/files/common7/Tools' % EXTRA_SRC_DIR, |
| '%s/third_party/vc_80/files/common7/Tools/bin' % EXTRA_SRC_DIR, |
| '%s/third_party/wix/v3_0_4220/files' % EXTRA_SRC_DIR] |
| rel_paths_x64 = ['%s/third_party/vc_80/files/vc/bin/x86_amd64' |
| % EXTRA_SRC_DIR] |
| rel_paths_x86 = ['%s/third_party/vc_80/files/vc/bin' % EXTRA_SRC_DIR] |
| if options.platform == 'x64': |
| rel_paths += rel_paths_x64 |
| rel_paths += rel_paths_x86 |
| abs_paths = [os.path.abspath(path) for path in rel_paths] |
| os.environ['PATH'] = os.pathsep.join(abs_paths + [os.getenv('PATH')]) |
| |
| os.environ['INCLUDE'] = '' |
| os.environ['LIB'] = '' |
| os.environ['LIBPATH'] = '' |
| |
| for target in targets: |
| # TODO(yukawa): target name is currently ignored. |
| (gyp_file_name, _) = ParseTarget(target) |
| gyp_file_name = os.path.join(original_directory_name, gyp_file_name) |
| CheckFileOrDie(gyp_file_name) |
| (sln_base_name, _) = os.path.splitext(gyp_file_name) |
| sln_file_path = os.path.abspath('%s.sln' % sln_base_name) |
| # To use different toolsets for vcbuild, we set %PATH%, %INCLUDE%, %LIB%, |
| # %LIBPATH% and specify /useenv option here. See the following article |
| # for details. |
| # http://blogs.msdn.com/vcblog/archive/2007/12/30/using-different-toolsets-for-vc-build.aspx |
| RunOrDie(['vcbuild', |
| '/useenv', # Use %PATH%, %INCLUDE%, %LIB%, %LIBPATH% |
| '/M', # Use concurrent build |
| '/time', # Show build time |
| '/platform:%s' % options.platform, |
| sln_file_path, |
| '%s|%s' % (options.configuration, options.platform)]) |
| |
| |
| def BuildMain(original_directory_name): |
| """The main function for the 'build' command.""" |
| (options, targets) = ParseBuildOptions() |
| |
| # Generate a version definition file. |
| print 'Generating version definition file...' |
| (template_path, version_path) = GetVersionFileNames() |
| GenerateVersionFile(template_path, version_path) |
| |
| # Set $QTDIR for mozc_tool |
| if options.qtdir: |
| print 'export $QTDIR = %s' % options.qtdir |
| os.environ['QTDIR'] = options.qtdir |
| |
| if IsMac(): |
| BuildOnMac(options, targets, original_directory_name) |
| elif IsLinux(): |
| BuildOnLinux(options, targets) |
| elif IsWindows(): |
| BuildOnWindows(options, targets, original_directory_name) |
| else: |
| print 'Unsupported platform: ', system |
| |
| |
| def BuildToolsMain(original_directory_name): |
| """The main function for 'build_tools' command.""" |
| build_tools_dir = os.path.join(GetRelpath(os.getcwd(), |
| original_directory_name), |
| '%s/build_tools' % SRC_DIR) |
| # build targets in this order |
| gyp_files = [ |
| os.path.join(build_tools_dir, 'primitive_tools', 'primitive_tools.gyp'), |
| os.path.join(build_tools_dir, 'build_tools.gyp') |
| ] |
| |
| for gyp_file in gyp_files: |
| (target, _) = os.path.splitext(os.path.basename(gyp_file)) |
| sys.argv.append('%s:%s' % (gyp_file, target)) |
| BuildMain(original_directory_name) |
| sys.argv.pop() |
| |
| |
| 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 ' 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(): |
| if len(sys.argv) < 2: |
| ShowHelpAndExit() |
| |
| # DEPS files should exist in the same directory of the script. |
| deps_file_name = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), |
| 'DEPS')) |
| # 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] |
| del(sys.argv[1]) # Delete the command. |
| if command == 'build': |
| BuildMain(original_directory_name) |
| elif command == 'build_tools': |
| BuildToolsMain(original_directory_name) |
| elif command == 'clean': |
| CleanMain() |
| elif command == 'gyp': |
| GypMain(deps_file_name) |
| else: |
| print 'Unknown command: ' + command |
| ShowHelpAndExit() |
| |
| if __name__ == '__main__': |
| main() |