build_tc.py revision e4b6f220f3cc7d3277daca7bca25f89d648dae8e
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    rv = self._ce.ChrootRunCommand(self._chromeos_root, full_command)
155    if rv != 0:
156      return rv
157    if self._name == "gcc":
158      command = ("sudo cp -r /usr/lib/gcc/%s /build/%s/usr/lib/gcc/." %
159                 (self._ctarget, self._board))
160      rv = self._ce.ChrootRunCommand(self._chromeos_root, command)
161    return rv
162
163  def MoveMaskFile(self):
164    self._new_mask_file = None
165    if os.path.isfile(self._mask_file):
166      self._new_mask_file = tempfile.mktemp()
167      command = "sudo mv %s %s" % (self._mask_file, self._new_mask_file)
168      self._ce.RunCommand(command)
169
170  def UnMoveMaskFile(self):
171    if self._new_mask_file:
172      command = "sudo mv %s %s" % (self._new_mask_file, self._mask_file)
173      self._ce.RunCommand(command)
174
175
176def Main(argv):
177  """The main function."""
178  # Common initializations
179  parser = optparse.OptionParser()
180  parser.add_option("-c",
181                    "--chromeos_root",
182                    dest="chromeos_root",
183                    default="../../",
184                    help=("ChromeOS root checkout directory"
185                          " uses ../.. if none given."))
186  parser.add_option("-g",
187                    "--gcc_dir",
188                    dest="gcc_dir",
189                    help="The directory where gcc resides.")
190  parser.add_option("--binutils_dir",
191                    dest="binutils_dir",
192                    help="The directory where binutils resides.")
193  parser.add_option("-x",
194                    "--gdb_dir",
195                    dest="gdb_dir",
196                    help="The directory where gdb resides.")
197  parser.add_option("-b",
198                    "--board",
199                    dest="board",
200                    default="x86-agz",
201                    help="The target board.")
202  parser.add_option("-n",
203                    "--noincremental",
204                    dest="noincremental",
205                    default=False,
206                    action="store_true",
207                    help="Use FEATURES=keepwork to do incremental builds.")
208  parser.add_option("--cflags",
209                    dest="cflags",
210                    default="",
211                    help="Build a compiler with specified CFLAGS")
212  parser.add_option("--cxxflags",
213                    dest="cxxflags",
214                    default="",
215                    help="Build a compiler with specified CXXFLAGS")
216  parser.add_option("--cflags_for_target",
217                    dest="cflags_for_target",
218                    default="",
219                    help="Build the target libraries with specified flags")
220  parser.add_option("--cxxflags_for_target",
221                    dest="cxxflags_for_target",
222                    default="",
223                    help="Build the target libraries with specified flags")
224  parser.add_option("--ldflags",
225                    dest="ldflags",
226                    default="",
227                    help="Build a compiler with specified LDFLAGS")
228  parser.add_option("-d",
229                    "--debug",
230                    dest="debug",
231                    default=False,
232                    action="store_true",
233                    help="Build a compiler with -g3 -O0 appended to both"
234                    " CFLAGS and CXXFLAGS.")
235  parser.add_option("-m",
236                    "--mount_only",
237                    dest="mount_only",
238                    default=False,
239                    action="store_true",
240                    help="Just mount the tool directories.")
241  parser.add_option("-u",
242                    "--unmount_only",
243                    dest="unmount_only",
244                    default=False,
245                    action="store_true",
246                    help="Just unmount the tool directories.")
247  parser.add_option("--extra_use_flags",
248                    dest="extra_use_flags",
249                    default="",
250                    help="Extra flag for USE, to be passed to the ebuild. "
251                    "('multislot' and 'mounted_<tool>' are always passed.)")
252  parser.add_option("--gcc_enable_ccache",
253                    dest="gcc_enable_ccache",
254                    default=False,
255                    action="store_true",
256                    help="Enable ccache for the gcc invocations")
257
258  options, _ = parser.parse_args(argv)
259
260  chromeos_root = misc.CanonicalizePath(options.chromeos_root)
261  if options.gcc_dir:
262    gcc_dir = misc.CanonicalizePath(options.gcc_dir)
263    assert gcc_dir and os.path.isdir(gcc_dir), "gcc_dir does not exist!"
264  if options.binutils_dir:
265    binutils_dir = misc.CanonicalizePath(options.binutils_dir)
266    assert os.path.isdir(binutils_dir), "binutils_dir does not exist!"
267  if options.gdb_dir:
268    gdb_dir = misc.CanonicalizePath(options.gdb_dir)
269    assert os.path.isdir(gdb_dir), "gdb_dir does not exist!"
270  if options.unmount_only:
271    options.mount_only = False
272  elif options.mount_only:
273    options.unmount_only = False
274  build_env = {}
275  if options.cflags:
276    build_env["CFLAGS"] = "`portageq envvar CFLAGS` " + options.cflags
277  if options.cxxflags:
278    build_env["CXXFLAGS"] = "`portageq envvar CXXFLAGS` " + options.cxxflags
279  if options.cflags_for_target:
280    build_env["CFLAGS_FOR_TARGET"] = options.cflags_for_target
281  if options.cxxflags_for_target:
282    build_env["CXXFLAGS_FOR_TARGET"] = options.cxxflags_for_target
283  if options.ldflags:
284    build_env["LDFLAGS"] = options.ldflags
285  if options.debug:
286    debug_flags = "-g3 -O0"
287    if "CFLAGS" in build_env:
288      build_env["CFLAGS"] += " %s" % (debug_flags)
289    else:
290      build_env["CFLAGS"] = debug_flags
291    if "CXXFLAGS" in build_env:
292      build_env["CXXFLAGS"] += " %s" % (debug_flags)
293    else:
294      build_env["CXXFLAGS"] = debug_flags
295  if options.extra_use_flags:
296    build_env["USE"] = options.extra_use_flags
297
298  # Create toolchain parts
299  toolchain_parts = {}
300  for board in options.board.split(","):
301    if options.gcc_dir:
302      tp = ToolchainPart("gcc", gcc_dir, chromeos_root, board,
303                         not options.noincremental, build_env,
304                         options.gcc_enable_ccache)
305      toolchain_parts[tp.tag] = tp
306      tp.RunSetupBoardIfNecessary()
307    if options.binutils_dir:
308      tp = ToolchainPart("binutils", binutils_dir, chromeos_root, board,
309                         not options.noincremental, build_env)
310      toolchain_parts[tp.tag] = tp
311      tp.RunSetupBoardIfNecessary()
312    if options.gdb_dir:
313      tp = ToolchainPart("gdb", gdb_dir, chromeos_root, board,
314                         not options.noincremental, build_env)
315      toolchain_parts[tp.tag] = tp
316      tp.RunSetupBoardIfNecessary()
317
318  rv = 0
319  try:
320    for tag in toolchain_parts:
321      tp = toolchain_parts[tag]
322      if options.mount_only or options.unmount_only:
323        tp.MountSources(options.unmount_only)
324      else:
325        rv = rv + tp.Build()
326  finally:
327    print "Exiting..."
328  return rv
329
330if __name__ == "__main__":
331  retval = Main(sys.argv)
332  sys.exit(retval)
333