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