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