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