Home | History | Annotate | Download | only in toolchain-utils
      1 #!/usr/bin/env python2
      2 """Script to bootstrap the chroot using new toolchain.
      3 
      4 This script allows you to build/install a customized version of gcc/binutils,
      5 either by specifying branch or a local directory.
      6 
      7 This script must be executed outside chroot.
      8 
      9 Below is some typical usage -
     10 
     11 ## Build gcc located at /local/gcc/dir and do a bootstrap using the new
     12 ## compiler for the chromeos root.  The script tries to find a valid chromeos
     13 ## tree all the way up from your current working directory.
     14 ./build_tool.py --gcc_dir=/loca/gcc/dir --bootstrap
     15 
     16 ## Build binutils, using remote branch "mobile_toolchain_v17" and do a
     17 ## bootstrap using the new binutils for the chromeos root. The script tries to
     18 ## find a valid chromeos tree all the way up from your current working
     19 ## directory.
     20 ./build_tool.py --binutils_branch=cros/mobile_toolchain_v17 \
     21     --chromeos_root=/chromeos/dir --bootstrap
     22 
     23 ## Same as above except only do it for board daisy - no bootstrapping involved.
     24 ./build_tool.py --binutils_branch=cros/mobile_toolchain_v16 \
     25     --chromeos_root=/chromeos/dir --board=daisy
     26 """
     27 
     28 from __future__ import print_function
     29 
     30 __author__ = 'shenhan (at] google.com (Han Shen)'
     31 
     32 import argparse
     33 import os
     34 import re
     35 import sys
     36 
     37 from cros_utils import command_executer
     38 from cros_utils import logger
     39 from cros_utils import misc
     40 import repo_to_repo
     41 
     42 REPO_PATH_PATTERN = 'src/third_party/{0}'
     43 TEMP_BRANCH_NAME = 'internal_testing_branch_no_use'
     44 CHROMIUMOS_OVERLAY_PATH = 'src/third_party/chromiumos-overlay'
     45 EBUILD_PATH_PATTERN = 'src/third_party/chromiumos-overlay/sys-devel/{0}'
     46 
     47 
     48 class Bootstrapper(object):
     49   """Class that handles bootstrap process."""
     50 
     51   def __init__(self,
     52                chromeos_root,
     53                ndk_dir,
     54                gcc_branch=None,
     55                gcc_dir=None,
     56                binutils_branch=None,
     57                binutils_dir=None,
     58                board=None,
     59                disable_2nd_bootstrap=False,
     60                setup_tool_ebuild_file_only=False):
     61     self._chromeos_root = chromeos_root
     62     self._ndk_dir = ndk_dir
     63 
     64     self._gcc_branch = gcc_branch
     65     self._gcc_branch_tree = None
     66     self._gcc_dir = gcc_dir
     67     self._gcc_ebuild_file = None
     68     self._gcc_ebuild_file_name = None
     69 
     70     self._binutils_branch = binutils_branch
     71     self._binutils_branch_tree = None
     72     self._binutils_dir = binutils_dir
     73     self._binutils_ebuild_file = None
     74     self._binutils_ebuild_file_name = None
     75 
     76     self._setup_tool_ebuild_file_only = setup_tool_ebuild_file_only
     77 
     78     self._ce = command_executer.GetCommandExecuter()
     79     self._logger = logger.GetLogger()
     80     self._board = board
     81     self._disable_2nd_bootstrap = disable_2nd_bootstrap
     82 
     83   def IsTreeSame(self, t1, t2):
     84     diff = 'diff -qr -x .git -x .svn "{0}" "{1}"'.format(t1, t2)
     85     if self._ce.RunCommand(diff, print_to_console=False) == 0:
     86       self._logger.LogOutput('"{0}" and "{1}" are the same."'.format(t1, t2))
     87       return True
     88     self._logger.LogWarning('"{0}" and "{1}" are different."'.format(t1, t2))
     89     return False
     90 
     91   def SubmitToLocalBranch(self):
     92     """Copy source code to the chromium source tree and submit it locally."""
     93     if self._gcc_dir:
     94       if not self.SubmitToolToLocalBranch(
     95           tool_name='gcc', tool_dir=self._gcc_dir):
     96         return False
     97       self._gcc_branch = TEMP_BRANCH_NAME
     98 
     99     if self._binutils_dir:
    100       if not self.SubmitToolToLocalBranch(
    101           tool_name='binutils', tool_dir=self._binutils_dir):
    102         return False
    103       self._binutils_branch = TEMP_BRANCH_NAME
    104 
    105     return True
    106 
    107   def SubmitToolToLocalBranch(self, tool_name, tool_dir):
    108     """Copy the source code to local chromium source tree.
    109 
    110     Args:
    111       tool_name: either 'gcc' or 'binutils'
    112       tool_dir: the tool source dir to be used
    113 
    114     Returns:
    115       True if all succeeded False otherwise.
    116     """
    117 
    118     # The next few steps creates an internal branch to sync with the tool dir
    119     # user provided.
    120     chrome_tool_dir = self.GetChromeOsToolDir(tool_name)
    121 
    122     # 0. Test to see if git tree is free of local changes.
    123     if not misc.IsGitTreeClean(chrome_tool_dir):
    124       self._logger.LogError(
    125           'Git repository "{0}" not clean, aborted.'.format(chrome_tool_dir))
    126       return False
    127 
    128     # 1. Checkout/create a (new) branch for testing.
    129     command = 'cd "{0}" && git checkout -B {1}'.format(chrome_tool_dir,
    130                                                        TEMP_BRANCH_NAME)
    131     ret = self._ce.RunCommand(command)
    132     if ret:
    133       self._logger.LogError('Failed to create a temp branch for test, aborted.')
    134       return False
    135 
    136     if self.IsTreeSame(tool_dir, chrome_tool_dir):
    137       self._logger.LogOutput('"{0}" and "{1}" are the same, sync skipped.'.
    138                              format(tool_dir, chrome_tool_dir))
    139       return True
    140 
    141     # 2. Sync sources from user provided tool dir to chromiumos tool git.
    142     local_tool_repo = repo_to_repo.FileRepo(tool_dir)
    143     chrome_tool_repo = repo_to_repo.GitRepo(chrome_tool_dir, TEMP_BRANCH_NAME)
    144     chrome_tool_repo.SetRoot(chrome_tool_dir)
    145     # Delete all stuff except '.git' before start mapping.
    146     self._ce.RunCommand(
    147         'cd {0} && find . -maxdepth 1 -not -name ".git" -not -name "." '
    148         r'\( -type f -exec rm {{}} \; -o '
    149         r'   -type d -exec rm -fr {{}} \; \)'.format(chrome_tool_dir))
    150     local_tool_repo.MapSources(chrome_tool_repo.GetRoot())
    151 
    152     # 3. Ensure after sync tree is the same.
    153     if self.IsTreeSame(tool_dir, chrome_tool_dir):
    154       self._logger.LogOutput('Sync successfully done.')
    155     else:
    156       self._logger.LogError('Sync not successful, aborted.')
    157       return False
    158 
    159     # 4. Commit all changes.
    160     # 4.1 Try to get some information about the tool dir we are using.
    161     cmd = 'cd {0} && git log -1 --pretty=oneline'.format(tool_dir)
    162     tool_dir_extra_info = None
    163     ret, tool_dir_extra_info, _ = self._ce.RunCommandWOutput(
    164         cmd, print_to_console=False)
    165     commit_message = 'Synced with tool source tree at - "{0}".'.format(tool_dir)
    166     if not ret:
    167       commit_message += '\nGit log for {0}:\n{1}'.format(
    168           tool_dir, tool_dir_extra_info.strip())
    169 
    170     if chrome_tool_repo.CommitLocally(commit_message):
    171       self._logger.LogError('Commit to local branch "{0}" failed, aborted.'.
    172                             format(TEMP_BRANCH_NAME))
    173       return False
    174     return True
    175 
    176   def CheckoutBranch(self):
    177     """Checkout working branch for the tools.
    178 
    179     Returns:
    180       True: if operation succeeds.
    181     """
    182 
    183     if self._gcc_branch:
    184       rv = self.CheckoutToolBranch('gcc', self._gcc_branch)
    185       if rv:
    186         self._gcc_branch_tree = rv
    187       else:
    188         return False
    189 
    190     if self._binutils_branch:
    191       rv = self.CheckoutToolBranch('binutils', self._binutils_branch)
    192       if rv:
    193         self._binutils_branch_tree = rv
    194       else:
    195         return False
    196 
    197     return True
    198 
    199   def CheckoutToolBranch(self, tool_name, tool_branch):
    200     """Checkout the tool branch for a certain tool.
    201 
    202     Args:
    203       tool_name: either 'gcc' or 'binutils'
    204       tool_branch: tool branch to use
    205 
    206     Returns:
    207       True: if operation succeeds. Otherwise False.
    208     """
    209 
    210     chrome_tool_dir = self.GetChromeOsToolDir(tool_name)
    211     command = 'cd "{0}" && git checkout {1}'.format(chrome_tool_dir,
    212                                                     tool_branch)
    213     if not self._ce.RunCommand(command, print_to_console=True):
    214       # Get 'TREE' value of this commit
    215       command = ('cd "{0}" && git cat-file -p {1} '
    216                  '| grep -E "^tree [a-f0-9]+$" '
    217                  '| cut -d" " -f2').format(chrome_tool_dir, tool_branch)
    218       ret, stdout, _ = self._ce.RunCommandWOutput(
    219           command, print_to_console=False)
    220       # Pipe operation always has a zero return value. So need to check if
    221       # stdout is valid.
    222       if not ret and stdout and re.match('[0-9a-h]{40}',
    223                                          stdout.strip(), re.IGNORECASE):
    224         tool_branch_tree = stdout.strip()
    225         self._logger.LogOutput('Find tree for {0} branch "{1}" - "{2}"'.format(
    226             tool_name, tool_branch, tool_branch_tree))
    227         return tool_branch_tree
    228     self._logger.LogError(('Failed to checkout "{0}" or failed to '
    229                            'get tree value, aborted.').format(tool_branch))
    230     return None
    231 
    232   def FindEbuildFile(self):
    233     """Find the ebuild files for the tools.
    234 
    235     Returns:
    236       True: if operation succeeds.
    237     """
    238 
    239     if self._gcc_branch:
    240       (rv, ef, efn) = self.FindToolEbuildFile('gcc')
    241       if rv:
    242         self._gcc_ebuild_file = ef
    243         self._gcc_ebuild_file_name = efn
    244       else:
    245         return False
    246 
    247     if self._binutils_branch:
    248       (rv, ef, efn) = self.FindToolEbuildFile('binutils')
    249       if rv:
    250         self._binutils_ebuild_file = ef
    251         self._binutils_ebuild_file_name = efn
    252       else:
    253         return False
    254 
    255     return True
    256 
    257   def FindToolEbuildFile(self, tool_name):
    258     """Find ebuild file for a specific tool.
    259 
    260     Args:
    261       tool_name: either "gcc" or "binutils".
    262 
    263     Returns:
    264       A triplet that consisits of whether operation succeeds or not,
    265       tool ebuild file full path and tool ebuild file name.
    266     """
    267 
    268     # To get the active gcc ebuild file, we need a workable chroot first.
    269     if not os.path.exists(
    270         os.path.join(self._chromeos_root, 'chroot')) and self._ce.RunCommand(
    271             'cd "{0}" && cros_sdk --create'.format(self._chromeos_root)):
    272       self._logger.LogError(('Failed to install a initial chroot, aborted.\n'
    273                              'If previous bootstrap failed, do a '
    274                              '"cros_sdk --delete" to remove '
    275                              'in-complete chroot.'))
    276       return (False, None, None)
    277 
    278     rv, stdout, _ = self._ce.ChrootRunCommandWOutput(
    279         self._chromeos_root,
    280         'equery w sys-devel/{0}'.format(tool_name),
    281         print_to_console=True)
    282     if rv:
    283       self._logger.LogError(
    284           ('Failed to execute inside chroot '
    285            '"equery w sys-devel/{0}", aborted.').format(tool_name))
    286       return (False, None, None)
    287     m = re.match(r'^.*/({0}/(.*\.ebuild))$'.format(
    288         EBUILD_PATH_PATTERN.format(tool_name)), stdout)
    289     if not m:
    290       self._logger.LogError(
    291           ('Failed to find {0} ebuild file, aborted. '
    292            'If previous bootstrap failed, do a "cros_sdk --delete" to remove '
    293            'in-complete chroot.').format(tool_name))
    294       return (False, None, None)
    295     tool_ebuild_file = os.path.join(self._chromeos_root, m.group(1))
    296     tool_ebuild_file_name = m.group(2)
    297 
    298     return (True, tool_ebuild_file, tool_ebuild_file_name)
    299 
    300   def InplaceModifyEbuildFile(self):
    301     """Modify the ebuild file.
    302 
    303     Returns:
    304       True if operation succeeds.
    305     """
    306 
    307     # Note we shall not use remote branch name (eg. "cros/gcc.gnu.org/...") in
    308     # CROS_WORKON_COMMIT, we have to use GITHASH. So we call GitGetCommitHash on
    309     # tool_branch.
    310     tool = None
    311     toolbranch = None
    312     if self._gcc_branch:
    313       tool = 'gcc'
    314       toolbranch = self._gcc_branch
    315       tooltree = self._gcc_branch_tree
    316       toolebuild = self._gcc_ebuild_file
    317     elif self._binutils_branch:
    318       tool = 'binutils'
    319       toolbranch = self._binutils_branch
    320       tooltree = self._binutils_branch_tree
    321       toolebuild = self._binutils_ebuild_file
    322 
    323     assert tool
    324 
    325     # An example for the following variables would be:
    326     #   tooldir = '~/android/master-ndk/toolchain/gcc/gcc-4.9'
    327     #   tool_branch_githash = xxxxx
    328     #   toolcomponents = toolchain/gcc
    329     tooldir = self.GetChromeOsToolDir(tool)
    330     toolgithash = misc.GitGetCommitHash(tooldir, toolbranch)
    331     if not toolgithash:
    332       return False
    333     toolcomponents = 'toolchain/{}'.format(tool)
    334     return self.InplaceModifyToolEbuildFile(toolcomponents, toolgithash,
    335                                             tooltree, toolebuild)
    336 
    337   @staticmethod
    338   def ResetToolEbuildFile(chromeos_root, tool_name):
    339     """Reset tool ebuild file to clean state.
    340 
    341     Args:
    342       chromeos_root: chromeos source tree
    343       tool_name: either "gcc" or "binutils"
    344 
    345     Returns:
    346       True if operation succeds.
    347     """
    348     rv = misc.GetGitChangesAsList(
    349         os.path.join(chromeos_root, CHROMIUMOS_OVERLAY_PATH),
    350         path=('sys-devel/{0}/{0}-*.ebuild'.format(tool_name)),
    351         staged=False)
    352     if rv:
    353       cmd = 'cd {0} && git checkout --'.format(
    354           os.path.join(chromeos_root, CHROMIUMOS_OVERLAY_PATH))
    355       for g in rv:
    356         cmd += ' ' + g
    357       rv = command_executer.GetCommandExecuter().RunCommand(cmd)
    358       if rv:
    359         logger.GetLogger().LogWarning(
    360             'Failed to reset the ebuild file. Please refer to log above.')
    361         return False
    362     else:
    363       logger.GetLogger().LogWarning(
    364           'Note - did not find any modified {0} ebuild file.'.format(tool_name))
    365       # Fall through
    366     return True
    367 
    368   def GetChromeOsToolDir(self, tool_name):
    369     """Return the chromeos git dir for a specific tool.
    370 
    371     Note, after we unified ChromeOs and Android, the tool dir is under
    372     ndk_dir/toolchain/[gcc,binutils].
    373 
    374     Args:
    375       tool_name: either 'gcc' or 'binutils'.
    376 
    377     Returns:
    378       Absolute git path for the tool.
    379     """
    380 
    381     tool_toppath = os.path.join(self._ndk_dir, 'toolchain', tool_name)
    382     # There may be sub-directories like 'binutils-2.25', 'binutils-2.24',
    383     # 'gcc-4.9', 'gcc-4.8', etc. find the newest binutils version.
    384     cmd = ('find {} -maxdepth 1 -type d -name "{}-*" '
    385            '| sort -r | head -1').format(tool_toppath, tool_name)
    386     rv, out, _ = self._ce.RunCommandWOutput(cmd, print_to_console=False)
    387     if rv:
    388       return None
    389     repo = out.strip()
    390 
    391     # cros-workon eclass expects every CROS_WORKON_PROJECT ends with ".git".
    392     self._ce.RunCommand(('cd $(dirname {0}) && '
    393                          'ln -sf $(basename {0}) $(basename {0}).git').format(
    394                              repo, print_to_console=True))
    395     return repo
    396 
    397   def InplaceModifyToolEbuildFile(self, tool_components, tool_branch_githash,
    398                                   tool_branch_tree, tool_ebuild_file):
    399     """Using sed to fill properly values into the ebuild file.
    400 
    401     Args:
    402       tool_components: either "toolchain/gcc" or "toolchain/binutils"
    403       tool_branch_githash: githash for tool_branch
    404       tool_branch_tree: treeish for the tool branch
    405       tool_ebuild_file: tool ebuild file
    406 
    407     Returns:
    408       True: if operation succeeded.
    409     """
    410 
    411     command = ('sed -i '
    412                '-e \'/^CROS_WORKON_REPO=".*"/i'
    413                ' # The following line is modified by script.\' '
    414                '-e \'s!^CROS_WORKON_REPO=".*"$!CROS_WORKON_REPO="{0}"!\' '
    415                '-e \'/^CROS_WORKON_PROJECT=".*"/i'
    416                ' # The following line is modified by script.\' '
    417                '-e \'s!^CROS_WORKON_PROJECT=.*$!CROS_WORKON_PROJECT="{1}"!\' '
    418                '-e \'/^CROS_WORKON_COMMIT=".*"/i'
    419                ' # The following line is modified by script.\' '
    420                '-e \'s!^CROS_WORKON_COMMIT=".*"$!CROS_WORKON_COMMIT="{2}"!\' '
    421                '-e \'/^CROS_WORKON_TREE=".*"/i'
    422                ' # The following line is modified by script.\' '
    423                '-e \'s!^CROS_WORKON_TREE=".*"$!CROS_WORKON_TREE="{3}"!\' '
    424                '{4}').format('/home/{}/ndk-root'.format(os.environ['USER']),
    425                              tool_components, tool_branch_githash,
    426                              tool_branch_tree, tool_ebuild_file)
    427     rv = self._ce.RunCommand(command)
    428     if rv:
    429       self._logger.LogError(
    430           'Failed to modify commit and tree value for "{0}"", aborted.'.format(
    431               tool_ebuild_file))
    432       return False
    433 
    434     # Warn that the ebuild file has been modified.
    435     self._logger.LogWarning(
    436         ('Ebuild file "{0}" is modified, to revert the file - \n'
    437          'bootstrap_compiler.py --chromeos_root={1} '
    438          '--reset_tool_ebuild_file').format(tool_ebuild_file,
    439                                             self._chromeos_root))
    440     return True
    441 
    442   def DoBuildForBoard(self):
    443     """Build tool for a specific board.
    444 
    445     Returns:
    446       True if operation succeeds.
    447     """
    448 
    449     if self._gcc_branch:
    450       if not self.DoBuildToolForBoard('gcc'):
    451         return False
    452     if self._binutils_branch:
    453       if not self.DoBuildToolForBoard('binutils'):
    454         return False
    455     return True
    456 
    457   def DoBuildToolForBoard(self, tool_name):
    458     """Build a specific tool for a specific board.
    459 
    460     Args:
    461       tool_name: either "gcc" or "binutils"
    462 
    463     Returns:
    464       True if operation succeeds.
    465     """
    466 
    467     chroot_ndk_root = os.path.join(self._chromeos_root, 'chroot', 'home',
    468                                    os.environ['USER'], 'ndk-root')
    469     self._ce.RunCommand('mkdir -p {}'.format(chroot_ndk_root))
    470     if self._ce.RunCommand(
    471         'sudo mount --bind {} {}'.format(self._ndk_dir, chroot_ndk_root)):
    472       self._logger.LogError('Failed to mount ndk dir into chroot')
    473       return False
    474 
    475     try:
    476       boards_to_build = self._board.split(',')
    477       target_built = set()
    478       failed = []
    479       for board in boards_to_build:
    480         if board == 'host':
    481           command = 'sudo emerge sys-devel/{0}'.format(tool_name)
    482         else:
    483           target = misc.GetCtargetFromBoard(board, self._chromeos_root)
    484           if not target:
    485             self._logger.LogError(
    486                 'Unsupported board "{0}", skip.'.format(board))
    487             failed.append(board)
    488             continue
    489           # Skip this board if we have already built for a board that has the
    490           # same target.
    491           if target in target_built:
    492             self._logger.LogWarning(
    493                 'Skipping toolchain for board "{}"'.format(board))
    494             continue
    495           target_built.add(target)
    496           command = 'sudo emerge cross-{0}/{1}'.format(target, tool_name)
    497 
    498         rv = self._ce.ChrootRunCommand(
    499             self._chromeos_root, command, print_to_console=True)
    500         if rv:
    501           self._logger.LogError(
    502               'Build {0} failed for {1}, aborted.'.format(tool_name, board))
    503           failed.append(board)
    504         else:
    505           self._logger.LogOutput(
    506               'Successfully built {0} for board {1}.'.format(tool_name, board))
    507     finally:
    508       # Make sure we un-mount ndk-root before we leave here, regardless of the
    509       # build result of the tool. Otherwise we may inadvertently delete ndk-root
    510       # dir, which is not part of the chroot and could be disastrous.
    511       if chroot_ndk_root:
    512         if self._ce.RunCommand('sudo umount {}'.format(chroot_ndk_root)):
    513           self._logger.LogWarning(
    514               ('Failed to umount "{}", please check '
    515                'before deleting chroot.').format(chroot_ndk_root))
    516 
    517       # Clean up soft links created during build.
    518       self._ce.RunCommand('cd {}/toolchain/{} && git clean -df'.format(
    519           self._ndk_dir, tool_name))
    520 
    521     if failed:
    522       self._logger.LogError(
    523           'Failed to build {0} for the following board(s): "{1}"'.format(
    524               tool_name, ' '.join(failed)))
    525       return False
    526     # All boards build successfully
    527     return True
    528 
    529   def DoBootstrapping(self):
    530     """Do bootstrapping the chroot.
    531 
    532     This step firstly downloads a prestine sdk, then use this sdk to build the
    533     new sdk, finally use the new sdk to build every host package.
    534 
    535     Returns:
    536       True if operation succeeds.
    537     """
    538 
    539     logfile = os.path.join(self._chromeos_root, 'bootstrap.log')
    540     command = 'cd "{0}" && cros_sdk --delete --bootstrap |& tee "{1}"'.format(
    541         self._chromeos_root, logfile)
    542     rv = self._ce.RunCommand(command, print_to_console=True)
    543     if rv:
    544       self._logger.LogError(
    545           'Bootstrapping failed, log file - "{0}"\n'.format(logfile))
    546       return False
    547 
    548     self._logger.LogOutput('Bootstrap succeeded.')
    549     return True
    550 
    551   def BuildAndInstallAmd64Host(self):
    552     """Build amd64-host (host) packages.
    553 
    554     Build all host packages in the newly-bootstrapped 'chroot' using *NEW*
    555     toolchain.
    556 
    557     So actually we perform 2 builds of all host packages -
    558       1. build new toolchain using old toolchain and build all host packages
    559          using the newly built toolchain
    560       2. build the new toolchain again but using new toolchain built in step 1,
    561          and build all host packages using the newly built toolchain
    562 
    563     Returns:
    564       True if operation succeeds.
    565     """
    566 
    567     cmd = ('cd {0} && cros_sdk -- -- ./setup_board --board=amd64-host '
    568            '--accept_licenses=@CHROMEOS --skip_chroot_upgrade --nousepkg '
    569            '--reuse_pkgs_from_local_boards').format(self._chromeos_root)
    570     rv = self._ce.RunCommand(cmd, print_to_console=True)
    571     if rv:
    572       self._logger.LogError('Build amd64-host failed.')
    573       return False
    574 
    575     # Package amd64-host into 'built-sdk.tar.xz'.
    576     sdk_package = os.path.join(self._chromeos_root, 'built-sdk.tar.xz')
    577     cmd = ('cd {0}/chroot/build/amd64-host && sudo XZ_OPT="-e9" '
    578            'tar --exclude="usr/lib/debug/*" --exclude="packages/*" '
    579            '--exclude="tmp/*" --exclude="usr/local/build/autotest/*" '
    580            '--sparse -I xz -vcf {1} . && sudo chmod a+r {1}').format(
    581                self._chromeos_root, sdk_package)
    582     rv = self._ce.RunCommand(cmd, print_to_console=True)
    583     if rv:
    584       self._logger.LogError('Failed to create "built-sdk.tar.xz".')
    585       return False
    586 
    587     # Install amd64-host into a new chroot.
    588     cmd = ('cd {0} && cros_sdk --chroot new-sdk-chroot --download --replace '
    589            '--nousepkg --url file://{1}').format(self._chromeos_root,
    590                                                  sdk_package)
    591     rv = self._ce.RunCommand(cmd, print_to_console=True)
    592     if rv:
    593       self._logger.LogError('Failed to install "built-sdk.tar.xz".')
    594       return False
    595     self._logger.LogOutput(
    596         'Successfully installed built-sdk.tar.xz into a new chroot.\nAll done.')
    597 
    598     # Rename the newly created new-sdk-chroot to chroot.
    599     cmd = ('cd {0} && sudo mv chroot chroot-old && '
    600            'sudo mv new-sdk-chroot chroot').format(self._chromeos_root)
    601     rv = self._ce.RunCommand(cmd, print_to_console=True)
    602     return rv == 0
    603 
    604   def Do(self):
    605     """Entrance of the class.
    606 
    607     Returns:
    608       True if everything is ok.
    609     """
    610 
    611     if (self.SubmitToLocalBranch() and self.CheckoutBranch() and
    612         self.FindEbuildFile() and self.InplaceModifyEbuildFile()):
    613       if self._setup_tool_ebuild_file_only:
    614         # Everything is done, we are good.
    615         ret = True
    616       else:
    617         if self._board:
    618           ret = self.DoBuildForBoard()
    619         else:
    620           # This implies '--bootstrap'.
    621           ret = (self.DoBootstrapping() and (self._disable_2nd_bootstrap or
    622                                              self.BuildAndInstallAmd64Host()))
    623     else:
    624       ret = False
    625     return ret
    626 
    627 
    628 def Main(argv):
    629   parser = argparse.ArgumentParser()
    630   parser.add_argument(
    631       '-c',
    632       '--chromeos_root',
    633       dest='chromeos_root',
    634       help=('Optional. ChromeOs root dir. '
    635             'When not specified, chromeos root will be deduced'
    636             ' from current working directory.'))
    637   parser.add_argument(
    638       '--ndk_dir',
    639       dest='ndk_dir',
    640       help=('Topmost android ndk dir, required. '
    641             'Do not need to include the "toolchain/*" part.'))
    642   parser.add_argument(
    643       '--gcc_branch',
    644       dest='gcc_branch',
    645       help=('The branch to test against. '
    646             'This branch must be a local branch '
    647             'inside "src/third_party/gcc". '
    648             'Notice, this must not be used with "--gcc_dir".'))
    649   parser.add_argument(
    650       '--binutils_branch',
    651       dest='binutils_branch',
    652       help=('The branch to test against binutils. '
    653             'This branch must be a local branch '
    654             'inside "src/third_party/binutils". '
    655             'Notice, this must not be used with '
    656             '"--binutils_dir".'))
    657   parser.add_argument(
    658       '-g',
    659       '--gcc_dir',
    660       dest='gcc_dir',
    661       help=('Use a local gcc tree to do bootstrapping. '
    662             'Notice, this must not be used with '
    663             '"--gcc_branch".'))
    664   parser.add_argument(
    665       '--binutils_dir',
    666       dest='binutils_dir',
    667       help=('Use a local binutils tree to do bootstrapping. '
    668             'Notice, this must not be used with '
    669             '"--binutils_branch".'))
    670   parser.add_argument(
    671       '--fixperm',
    672       dest='fixperm',
    673       default=False,
    674       action='store_true',
    675       help=('Fix the (notorious) permission error '
    676             'while trying to bootstrap the chroot. '
    677             'Note this takes an extra 10-15 minutes '
    678             'and is only needed once per chromiumos tree.'))
    679   parser.add_argument(
    680       '--setup_tool_ebuild_file_only',
    681       dest='setup_tool_ebuild_file_only',
    682       default=False,
    683       action='store_true',
    684       help=('Setup gcc and/or binutils ebuild file '
    685             'to pick up the branch (--gcc/binutils_branch) or '
    686             'use gcc and/or binutils source '
    687             '(--gcc/binutils_dir) and exit. Keep chroot as is.'
    688             ' This should not be used with '
    689             '--gcc/binutils_dir/branch options.'))
    690   parser.add_argument(
    691       '--reset_tool_ebuild_file',
    692       dest='reset_tool_ebuild_file',
    693       default=False,
    694       action='store_true',
    695       help=('Reset the modification that is done by this '
    696             'script. Note, when this script is running, it '
    697             'will modify the active gcc/binutils ebuild file. '
    698             'Use this option to reset (what this script has '
    699             'done) and exit. This should not be used with -- '
    700             'gcc/binutils_dir/branch options.'))
    701   parser.add_argument(
    702       '--board',
    703       dest='board',
    704       default=None,
    705       help=('Only build toolchain for specific board(s). '
    706             'Use "host" to build for host. '
    707             'Use "," to seperate multiple boards. '
    708             'This does not perform a chroot bootstrap.'))
    709   parser.add_argument(
    710       '--bootstrap',
    711       dest='bootstrap',
    712       default=False,
    713       action='store_true',
    714       help=('Performs a chroot bootstrap. '
    715             'Note, this will *destroy* your current chroot.'))
    716   parser.add_argument(
    717       '--disable-2nd-bootstrap',
    718       dest='disable_2nd_bootstrap',
    719       default=False,
    720       action='store_true',
    721       help=('Disable a second bootstrap '
    722             '(build of amd64-host stage).'))
    723 
    724   options = parser.parse_args(argv)
    725   # Trying to deduce chromeos root from current directory.
    726   if not options.chromeos_root:
    727     logger.GetLogger().LogOutput('Trying to deduce chromeos root ...')
    728     wdir = os.getcwd()
    729     while wdir and wdir != '/':
    730       if misc.IsChromeOsTree(wdir):
    731         logger.GetLogger().LogOutput('Find chromeos_root: {}'.format(wdir))
    732         options.chromeos_root = wdir
    733         break
    734       wdir = os.path.dirname(wdir)
    735 
    736   if not options.chromeos_root:
    737     parser.error('Missing or failing to deduce mandatory option "--chromeos".')
    738     return 1
    739 
    740   options.chromeos_root = os.path.abspath(
    741       os.path.expanduser(options.chromeos_root))
    742 
    743   if not os.path.isdir(options.chromeos_root):
    744     logger.GetLogger().LogError(
    745         '"{0}" does not exist.'.format(options.chromeos_root))
    746     return 1
    747 
    748   options.ndk_dir = os.path.expanduser(options.ndk_dir)
    749   if not options.ndk_dir:
    750     parser.error('Missing mandatory option "--ndk_dir".')
    751     return 1
    752 
    753   # Some tolerance regarding user input. We only need the ndk_root part, do not
    754   # include toolchain/(gcc|binutils)/ part in this option.
    755   options.ndk_dir = re.sub('/toolchain(/gcc|/binutils)?/?$', '',
    756                            options.ndk_dir)
    757 
    758   if not (os.path.isdir(options.ndk_dir) and
    759           os.path.isdir(os.path.join(options.ndk_dir, 'toolchain'))):
    760     logger.GetLogger().LogError(
    761         '"toolchain" directory not found under "{0}".'.format(options.ndk_dir))
    762     return 1
    763 
    764   if options.fixperm:
    765     # Fix perm error before continuing.
    766     cmd = (r'sudo find "{0}" \( -name ".cache" -type d -prune \) -o '
    767            r'\( -name "chroot" -type d -prune \) -o '
    768            r'\( -type f -exec chmod a+r {{}} \; \) -o '
    769            r'\( -type d -exec chmod a+rx {{}} \; \)'
    770           ).format(options.chromeos_root)
    771     logger.GetLogger().LogOutput(
    772         'Fixing perm issues for chromeos root, this might take some time.')
    773     command_executer.GetCommandExecuter().RunCommand(cmd)
    774 
    775   if options.reset_tool_ebuild_file:
    776     if (options.gcc_dir or options.gcc_branch or options.binutils_dir or
    777         options.binutils_branch):
    778       logger.GetLogger().LogWarning(
    779           'Ignoring any "--gcc/binutils_dir" and/or "--gcc/binutils_branch".')
    780     if options.setup_tool_ebuild_file_only:
    781       logger.GetLogger().LogError(
    782           ('Conflict options "--reset_tool_ebuild_file" '
    783            'and "--setup_tool_ebuild_file_only".'))
    784       return 1
    785     rv = Bootstrapper.ResetToolEbuildFile(options.chromeos_root, 'gcc')
    786     rv1 = Bootstrapper.ResetToolEbuildFile(options.chromeos_root, 'binutils')
    787     return 0 if (rv and rv1) else 1
    788 
    789   if options.gcc_dir:
    790     options.gcc_dir = os.path.abspath(os.path.expanduser(options.gcc_dir))
    791     if not os.path.isdir(options.gcc_dir):
    792       logger.GetLogger().LogError(
    793           '"{0}" does not exist.'.format(options.gcc_dir))
    794       return 1
    795 
    796   if options.gcc_branch and options.gcc_dir:
    797     parser.error('Only one of "--gcc_dir" and "--gcc_branch" can be specified.')
    798     return 1
    799 
    800   if options.binutils_dir:
    801     options.binutils_dir = os.path.abspath(
    802         os.path.expanduser(options.binutils_dir))
    803     if not os.path.isdir(options.binutils_dir):
    804       logger.GetLogger().LogError(
    805           '"{0}" does not exist.'.format(options.binutils_dir))
    806       return 1
    807 
    808   if options.binutils_branch and options.binutils_dir:
    809     parser.error('Only one of "--binutils_dir" and '
    810                  '"--binutils_branch" can be specified.')
    811     return 1
    812 
    813   if (not (options.binutils_branch or options.binutils_dir or
    814            options.gcc_branch or options.gcc_dir)):
    815     parser.error(('At least one of "--gcc_dir", "--gcc_branch", '
    816                   '"--binutils_dir" and "--binutils_branch" must '
    817                   'be specified.'))
    818     return 1
    819 
    820   if not options.board and not options.bootstrap:
    821     parser.error('You must specify either "--board" or "--bootstrap".')
    822     return 1
    823 
    824   if (options.board and options.bootstrap and
    825       not options.setup_tool_ebuild_file_only):
    826     parser.error('You must specify only one of "--board" and "--bootstrap".')
    827     return 1
    828 
    829   if not options.bootstrap and options.disable_2nd_bootstrap:
    830     parser.error('"--disable-2nd-bootstrap" has no effect '
    831                  'without specifying "--bootstrap".')
    832     return 1
    833 
    834   if Bootstrapper(
    835       options.chromeos_root,
    836       options.ndk_dir,
    837       gcc_branch=options.gcc_branch,
    838       gcc_dir=options.gcc_dir,
    839       binutils_branch=options.binutils_branch,
    840       binutils_dir=options.binutils_dir,
    841       board=options.board,
    842       disable_2nd_bootstrap=options.disable_2nd_bootstrap,
    843       setup_tool_ebuild_file_only=options.setup_tool_ebuild_file_only).Do():
    844     return 0
    845   return 1
    846 
    847 
    848 if __name__ == '__main__':
    849   retval = Main(sys.argv[1:])
    850   sys.exit(retval)
    851