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