1
# Copyright 2010, Google Inc.
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are
8
# * Redistributions of source code must retain the above copyright
9
# notice, this list of conditions and the following disclaimer.
10
# * Redistributions in binary form must reproduce the above
11
# copyright notice, this list of conditions and the following disclaimer
12
# in the documentation and/or other materials provided with the
14
# * Neither the name of Google Inc. nor the names of its
15
# contributors may be used to endorse or promote products derived from
16
# this software without specific prior written permission.
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
"""Script building Mozc.
34
% python build_mozc.py gyp
35
% python build_mozc.py build_tools -c Release
36
% python build_mozc.py build base/base.gyp:base
39
__author__ = "komatsu"
52
sys.path.append(SRC_DIR)
54
from build_tools import mozc_version
58
"""Returns true if the platform is Windows."""
59
return os.name == 'nt'
63
"""Returns true if the platform is Mac."""
64
return os.name == 'posix' and os.uname()[0] == 'Darwin'
68
"""Returns true if the platform is Linux."""
69
return os.name == 'posix' and os.uname()[0] == 'Linux'
72
def GetGeneratorName():
73
"""Gets the generator name based on the platform."""
82
def GenerateVersionFile(version_template_path, version_path):
83
"""Reads the version template file and stores it into version_path.
85
This doesn't update the "version_path" if nothing will be changed to
86
reduce unnecessary build caused by file timestamp.
89
version_template_path: a file name which contains the template of version.
90
version_path: a file name to be stored the official version.
92
version = mozc_version.MozcVersion(version_template_path, expand_daily=True)
93
version_definition = version.GetVersionInFormat(
94
'MAJOR=@MAJOR@\nMINOR=@MINOR@\nBUILD=@BUILD@\nREVISION=@REVISION@\n')
96
if os.path.exists(version_path):
97
# if the target file already exists, need to check the necessity of update.
98
old_content = open(version_path).read()
100
if version_definition != old_content:
101
open(version_path, 'w').write(version_definition)
104
def GetVersionFileNames():
105
"""Gets the (template of version file, version file) pair."""
106
template_path = '%s/mozc_version_template.txt' % SRC_DIR
107
version_path = '%s/mozc_version.txt' % SRC_DIR
108
return (template_path, version_path)
111
def GetGypFileNames():
112
"""Gets the list of gyp file names."""
114
mozc_top_level_names = glob.glob('%s/*' % SRC_DIR)
115
# Exclude the gyp directory where we have special gyp files like
116
# breakpad.gyp that we should exclude.
117
mozc_top_level_names = [x for x in mozc_top_level_names if
118
os.path.basename(x) != 'gyp']
119
for name in mozc_top_level_names:
120
gyp_file_names.extend(glob.glob(name + '/*.gyp'))
121
gyp_file_names.extend(glob.glob('%s/build_tools/*/*.gyp' % SRC_DIR))
122
# Include subdirectory of dictionary
123
gyp_file_names.append(
124
'%s/dictionary/file/dictionary_file.gyp' % SRC_DIR)
125
gyp_file_names.append(
126
'%s/dictionary/system/system_dictionary.gyp' % SRC_DIR)
127
# Include subdirectory of win32 and breakpad for Windows
129
gyp_file_names.extend(glob.glob('%s/win32/*/*.gyp' % SRC_DIR))
130
gyp_file_names.extend(glob.glob('third_party/breakpad/*.gyp'))
131
gyp_file_names.append('third_party/mozc/sandbox/sandbox.gyp')
133
gyp_file_names.extend(glob.glob('%s/unix/*/*.gyp' % SRC_DIR))
134
gyp_file_names.extend(glob.glob('third_party/rx/*.gyp'))
135
gyp_file_names.sort()
136
return gyp_file_names
139
def RemoveFile(file_name):
140
"""Removes the specified file."""
141
if not os.path.isfile(file_name):
142
return # Do nothing if not exist.
144
# Read-only files cannot be deleted on Windows.
145
os.chmod(file_name, 0700)
146
print 'Removing file: %s' % file_name
150
def CopyFile(source, destination):
151
"""Copies a file to the destination. Remove an old version if needed."""
152
if os.path.isfile(destination): # Remove the old one if exists.
153
RemoveFile(destination)
154
print 'Copying file to: %s' % destination
155
shutil.copy(source, destination)
158
def RecursiveRemoveDirectory(directory):
159
"""Removes the specified directory recursively."""
160
if os.path.isdir(directory):
161
print 'Removing directory: %s' % directory
163
# Use RD because shutil.rmtree fails when the directory is readonly.
164
RunOrDie(['CMD.exe', '/C', 'RD', '/S', '/Q',
165
os.path.normpath(directory)])
167
shutil.rmtree(directory, ignore_errors=True)
170
def CleanBuildFilesAndDirectories():
171
"""Cleans build files and directories."""
172
# File and directory names to be removed.
176
# Collect stuff in the gyp directories.
177
gyp_directory_names = [os.path.dirname(f) for f in GetGypFileNames()]
178
for gyp_directory_name in gyp_directory_names:
180
for pattern in ['*.rules', '*.sln', '*.vcproj']:
181
file_names.extend(glob.glob(os.path.join(gyp_directory_name,
183
for build_type in ['Debug', 'Optimize', 'Release']:
184
directory_names.append(os.path.join(gyp_directory_name,
187
directory_names.extend(glob.glob(os.path.join(gyp_directory_name,
190
file_names.extend(glob.glob(os.path.join(gyp_directory_name,
192
file_names.append('%s/mozc_version.txt' % SRC_DIR)
193
file_names.append('third_party/rx/rx.gyp')
194
# Collect stuff in the top-level directory.
195
directory_names.append('mozc_build_tools')
197
directory_names.append('xcodebuild')
199
file_names.append('Makefile')
200
directory_names.append('out')
202
file_names.append('third_party/breakpad/breakpad.gyp')
203
directory_names.append('out_win')
205
for file_name in file_names:
206
RemoveFile(file_name)
207
# Remove directories.
208
for directory_name in directory_names:
209
RecursiveRemoveDirectory(directory_name)
212
def GetTopLevelSourceDirectoryName():
213
"""Gets the top level source directory name."""
216
script_file_directory_name = os.path.dirname(sys.argv[0])
217
num_components = len(SRC_DIR.split('/'))
218
dots = ['..'] * num_components
219
return os.path.join(script_file_directory_name, '/'.join(dots))
222
def MoveToTopLevelSourceDirectory():
223
"""Moves to the build top level directory."""
224
os.chdir(GetTopLevelSourceDirectoryName())
227
def GetGypSvnUrl(deps_file_name):
228
"""Get the GYP SVN URL from DEPS file."""
229
contents = file(deps_file_name).read()
230
match = re.search(r'"(http://gyp\.googlecode\.com.*?)"', contents)
232
return match.group(1)
234
PrintErrorAndExit('GYP URL not found in %s:' % deps_file_name)
237
def GypMain(deps_file_name):
238
options = ParseGypOptions()
239
"""The main function for the 'gyp' command."""
240
# Copy rx.gyp to the third party directory.
241
CopyFile('%s/gyp/rx.gyp' % SRC_DIR,
242
'third_party/rx/rx.gyp')
243
# Copy breakpad.gyp to the third party directory, if necessary.
245
CopyFile('%s/gyp/breakpad.gyp' % SRC_DIR,
246
'third_party/breakpad/breakpad.gyp')
248
# Determine the generator name.
249
generator = GetGeneratorName()
250
os.environ['GYP_GENERATORS'] = generator
251
print 'Build tool: %s' % generator
253
# Get and show the list of .gyp file names.
254
gyp_file_names = GetGypFileNames()
256
for file_name in gyp_file_names:
257
print '- %s' % file_name
258
# We use the one in mozc_build_tools/gyp
259
gyp_script = 'mozc_build_tools/gyp/gyp'
260
# If we don't have a copy of gyp, download it.
261
if not os.path.isfile(gyp_script):
262
# SVN creates mozc_build_tools directory if it's not present.
263
gyp_svn_url = GetGypSvnUrl(deps_file_name)
264
RunOrDie(['svn', 'checkout', gyp_svn_url, 'mozc_build_tools/gyp'])
266
print 'Running GYP...'
267
command_line = [sys.executable, gyp_script,
268
'--no-circular-check',
270
'--include=%s/gyp/common.gypi' % SRC_DIR]
271
command_line.extend(['-D', 'two_pass_build=1'])
272
command_line.extend(gyp_file_names)
275
command_line.extend(['-D', 'branding=%s' % options.branding])
276
RunOrDie(command_line)
284
"""The main function for the 'clean' command."""
285
CleanBuildFilesAndDirectories()
288
class RunOrDieError(StandardError):
289
"""The exception class for RunOrDie."""
291
def __init__(self, message):
292
StandardError.__init__(self, message)
296
"""Run the command, or die if it failed."""
298
# Rest are the target program name and the parameters, but we special
299
# case if the target program name ends with '.py'
300
if argv[0].endswith('.py'):
301
argv.insert(0, sys.executable) # Inject the python interpreter path.
302
# We don't capture stdout and stderr from Popen. The output will just
303
# be emitted to a terminal or console.
304
print 'Running: ' + ' '.join(argv)
305
process = subprocess.Popen(argv)
307
if process.wait() != 0:
308
raise RunOrDieError('\n'.join(['',
310
' ERROR: %s' % ' '.join(argv),
314
def PrintErrorAndExit(error_message):
315
"""Prints the error message and exists."""
320
def ParseGypOptions():
321
"""Parse command line options for the gyp command."""
322
parser = optparse.OptionParser(usage='Usage: %prog gyp [options]')
323
parser.add_option('--onepass', '-1', dest='onepass', action='store_true',
324
default=False, help='build mozc in one pass. ' +
325
'Not recommended for Debug build.')
326
parser.add_option('--branding', dest='branding', default='Mozc')
327
(options, unused_args) = parser.parse_args()
331
def ParseBuildOptions():
332
"""Parse command line options for the build command."""
333
parser = optparse.OptionParser(usage='Usage: %prog build [options]')
334
parser.add_option('--jobs', '-j', dest='jobs', default='4', metavar='N',
335
help='run jobs in parallel')
336
parser.add_option('--configuration', '-c', dest='configuration',
337
default='Debug', help='specify the build configuration.')
339
parser.add_option('--platform', '-p', dest='platform',
341
help='specify the target plaform: [Win32|x64]')
342
# default Qt dir to support the current build procedure for Debian.
343
default_qtdir = '/usr/local/Trolltech/Qt-4.5.2'
346
parser.add_option('--qtdir', dest='qtdir',
347
default=os.getenv('QTDIR', default_qtdir),
348
help='Qt base directory to be used.')
350
(options, args) = parser.parse_args()
354
PrintErrorAndExit('No build target is specified.')
356
return (options, args)
359
def ParseTarget(target):
360
"""Parses the target string."""
361
if not ':' in target:
362
PrintErrorAndExit('Invalid target: ' + target)
363
(gyp_file_name, target_name) = target.split(':')
364
return (gyp_file_name, target_name)
367
def BuildOnLinux(options, targets):
368
"""Build the targets on Linux."""
370
for target in targets:
371
(unused_gyp_file_name, target_name) = ParseTarget(target)
372
target_names.append(target_name)
374
make_command = os.getenv('BUILD_COMMAND', 'make')
375
# flags for building in Chrome OS chroot environment
386
for envvar in envvars:
387
if envvar in os.environ:
388
os.environ[envvar] = os.getenv(envvar)
390
RunOrDie([make_command, '-j%s' % options.jobs,
391
'BUILDTYPE=%s' % options.configuration] +
395
def CheckFileOrDie(file_name):
396
"""Check the file exists or dies if not."""
397
if not os.path.isfile(file_name):
398
PrintErrorAndExit('No such file: ' + file_name)
401
def GetRelpath(path, start):
402
"""Return a relative path to |path| from |start|."""
403
# NOTE: Python 2.6 provides os.path.relpath, which has almost the same
404
# functionality as this function. Since Python 2.6 is not the internal
405
# official version, we reimplement it.
406
path_list = os.path.abspath(os.path.normpath(path)).split(os.sep)
407
start_list = os.path.abspath(os.path.normpath(start)).split(os.sep)
409
common_prefix_count = 0
410
for i in range(0, min(len(path_list), len(start_list))):
411
if path_list[i] != start_list[i]:
413
common_prefix_count += 1
415
return os.sep.join(['..'] * (len(start_list) - common_prefix_count) +
416
path_list[common_prefix_count:])
419
def BuildOnMac(options, targets, original_directory_name):
420
"""Build the targets on Mac."""
421
# For some reason, xcodebuild does not accept absolute path names for
422
# the -project parameter. Convert the original_directory_name to a
423
# relative path from the build top level directory.
424
original_directory_relpath = GetRelpath(original_directory_name, os.getcwd())
425
sym_root = os.path.join(os.getcwd(), 'xcodebuild')
426
for target in targets:
427
(gyp_file_name, target_name) = ParseTarget(target)
428
gyp_file_name = os.path.join(original_directory_relpath, gyp_file_name)
429
CheckFileOrDie(gyp_file_name)
430
(xcode_base_name, _) = os.path.splitext(gyp_file_name)
431
RunOrDie(['xcodebuild',
432
'-project', '%s.xcodeproj' % xcode_base_name,
433
'-configuration', options.configuration,
434
'-target', target_name,
435
'-parallelizeTargets',
436
'SYMROOT=%s' % sym_root])
439
def BuildOnWindows(options, targets, original_directory_name):
440
"""Build the target on Windowsw."""
441
# TODO(yukawa): make a python module to set up environment for vcbuild.
443
# TODO(yukawa): Locate the directory of the vcbuild.exe as follows.
444
# 1. Get the clsid corresponding to 'VisualStudio.VCProjectEngine.8.0'
445
# 2. Get the directory of the DLL corresponding to retrieved clsid
446
program_files_path = os.getenv('ProgramFiles(x86)',
447
os.getenv('ProgramFiles'))
448
rel_paths = ['Microsoft Visual Studio 8/VC/vcpackages',
449
'Microsoft SDKs/Windows/v6.0/VC/Bin']
451
for rel_path in rel_paths:
452
search_dir = os.path.join(program_files_path, rel_path)
453
if os.path.exists(os.path.join(search_dir, 'vcbuild.exe')):
454
abs_vcbuild_dir = os.path.abspath(search_dir)
456
CheckFileOrDie(os.path.join(abs_vcbuild_dir, 'vcbuild.exe'))
458
if os.getenv('PATH'):
459
os.environ['PATH'] = os.pathsep.join([abs_vcbuild_dir, os.getenv('PATH')])
461
os.environ['PATH'] = abs_vcbuild_dir
463
rel_paths = ['%s/third_party/platformsdk/v6_1/files/Bin' % EXTRA_SRC_DIR,
464
'%s/third_party/code_signing' % EXTRA_SRC_DIR,
465
'%s/third_party/vc_80/files/common7/IDE' % EXTRA_SRC_DIR,
466
'%s/third_party/vc_80/files/common7/Tools' % EXTRA_SRC_DIR,
467
'%s/third_party/vc_80/files/common7/Tools/bin' % EXTRA_SRC_DIR,
468
'%s/third_party/wix/v3_0_4220/files' % EXTRA_SRC_DIR]
469
rel_paths_x64 = ['%s/third_party/vc_80/files/vc/bin/x86_amd64'
471
rel_paths_x86 = ['%s/third_party/vc_80/files/vc/bin' % EXTRA_SRC_DIR]
472
if options.platform == 'x64':
473
rel_paths += rel_paths_x64
474
rel_paths += rel_paths_x86
475
abs_paths = [os.path.abspath(path) for path in rel_paths]
476
os.environ['PATH'] = os.pathsep.join(abs_paths + [os.getenv('PATH')])
478
os.environ['INCLUDE'] = ''
479
os.environ['LIB'] = ''
480
os.environ['LIBPATH'] = ''
482
for target in targets:
483
# TODO(yukawa): target name is currently ignored.
484
(gyp_file_name, _) = ParseTarget(target)
485
gyp_file_name = os.path.join(original_directory_name, gyp_file_name)
486
CheckFileOrDie(gyp_file_name)
487
(sln_base_name, _) = os.path.splitext(gyp_file_name)
488
sln_file_path = os.path.abspath('%s.sln' % sln_base_name)
489
# To use different toolsets for vcbuild, we set %PATH%, %INCLUDE%, %LIB%,
490
# %LIBPATH% and specify /useenv option here. See the following article
492
# http://blogs.msdn.com/vcblog/archive/2007/12/30/using-different-toolsets-for-vc-build.aspx
494
'/useenv', # Use %PATH%, %INCLUDE%, %LIB%, %LIBPATH%
495
'/M', # Use concurrent build
496
'/time', # Show build time
497
'/platform:%s' % options.platform,
499
'%s|%s' % (options.configuration, options.platform)])
502
def BuildMain(original_directory_name):
503
"""The main function for the 'build' command."""
504
(options, targets) = ParseBuildOptions()
506
# Generate a version definition file.
507
print 'Generating version definition file...'
508
(template_path, version_path) = GetVersionFileNames()
509
GenerateVersionFile(template_path, version_path)
511
# Set $QTDIR for mozc_tool
513
print 'export $QTDIR = %s' % options.qtdir
514
os.environ['QTDIR'] = options.qtdir
517
BuildOnMac(options, targets, original_directory_name)
519
BuildOnLinux(options, targets)
521
BuildOnWindows(options, targets, original_directory_name)
523
print 'Unsupported platform: ', system
526
def BuildToolsMain(original_directory_name):
527
"""The main function for 'build_tools' command."""
528
build_tools_dir = os.path.join(GetRelpath(os.getcwd(),
529
original_directory_name),
530
'%s/build_tools' % SRC_DIR)
531
# build targets in this order
533
os.path.join(build_tools_dir, 'primitive_tools', 'primitive_tools.gyp'),
534
os.path.join(build_tools_dir, 'build_tools.gyp')
537
for gyp_file in gyp_files:
538
(target, _) = os.path.splitext(os.path.basename(gyp_file))
539
sys.argv.append('%s:%s' % (gyp_file, target))
540
BuildMain(original_directory_name)
544
def ShowHelpAndExit():
545
"""Shows the help message."""
546
print 'Usage: build_mozc.py COMMAND [ARGS]'
548
print ' gyp Generate project files.'
549
print ' build Build the specified target.'
550
print ' build_tools Build tools used by the build command.'
551
print ' clean Clean all the build files and directories.'
553
print 'See also the comment in the script for typical usage.'
558
if len(sys.argv) < 2:
561
# DEPS files should exist in the same directory of the script.
562
deps_file_name = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),
564
# Remember the original current directory name.
565
original_directory_name = os.getcwd()
566
# Move to the top level source directory only once since os.chdir
567
# affects functions in os.path and that causes troublesome errors.
568
MoveToTopLevelSourceDirectory()
570
command = sys.argv[1]
571
del(sys.argv[1]) # Delete the command.
572
if command == 'build':
573
BuildMain(original_directory_name)
574
elif command == 'build_tools':
575
BuildToolsMain(original_directory_name)
576
elif command == 'clean':
578
elif command == 'gyp':
579
GypMain(deps_file_name)
581
print 'Unknown command: ' + command
584
if __name__ == '__main__':