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