build_tc.py revision f2a3ef46f75d2196a93d3ed27f4d1fcf22b54fbe
1#!/usr/bin/python
2#
3# Copyright 2010 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Script to build the ChromeOS toolchain.
7
8This script sets up the toolchain if you give it the gcctools directory.
9"""
10
11__author__ = 'asharif@google.com (Ahmad Sharif)'
12
13import getpass
14import optparse
15import os
16import sys
17import tempfile
18
19import tc_enter_chroot
20from utils import command_executer
21from utils import constants
22from utils import misc
23
24
25class ToolchainPart(object):
26
27  def __init__(self,
28               name,
29               source_path,
30               chromeos_root,
31               board,
32               incremental,
33               build_env,
34               gcc_enable_ccache=False):
35    self._name = name
36    self._source_path = misc.CanonicalizePath(source_path)
37    self._chromeos_root = chromeos_root
38    self._board = board
39    self._ctarget = misc.GetCtargetFromBoard(self._board, self._chromeos_root)
40    self._gcc_libs_dest = misc.GetGccLibsDestForBoard(self._board,
41                                                      self._chromeos_root)
42    self.tag = '%s-%s' % (name, self._ctarget)
43    self._ce = command_executer.GetCommandExecuter()
44    self._mask_file = os.path.join(self._chromeos_root, 'chroot',
45                                   'etc/portage/package.mask/cross-%s' %
46                                   self._ctarget)
47    self._new_mask_file = None
48
49    self._chroot_source_path = os.path.join(constants.MOUNTED_TOOLCHAIN_ROOT,
50                                            self._name).lstrip('/')
51    self._incremental = incremental
52    self._build_env = build_env
53    self._gcc_enable_ccache = gcc_enable_ccache
54
55  def RunSetupBoardIfNecessary(self):
56    cross_symlink = os.path.join(self._chromeos_root, 'chroot',
57                                 'usr/local/bin/emerge-%s' % self._board)
58    if not os.path.exists(cross_symlink):
59      command = ('%s/setup_board --board=%s' %
60                 (misc.CHROMEOS_SCRIPTS_DIR, self._board))
61      self._ce.ChrootRunCommand(self._chromeos_root, command)
62
63  def Build(self):
64    rv = 1
65    try:
66      self.UninstallTool()
67      self.MoveMaskFile()
68      self.MountSources(False)
69      self.RemoveCompiledFile()
70      rv = self.BuildTool()
71    finally:
72      self.UnMoveMaskFile()
73    return rv
74
75  def RemoveCompiledFile(self):
76    compiled_file = os.path.join(self._chromeos_root, 'chroot',
77                                 'var/tmp/portage/cross-%s' % self._ctarget,
78                                 '%s-9999' % self._name, '.compiled')
79    command = 'rm -f %s' % compiled_file
80    self._ce.RunCommand(command)
81
82  def MountSources(self, unmount_source):
83    mount_points = []
84    mounted_source_path = os.path.join(self._chromeos_root, 'chroot',
85                                       self._chroot_source_path)
86    src_mp = tc_enter_chroot.MountPoint(self._source_path, mounted_source_path,
87                                        getpass.getuser(), 'ro')
88    mount_points.append(src_mp)
89
90    build_suffix = 'build-%s' % self._ctarget
91    build_dir = '%s-%s' % (self._source_path, build_suffix)
92
93    if not self._incremental and os.path.exists(build_dir):
94      command = 'rm -rf %s/*' % build_dir
95      self._ce.RunCommand(command)
96
97    # Create a -build directory for the objects.
98    command = 'mkdir -p %s' % build_dir
99    self._ce.RunCommand(command)
100
101    mounted_build_dir = os.path.join(self._chromeos_root, 'chroot', '%s-%s' %
102                                     (self._chroot_source_path, build_suffix))
103    build_mp = tc_enter_chroot.MountPoint(build_dir, mounted_build_dir,
104                                          getpass.getuser())
105    mount_points.append(build_mp)
106
107    if unmount_source:
108      unmount_statuses = [mp.UnMount() == 0 for mp in mount_points]
109      assert all(unmount_statuses), 'Could not unmount all mount points!'
110    else:
111      mount_statuses = [mp.DoMount() == 0 for mp in mount_points]
112
113      if not all(mount_statuses):
114        mounted = [mp
115                   for mp, status in zip(mount_points, mount_statuses)
116                   if status]
117        unmount_statuses = [mp.UnMount() == 0 for mp in mounted]
118        assert all(unmount_statuses), 'Could not unmount all mount points!'
119
120  def UninstallTool(self):
121    command = 'sudo CLEAN_DELAY=0 emerge -C cross-%s/%s' % (self._ctarget,
122                                                            self._name)
123    self._ce.ChrootRunCommand(self._chromeos_root, command)
124
125  def BuildTool(self):
126    env = self._build_env
127    # FEATURES=buildpkg adds minutes of time so we disable it.
128    # TODO(shenhan): keep '-sandbox' for a while for compatibility, then remove
129    # it after a while.
130    features = ('nostrip userpriv userfetch -usersandbox -sandbox noclean '
131                '-buildpkg')
132    env['FEATURES'] = features
133
134    if self._incremental:
135      env['FEATURES'] += ' keepwork'
136
137    if 'USE' in env:
138      env['USE'] += ' multislot mounted_%s' % self._name
139    else:
140      env['USE'] = 'multislot mounted_%s' % self._name
141
142    # Disable ccache in our compilers. cache may be problematic for us.
143    # It ignores compiler environments settings and it is not clear if
144    # the cache hit algorithm verifies all the compiler binaries or
145    # just the driver.
146    if self._name == 'gcc' and not self._gcc_enable_ccache:
147      env['USE'] += ' -wrapper_ccache'
148
149    env['%s_SOURCE_PATH' % self._name.upper()] = (
150        os.path.join('/', self._chroot_source_path))
151    env['ACCEPT_KEYWORDS'] = '~*'
152    env_string = ' '.join(["%s=\"%s\"" % var for var in env.items()])
153    command = 'emerge =cross-%s/%s-9999' % (self._ctarget, self._name)
154    full_command = 'sudo %s %s' % (env_string, command)
155    rv = self._ce.ChrootRunCommand(self._chromeos_root, full_command)
156    if rv != 0:
157      return rv
158    if self._name == 'gcc':
159      command = ('sudo cp -r /usr/lib/gcc/%s %s' %
160                 (self._ctarget, self._gcc_libs_dest))
161      rv = self._ce.ChrootRunCommand(self._chromeos_root, command)
162    return rv
163
164  def MoveMaskFile(self):
165    self._new_mask_file = None
166    if os.path.isfile(self._mask_file):
167      self._new_mask_file = tempfile.mktemp()
168      command = 'sudo mv %s %s' % (self._mask_file, self._new_mask_file)
169      self._ce.RunCommand(command)
170
171  def UnMoveMaskFile(self):
172    if self._new_mask_file:
173      command = 'sudo mv %s %s' % (self._new_mask_file, self._mask_file)
174      self._ce.RunCommand(command)
175
176
177def Main(argv):
178  """The main function."""
179  # Common initializations
180  parser = optparse.OptionParser()
181  parser.add_option('-c',
182                    '--chromeos_root',
183                    dest='chromeos_root',
184                    default='../../',
185                    help=('ChromeOS root checkout directory'
186                          ' uses ../.. if none given.'))
187  parser.add_option('-g',
188                    '--gcc_dir',
189                    dest='gcc_dir',
190                    help='The directory where gcc resides.')
191  parser.add_option('--binutils_dir',
192                    dest='binutils_dir',
193                    help='The directory where binutils resides.')
194  parser.add_option('-x',
195                    '--gdb_dir',
196                    dest='gdb_dir',
197                    help='The directory where gdb resides.')
198  parser.add_option('-b',
199                    '--board',
200                    dest='board',
201                    default='x86-agz',
202                    help='The target board.')
203  parser.add_option('-n',
204                    '--noincremental',
205                    dest='noincremental',
206                    default=False,
207                    action='store_true',
208                    help='Use FEATURES=keepwork to do incremental builds.')
209  parser.add_option('--cflags',
210                    dest='cflags',
211                    default='',
212                    help='Build a compiler with specified CFLAGS')
213  parser.add_option('--cxxflags',
214                    dest='cxxflags',
215                    default='',
216                    help='Build a compiler with specified CXXFLAGS')
217  parser.add_option('--cflags_for_target',
218                    dest='cflags_for_target',
219                    default='',
220                    help='Build the target libraries with specified flags')
221  parser.add_option('--cxxflags_for_target',
222                    dest='cxxflags_for_target',
223                    default='',
224                    help='Build the target libraries with specified flags')
225  parser.add_option('--ldflags',
226                    dest='ldflags',
227                    default='',
228                    help='Build a compiler with specified LDFLAGS')
229  parser.add_option('-d',
230                    '--debug',
231                    dest='debug',
232                    default=False,
233                    action='store_true',
234                    help='Build a compiler with -g3 -O0 appended to both'
235                    ' CFLAGS and CXXFLAGS.')
236  parser.add_option('-m',
237                    '--mount_only',
238                    dest='mount_only',
239                    default=False,
240                    action='store_true',
241                    help='Just mount the tool directories.')
242  parser.add_option('-u',
243                    '--unmount_only',
244                    dest='unmount_only',
245                    default=False,
246                    action='store_true',
247                    help='Just unmount the tool directories.')
248  parser.add_option('--extra_use_flags',
249                    dest='extra_use_flags',
250                    default='',
251                    help='Extra flag for USE, to be passed to the ebuild. '
252                    "('multislot' and 'mounted_<tool>' are always passed.)")
253  parser.add_option('--gcc_enable_ccache',
254                    dest='gcc_enable_ccache',
255                    default=False,
256                    action='store_true',
257                    help='Enable ccache for the gcc invocations')
258
259  options, _ = parser.parse_args(argv)
260
261  chromeos_root = misc.CanonicalizePath(options.chromeos_root)
262  if options.gcc_dir:
263    gcc_dir = misc.CanonicalizePath(options.gcc_dir)
264    assert gcc_dir and os.path.isdir(gcc_dir), 'gcc_dir does not exist!'
265  if options.binutils_dir:
266    binutils_dir = misc.CanonicalizePath(options.binutils_dir)
267    assert os.path.isdir(binutils_dir), 'binutils_dir does not exist!'
268  if options.gdb_dir:
269    gdb_dir = misc.CanonicalizePath(options.gdb_dir)
270    assert os.path.isdir(gdb_dir), 'gdb_dir does not exist!'
271  if options.unmount_only:
272    options.mount_only = False
273  elif options.mount_only:
274    options.unmount_only = False
275  build_env = {}
276  if options.cflags:
277    build_env['CFLAGS'] = '`portageq envvar CFLAGS` ' + options.cflags
278  if options.cxxflags:
279    build_env['CXXFLAGS'] = '`portageq envvar CXXFLAGS` ' + options.cxxflags
280  if options.cflags_for_target:
281    build_env['CFLAGS_FOR_TARGET'] = options.cflags_for_target
282  if options.cxxflags_for_target:
283    build_env['CXXFLAGS_FOR_TARGET'] = options.cxxflags_for_target
284  if options.ldflags:
285    build_env['LDFLAGS'] = options.ldflags
286  if options.debug:
287    debug_flags = '-g3 -O0'
288    if 'CFLAGS' in build_env:
289      build_env['CFLAGS'] += ' %s' % (debug_flags)
290    else:
291      build_env['CFLAGS'] = debug_flags
292    if 'CXXFLAGS' in build_env:
293      build_env['CXXFLAGS'] += ' %s' % (debug_flags)
294    else:
295      build_env['CXXFLAGS'] = debug_flags
296  if options.extra_use_flags:
297    build_env['USE'] = options.extra_use_flags
298
299  # Create toolchain parts
300  toolchain_parts = {}
301  for board in options.board.split(','):
302    if options.gcc_dir:
303      tp = ToolchainPart('gcc', gcc_dir, chromeos_root, board,
304                         not options.noincremental, build_env,
305                         options.gcc_enable_ccache)
306      toolchain_parts[tp.tag] = tp
307      tp.RunSetupBoardIfNecessary()
308    if options.binutils_dir:
309      tp = ToolchainPart('binutils', binutils_dir, chromeos_root, board,
310                         not options.noincremental, build_env)
311      toolchain_parts[tp.tag] = tp
312      tp.RunSetupBoardIfNecessary()
313    if options.gdb_dir:
314      tp = ToolchainPart('gdb', gdb_dir, chromeos_root, board,
315                         not options.noincremental, build_env)
316      toolchain_parts[tp.tag] = tp
317      tp.RunSetupBoardIfNecessary()
318
319  rv = 0
320  try:
321    for tag in toolchain_parts:
322      tp = toolchain_parts[tag]
323      if options.mount_only or options.unmount_only:
324        tp.MountSources(options.unmount_only)
325      else:
326        rv = rv + tp.Build()
327  finally:
328    print 'Exiting...'
329  return rv
330
331
332if __name__ == '__main__':
333  retval = Main(sys.argv)
334  sys.exit(retval)
335