Add support for building gcc/binutils for a specific board for bootstrap_compiler.py.

TEST=None
BUG=None

Change-Id: I11edfd930b3c4d1985731a6912dec64e941ba03f
Reviewed-on: https://chrome-internal-review.googlesource.com/150959
Reviewed-by: Han Shen <shenhan@google.com>
Commit-Queue: Han Shen <shenhan@google.com>
Tested-by: Han Shen <shenhan@google.com>
diff --git a/bootstrap_compiler.py b/bootstrap_compiler.py
index b00464f..22dc7d3 100755
--- a/bootstrap_compiler.py
+++ b/bootstrap_compiler.py
@@ -1,286 +1,629 @@
 #!/usr/bin/python
+"""Script to bootstrap the chroot using new toolchain.
+
+This script allows you to build/install a customized version of gcc/binutils,
+either by specifying branch or a local directory.
+
+This script must be executed outside chroot.
+
+Below is some typical usage -
+
+## Build gcc located at /local/gcc/dir and do a bootstrap using the new compiler
+## for the chromeos root.  The script tries to find a valid chromeos tree all
+## the way up from your current working directory.
+./build_tool.py --gcc_dir=/loca/gcc/dir --bootstrap
+
+## Build binutils, using remote branch "mobile_toolchain_v17" and do a bootstrap
+## using the new binutils for the chromeos root. The script tries to find a
+## valid chromeos tree all the way up from your current working directory.
+./build_tool.py --binutils_branch=cros/mobile_toolchain_v17 \
+    --chromeos_root=/chromeos/dir --bootstrap
+
+## Same as above except only do it for board daisy - no bootstrapping involved.
+./build_tool.py --binutils_branch=cros/mobile_toolchain_v16 \
+    --chromeos_root=/chromeos/dir --board=daisy
+"""
 
 __author__ = 'shenhan@google.com (Han Shen)'
 
 import optparse
 import os
 import re
-import repo_to_repo
 import sys
 
+import repo_to_repo
 from utils import command_executer
 from utils import logger
 from utils import misc
 
-GCC_REPO_PATH='src/third_party/gcc'
-CHROMIUMOS_OVERLAY_PATH='src/third_party/chromiumos-overlay'
-GCC_EBUILD_PATH='src/third_party/chromiumos-overlay/sys-devel/gcc'
+REPO_PATH_PATTERN = 'src/third_party/{0}'
+TEMP_BRANCH_NAME = 'internal_testing_branch_no_use'
+CHROMIUMOS_OVERLAY_PATH = 'src/third_party/chromiumos-overlay'
+EBUILD_PATH_PATTERN = 'src/third_party/chromiumos-overlay/sys-devel/{0}'
+
 
 class Bootstrapper(object):
-  def __init__(self, chromeos_root, branch=None, gcc_dir=None,
-               setup_gcc_ebuild_file_only=False):
+  """Class that handles bootstrap process.
+  """
+
+  def __init__(self, chromeos_root, gcc_branch=None, gcc_dir=None,
+               binutils_branch=None, binutils_dir=None,
+               board=None, setup_tool_ebuild_file_only=False):
     self._chromeos_root = chromeos_root
-    self._branch = branch
+
+    self._gcc_branch = gcc_branch
+    self._gcc_branch_tree = None
     self._gcc_dir = gcc_dir
-    self._ce = command_executer.GetCommandExecuter()
-    self._logger = logger.GetLogger()
-    self._gcc_src_dir = None
-    self._branch_tree = None
     self._gcc_ebuild_file = None
     self._gcc_ebuild_file_name = None
-    self._setup_gcc_ebuild_file_only = setup_gcc_ebuild_file_only
+
+    self._binutils_branch = binutils_branch
+    self._binutils_branch_tree = None
+    self._binutils_dir = binutils_dir
+    self._binutils_ebuild_file = None
+    self._binutils_ebuild_file_name = None
+
+    self._setup_tool_ebuild_file_only = setup_tool_ebuild_file_only
+
+    self._ce = command_executer.GetCommandExecuter()
+    self._logger = logger.GetLogger()
+    self._board = board
+
+  def IsTreeSame(self, t1, t2):
+    diff = 'diff -qr -x .git -x .svn "{0}" "{1}"'.format(t1, t2)
+    if self._ce.RunCommand(diff, print_to_console=False) == 0:
+      self._logger.LogOutput('"{0}" and "{1}" are the same."'.format(t1, t2))
+      return True
+    self._logger.LogWarning('"{0}" and "{1}" are different."'.format(t1, t2))
+    return False
 
   def SubmitToLocalBranch(self):
-    # If "_branch" is set, we just use it.
-    if self._branch:
-      return True
+    """Copy source code to the chromium source tree and submit it locally."""
+    if self._gcc_dir:
+      if not self.SubmitToolToLocalBranch(
+          tool_name='gcc', tool_dir=self._gcc_dir):
+        return False
+      self._gcc_branch = TEMP_BRANCH_NAME
 
-    # The next few steps creates an internal branch to sync with the gcc dir
+    if self._binutils_dir:
+      if not self.SubmitToolToLocalBranch(
+          tool_name='binutils', tool_dir=self._binutils_dir):
+        return False
+      self._binutils_branch = TEMP_BRANCH_NAME
+
+    return True
+
+  def SubmitToolToLocalBranch(self, tool_name, tool_dir):
+    """Copy the source code to local chromium source tree.
+
+    Args:
+      tool_name: either 'gcc' or 'binutils'
+      tool_dir: the tool source dir to be used
+    Returns:
+      True if all succeeded False otherwise.
+    """
+
+    # The next few steps creates an internal branch to sync with the tool dir
     # user provided.
-    self._branch = 'internal_testing_branch_no_use'
-    chrome_gcc_dir = os.path.join(
-      self._chromeos_root, GCC_REPO_PATH)
+    chrome_tool_dir = self.GetChromeOsToolDir(tool_name)
 
     # 0. Test to see if git tree is free of local changes.
-    if not misc.IsGitTreeClean(chrome_gcc_dir):
+    if not misc.IsGitTreeClean(chrome_tool_dir):
       self._logger.LogError(
-        'Git repository "{0}" not clean, aborted.'.format(chrome_gcc_dir))
+          'Git repository "{0}" not clean, aborted.'.format(chrome_tool_dir))
       return False
 
     # 1. Checkout/create a (new) branch for testing.
-    command = 'cd "{0}" && git checkout -B {1} cros/master'.format(
-      chrome_gcc_dir, self._branch)
+    command = 'cd "{0}" && git checkout -B {1}'.format(
+        chrome_tool_dir, TEMP_BRANCH_NAME)
     ret = self._ce.RunCommand(command)
     if ret:
       self._logger.LogError('Failed to create a temp branch for test, aborted.')
       return False
 
-    # 2. Sync sources from user provided gcc dir to chromiumos gcc git.
-    local_gcc_repo = repo_to_repo.FileRepo(self._gcc_dir)
-    chrome_gcc_repo = repo_to_repo.GitRepo(chrome_gcc_dir, self._branch)
-    chrome_gcc_repo._root_dir = chrome_gcc_dir
-    # Delete all stuff before start mapping.
-    self._ce.RunCommand('cd {0} && rm -rf *'.format(chrome_gcc_dir))
-    local_gcc_repo.MapSources(chrome_gcc_repo.GetRoot())
+    if self.IsTreeSame(tool_dir, chrome_tool_dir):
+      self._logger.LogOutput(
+          '"{0}" and "{1}" are the same, sync skipped.'.format(
+              tool_dir, chrome_tool_dir))
+      return True
 
-     # 3. Verify sync successfully.
-    diff = 'diff -r -x .git -x .svn "{0}" "{1}"'.format(
-      self._gcc_dir, chrome_gcc_dir)
-    if self._ce.RunCommand(diff):
-      self._logger.LogError('Sync not successfully, aborted.')
-      return False
-    else:
+    # 2. Sync sources from user provided tool dir to chromiumos tool git.
+    local_tool_repo = repo_to_repo.FileRepo(tool_dir)
+    chrome_tool_repo = repo_to_repo.GitRepo(chrome_tool_dir, TEMP_BRANCH_NAME)
+    chrome_tool_repo._root_dir = chrome_tool_dir
+    # Delete all stuff before start mapping.
+    self._ce.RunCommand('cd {0} && rm -rf *'.format(chrome_tool_dir))
+    local_tool_repo.MapSources(chrome_tool_repo.GetRoot())
+
+    # 3. Ensure after sync tree is the same.
+    if self.IsTreeSame(tool_dir, chrome_tool_dir):
       self._logger.LogOutput('Sync successfully done.')
+    else:
+      self._logger.LogError('Sync not successful, aborted.')
+      return False
 
     # 4. Commit all changes.
-    ret = chrome_gcc_repo.CommitLocally(
-      'Synced with gcc source tree at - "{0}".'.format(self._gcc_dir))
+    ret = chrome_tool_repo.CommitLocally(
+        'Synced with tool source tree at - "{0}".'.format(tool_dir))
     if ret:
       self._logger.LogError('Commit to local branch "{0}" failed, aborted.'.
-                            format(self._branch))
+                            format(TEMP_BRANCH_NAME))
       return False
     return True
 
   def CheckoutBranch(self):
-    self._gcc_src_dir = os.path.join(self._chromeos_root, GCC_REPO_PATH)
-    command = 'cd "{0}" && git checkout {1}'.format(
-      self._gcc_src_dir, self._branch)
-    if not self._ce.RunCommand(command, print_to_console=True):
-      # Get 'TREE' value of this commit
-      command = 'cd "{0}" && git cat-file -p {1} ' \
-          '| grep -E "^tree [a-f0-9]+$" | cut -d" " -f2'.format(
-        self._gcc_src_dir, self._branch)
-      ret, stdout, stderr  = self._ce.RunCommand(
-        command, return_output=True, print_to_console=False)
-      # Pipe operation always has a zero return value. So need to check if
-      # stdout is valid.
-      if not ret and stdout and \
-            re.match('[0-9a-h]{40}', stdout.strip(), re.IGNORECASE):
-        self._branch_tree = stdout.strip()
-        self._logger.LogOutput('Find tree for branch "{0}" - "{1}"'.format(
-            self._branch, self._branch_tree))
-        return True
-    self._logger.LogError(
-      'Failed to checkout "{0}" or failed to get tree value, aborted.'.format(
-        self._branch))
-    return False
+    """Checkout working branch for the tools.
 
-  def FindGccEbuildFile(self):
-    # To get the active gcc ebuild file, we need a workable chroot first.
-    if not os.path.exists(os.path.join(self._chromeos_root, 'chroot')) and \
-          self._ce.RunCommand('cd "{0}" && cros_sdk --create'.format(
-        self._chromeos_root)):
-      self._logger.LogError(
-        ('Failed to instal a initial chroot, aborted.\n'
-         'If previous bootstrap failed, do a "cros_sdk --delete" to remove '
-         'in-complete chroot.'))
-      return False
+    Returns:
+      True: if operation succeeds.
+    """
 
-    rv, stdout, stderr  = self._ce.ChrootRunCommand(self._chromeos_root,
-      'equery w sys-devel/gcc', return_output=True, print_to_console=True)
-    if rv:
-      self._logger.LogError('Failed to execute inside chroot '
-                            '"equery w sys-devel/gcc", aborted.')
-      return False
-    m = re.match('^.*/({0}/(.*\.ebuild))$'.format(GCC_EBUILD_PATH), stdout)
-    if not m:
-      self._logger.LogError(
-        ('Failed to find gcc ebuild file, aborted. '
-         'If previous bootstrap failed, do a "cros_sdk --delete" to remove '
-         'in-complete chroot.'))
-      return False
-    self._gcc_ebuild_file = os.path.join(self._chromeos_root, m.group(1))
-    self._gcc_ebuild_file_name = m.group(2)
+    if self._gcc_branch:
+      rv = self.CheckoutToolBranch('gcc', self._gcc_branch)
+      if rv:
+        self._gcc_branch_tree = rv
+      else:
+        return False
+
+    if self._binutils_branch:
+      rv = self.CheckoutToolBranch('binutils', self._binutils_branch)
+      if rv:
+        self._binutils_branch_tree = rv
+      else:
+        return False
+
     return True
 
-  def InplaceModifyEbuildFile(self):
-    """Using sed to fill properly the values into the following lines -
-         CROS_WORKON_COMMIT="..."
-         CROS_WORKON_TREE="..."
+  def CheckoutToolBranch(self, tool_name, tool_branch):
+    """Checkout the tool branch for a certain tool.
+
+    Args:
+      tool_name: either 'gcc' or 'binutils'
+      tool_branch: tool branch to use
+    Returns:
+      True: if operation succeeds. Otherwise False.
     """
-    command = 'sed -i ' \
-        '-e \'s!^CROS_WORKON_COMMIT=".*"$!CROS_WORKON_COMMIT="{0}"!\' ' \
-        '-e \'s!^CROS_WORKON_TREE=".*"$!CROS_WORKON_TREE="{1}"!\' {2}'.format(
-      self._branch, self._branch_tree, self._gcc_ebuild_file)
+
+    chrome_tool_dir = self.GetChromeOsToolDir(tool_name)
+    command = 'cd "{0}" && git checkout {1}'.format(
+        chrome_tool_dir, tool_branch)
+    if not self._ce.RunCommand(command, print_to_console=True):
+      # Get 'TREE' value of this commit
+      command = ('cd "{0}" && git cat-file -p {1} '
+                 '| grep -E "^tree [a-f0-9]+$" '
+                 '| cut -d" " -f2').format(chrome_tool_dir, tool_branch)
+      ret, stdout, _ = self._ce.RunCommand(
+          command, return_output=True, print_to_console=False)
+      # Pipe operation always has a zero return value. So need to check if
+      # stdout is valid.
+      if not ret and stdout and re.match(
+          '[0-9a-h]{40}', stdout.strip(), re.IGNORECASE):
+        tool_branch_tree = stdout.strip()
+        self._logger.LogOutput('Find tree for {0} branch "{1}" - "{2}"'.format(
+            tool_name, tool_branch, tool_branch_tree))
+        return tool_branch_tree
+    self._logger.LogError(('Failed to checkout "{0}" or failed to '
+                           'get tree value, aborted.').format(tool_branch))
+    return None
+
+  def FindEbuildFile(self):
+    """Find the ebuild files for the tools.
+
+    Returns:
+      True: if operation succeeds.
+    """
+
+    if self._gcc_branch:
+      (rv, ef, efn) = self.FindToolEbuildFile('gcc')
+      if rv:
+        self._gcc_ebuild_file = ef
+        self._gcc_ebuild_file_name = efn
+      else:
+        return False
+
+    if self._binutils_branch:
+      (rv, ef, efn) = self.FindToolEbuildFile('binutils')
+      if rv:
+        self._binutils_ebuild_file = ef
+        self._binutils_ebuild_file_name = efn
+      else:
+        return False
+
+    return True
+
+  def FindToolEbuildFile(self, tool_name):
+    """Find ebuild file for a specific tool.
+
+    Args:
+      tool_name: either "gcc" or "binutils".
+    Returns:
+      A triplet that consisits of whether operation succeeds or not,
+      tool ebuild file full path and tool ebuild file name.
+    """
+
+    # To get the active gcc ebuild file, we need a workable chroot first.
+    if not os.path.exists(
+        os.path.join(self._chromeos_root, 'chroot')) and self._ce.RunCommand(
+            'cd "{0}" && cros_sdk --create'.format(self._chromeos_root)):
+      self._logger.LogError(('Failed to install a initial chroot, aborted.\n'
+                             'If previous bootstrap failed, do a '
+                             '"cros_sdk --delete" to remove '
+                             'in-complete chroot.'))
+      return (False, None, None)
+
+    rv, stdout, _ = self._ce.ChrootRunCommand(
+        self._chromeos_root, 'equery w sys-devel/{0}'.format(tool_name),
+        return_output=True, print_to_console=True)
+    if rv:
+      self._logger.LogError(
+          ('Failed to execute inside chroot '
+           '"equery w sys-devel/{0}", aborted.').format(tool_name))
+      return (False, None, None)
+    m = re.match(r'^.*/({0}/(.*\.ebuild))$'.format(
+        EBUILD_PATH_PATTERN.format(tool_name)), stdout)
+    if not m:
+      self._logger.LogError(
+          ('Failed to find {0} ebuild file, aborted. '
+           'If previous bootstrap failed, do a "cros_sdk --delete" to remove '
+           'in-complete chroot.').format(tool_name))
+      return (False, None, None)
+    tool_ebuild_file = os.path.join(self._chromeos_root, m.group(1))
+    tool_ebuild_file_name = m.group(2)
+
+    return (True, tool_ebuild_file, tool_ebuild_file_name)
+
+  def InplaceModifyEbuildFile(self):
+    """Modify the ebuild file.
+
+    Returns:
+      True if operation succeeds.
+    """
+
+    # Note we shall not use remote branch name (eg. "cros/gcc.gnu.org/...") in
+    # CROS_WORKON_COMMIT, we have to use GITHASH. So we call GitGetCommitHash on
+    # tool_branch.
+    if self._gcc_branch:
+      tool_branch_githash = misc.GitGetCommitHash(
+          self.GetChromeOsToolDir('gcc'), self._gcc_branch)
+      if not tool_branch_githash:
+        return False
+      if not self.InplaceModifyToolEbuildFile(
+          tool_branch_githash, self._gcc_branch_tree, self._gcc_ebuild_file):
+        return False
+
+    if self._binutils_branch:
+      tool_branch_githash = misc.GitGetCommitHash(
+          self.GetChromeOsToolDir('binutils'), self._binutils_branch)
+      if not self.InplaceModifyToolEbuildFile(
+          tool_branch_githash, self._binutils_branch_tree,
+          self._binutils_ebuild_file):
+        return False
+    return True
+
+  @staticmethod
+  def ResetToolEbuildFile(chromeos_root, tool_name):
+    """Reset tool ebuild file to clean state.
+
+    Args:
+      chromeos_root: chromeos source tree
+      tool_name: either "gcc" or "binutils"
+    Returns:
+      True if operation succeds.
+    """
+    rv = misc.GetGitChangesAsList(
+        os.path.join(chromeos_root, CHROMIUMOS_OVERLAY_PATH),
+        path=('sys-devel/{0}/{0}-*.ebuild'.format(tool_name)),
+        staged=False)
+    if rv:
+      cmd = 'cd {0} && git checkout --'.format(os.path.join(
+          chromeos_root, CHROMIUMOS_OVERLAY_PATH))
+      for g in rv:
+        cmd += ' ' + g
+      rv = command_executer.GetCommandExecuter().RunCommand(cmd)
+      if rv:
+        logger.GetLogger().LogWarning(
+            'Failed to reset the ebuild file. Please refer to log above.')
+        return False
+    else:
+      logger.GetLogger().LogWarning(
+          'Note - did not find any modified {0} ebuild file.'.format(tool_name))
+      # Fall through
+    return True
+
+  def GetChromeOsToolDir(self, tool_name):
+    """Return the chromeos git dir for a specific tool.
+
+    Args:
+      tool_name: either 'gcc' or 'binutils'.
+    Returns:
+      Absolute git path for the tool.
+    """
+
+    return os.path.join(
+        self._chromeos_root, REPO_PATH_PATTERN.format(tool_name))
+
+  def InplaceModifyToolEbuildFile(
+      self, tool_branch_githash, tool_branch_tree, tool_ebuild_file):
+    """Using sed to fill properly values into the ebuild file.
+
+    Args:
+      tool_branch_githash: githash for tool_branch
+      tool_branch_tree: treeish for the tool branch
+      tool_ebuild_file: tool ebuild file
+    Returns:
+      True: if operation succeeded.
+    """
+
+    command = ('sed -i '
+               '-e \'/^CROS_WORKON_COMMIT=".*"/i'
+               ' # The following line is modified by script.\' '
+               '-e \'s!^CROS_WORKON_COMMIT=".*"$!CROS_WORKON_COMMIT="{0}"!\' '
+               '-e \'/^CROS_WORKON_TREE=".*"/i'
+               ' # The following line is modified by script.\' '
+               '-e \'s!^CROS_WORKON_TREE=".*"$!CROS_WORKON_TREE="{1}"!\' '
+               '{2}').format(tool_branch_githash,
+                             tool_branch_tree,
+                             tool_ebuild_file)
     rv = self._ce.RunCommand(command)
     if rv:
       self._logger.LogError(
-        'Failed to modify commit and tree value for "{0}"", aborted.'.format(
-          self._gcc_ebuild_file))
+          'Failed to modify commit and tree value for "{0}"", aborted.'.format(
+              tool_ebuild_file))
+      return False
+
+    # Warn that the ebuild file has been modified.
+    self._logger.LogWarning(
+        ('Ebuild file "{0}" is modified, to revert the file - \n'
+         'bootstrap_compiler.py --chromeos_root={1} '
+         '--reset_tool_ebuild_file').format(
+             tool_ebuild_file, self._chromeos_root))
+    return True
+
+  def DoBuildForBoard(self):
+    """Build tool for a specific board.
+
+    Returns:
+      True if operation succeeds.
+    """
+
+    if self._gcc_branch:
+      if not self.DoBuildToolForBoard('gcc'):
+        return False
+    if self._binutils_branch:
+      if not self.DoBuildToolForBoard('binutils'):
+        return False
+    return True
+
+  def DoBuildToolForBoard(self, tool_name):
+    """Build a specific tool for a specific board.
+
+    Args:
+      tool_name: either "gcc" or "binutils"
+    Returns:
+      True if operation succeeds.
+    """
+
+    if self._board == 'host':
+      command = 'sudo emerge sys-devel/{0}'.format(tool_name)
+    else:
+      target = misc.GetCtargetFromBoard(self._board, self._chromeos_root)
+      if not target:
+        self._logger.LogError('Unsupported board "{0}", aborted.'.format(
+            self._board))
+        return False
+      command = 'sudo emerge cross-{0}/{1}'.format(target, tool_name)
+
+    rv = self._ce.ChrootRunCommand(
+        self._chromeos_root,
+        command, return_output=False,
+        print_to_console=True)
+    if rv:
+      self._logger.LogError(
+          'Build {0} failed for "{1}", aborted.'.format(tool_name, self._board))
       return False
     return True
 
   def DoBootstrapping(self):
+    """Do bootstrapping the chroot.
+
+    Returns:
+      True if operation succeeds.
+    """
+
     logfile = os.path.join(self._chromeos_root, 'bootstrap.log')
-    command = 'cd "{0}" && cros_sdk --delete --bootstrap |& tee "{1}"'. \
-        format(self._chromeos_root, logfile)
-    rv = self._ce.RunCommand(command, \
-                               return_output=False, print_to_console=True)
+    command = 'cd "{0}" && cros_sdk --delete --bootstrap |& tee "{1}"'.format(
+        self._chromeos_root, logfile)
+    rv = self._ce.RunCommand(command, return_output=False,
+                             print_to_console=True)
     if rv:
       self._logger.LogError('Bootstrapping failed, log file - "{0}"\n'.format(
           logfile))
       return False
 
+    ## Workaround for - crbug/331713.
+    ## We do not test for success, failure is not important at this step.
+    self._ce.ChrootRunCommand(
+        self._chromeos_root, 'sudo emerge dev-util/pkgconfig',
+        return_output=False, print_to_console=True)
+
     self._logger.LogOutput('Bootstrap succeeded.')
     return True
 
   def Do(self):
-    if self.SubmitToLocalBranch() and \
-          self.CheckoutBranch() and \
-          self.FindGccEbuildFile() and \
-          self.InplaceModifyEbuildFile() and \
-          (self._setup_gcc_ebuild_file_only or self.DoBootstrapping()):
-      ret = True
+    """Entrance of the class.
+
+    Returns:
+      True if everything is ok.
+    """
+
+    if (self.SubmitToLocalBranch() and
+        self.CheckoutBranch() and
+        self.FindEbuildFile() and
+        self.InplaceModifyEbuildFile()):
+      if self._setup_tool_ebuild_file_only:
+        # Everything is done, we are good.
+        ret = True
+      else:
+        if self._board:
+          ret = self.DoBuildForBoard()
+        else:
+          # This implies '--bootstrap'.
+          ret = self.DoBootstrapping()
     else:
       ret = False
-    ## Warn that the ebuild file is modified.
-    if self._gcc_ebuild_file:
-      self._logger.LogWarning(
-        ('Gcc ebuild file is (probably) modified, to revert the file - \n'
-         'bootstrap_compiler.py --chromeos={0} --reset_gcc_ebuild_file').format(
-          self._chromeos_root))
-
     return ret
 
 
 def Main(argv):
   parser = optparse.OptionParser()
   parser.add_option('-c', '--chromeos_root', dest='chromeos_root',
-                    help=('ChromeOs root dir.'))
-  parser.add_option('-b', '--branch', dest='branch',
+                    help=('Optional. ChromeOs root dir. '
+                          'When not specified, chromeos root will be deduced '
+                          'from current working directory.'))
+  parser.add_option('--gcc_branch', dest='gcc_branch',
                     help=('The branch to test against. '
                           'This branch must be a local branch '
                           'inside "src/third_party/gcc". '
-                          'Notice, this must not be used with "--gcc".'))
+                          'Notice, this must not be used with "--gcc_dir".'))
+  parser.add_option('--binutils_branch', dest='binutils_branch',
+                    help=('The branch to test against binutils. '
+                          'This branch must be a local branch '
+                          'inside "src/third_party/binutils". '
+                          'Notice, this must not be used with '
+                          '"--binutils_dir".'))
   parser.add_option('-g', '--gcc_dir', dest='gcc_dir',
                     help=('Use a local gcc tree to do bootstrapping. '
-                          'Notice, this must not be used with "--branch".'))
+                          'Notice, this must not be used with "--gcc_branch".'))
+  parser.add_option('--binutils_dir', dest='binutils_dir',
+                    help=('Use a local binutils tree to do bootstrapping. '
+                          'Notice, this must not be used with '
+                          '"--binutils_branch".'))
   parser.add_option('--fixperm', dest='fixperm',
                     default=False, action='store_true',
                     help=('Fix the (notorious) permission error '
                           'while trying to bootstrap the chroot. '
                           'Note this takes an extra 10-15 minutes '
                           'and is only needed once per chromiumos tree.'))
-  parser.add_option('--setup_gcc_ebuild_file_only',
-                    dest='setup_gcc_ebuild_file_only',
+  parser.add_option('--setup_tool_ebuild_file_only',
+                    dest='setup_tool_ebuild_file_only',
                     default=False, action='store_true',
-                    help=('Setup gcc ebuild file to pick up the '
-                          'branch (--branch) or user gcc source (--gcc_dir) '
-                          'and exit. Keep chroot as is.'))
-  parser.add_option('--reset_gcc_ebuild_file', dest='reset_gcc_ebuild_file',
+                    help=('Setup gcc and/or binutils ebuild file '
+                          'to pick up the branch (--gcc/binutils_branch) or '
+                          'use gcc and/or binutils source (--gcc/binutils_dir) '
+                          'and exit. Keep chroot as is. This should not be '
+                          'used with --gcc/binutils_dir/branch options.'))
+  parser.add_option('--reset_tool_ebuild_file', dest='reset_tool_ebuild_file',
                     default=False, action='store_true',
                     help=('Reset the modification that is done by this script.'
                           'Note, when this script is running, it will modify '
-                          'the active gcc ebuild file. Use this option to '
-                          'reset (what this script has done) and exit.'))
+                          'the active gcc/binutils ebuild file. Use this '
+                          'option to reset (what this script has done) '
+                          'and exit. This should not be used with -- '
+                          'gcc/binutils_dir/branch options.'))
+  parser.add_option('--board', dest='board', default=None,
+                    help=('Only build toolchain for a specific board. '
+                          'Use "host" to build for host. '
+                          'This does not perform a chroot bootstrap.'))
+  parser.add_option('--bootstrap', dest='bootstrap',
+                    default=False, action='store_true',
+                    help=('Performs a chroot bootstrap. '
+                          'Note, this will *destroy* your current chroot.'))
+
   options = parser.parse_args(argv)[0]
+  # Trying to deduce chromeos root from current directory.
   if not options.chromeos_root:
-    parser.error('Missing mandatory option "--chromeos".')
+    logger.GetLogger().LogOutput('Trying to deduce chromeos root ...')
+    wdir = os.getcwd()
+    while wdir and wdir != '/':
+      if misc.IsChromeOsTree(wdir):
+        logger.GetLogger().LogOutput('Find chromeos_root: {}'.format(wdir))
+        options.chromeos_root = wdir
+        break
+      wdir = os.path.dirname(wdir)
+
+  if not options.chromeos_root:
+    parser.error('Missing or failing to deduce mandatory option "--chromeos".')
     return 1
 
   options.chromeos_root = os.path.abspath(
-    os.path.expanduser(options.chromeos_root))
+      os.path.expanduser(options.chromeos_root))
 
   if not os.path.isdir(options.chromeos_root):
     logger.GetLogger().LogError(
-      '"{0}" does not exist.'.format(options.chromeos_root))
+        '"{0}" does not exist.'.format(options.chromeos_root))
     return 1
 
   if options.fixperm:
     # Fix perm error before continuing.
-    cmd = ('sudo find "{0}" \( -name ".cache" -type d -prune \) -o ' + \
-             '\( -name "chroot" -type d -prune \) -o ' + \
-             '\( -type f -exec chmod a+r {{}} \; \) -o ' + \
-             '\( -type d -exec chmod a+rx {{}} \; \)').format(
-      options.chromeos_root)
+    cmd = (r'sudo find "{0}" \( -name ".cache" -type d -prune \) -o '
+           r'\( -name "chroot" -type d -prune \) -o '
+           r'\( -type f -exec chmod a+r {{}} \; \) -o '
+           r'\( -type d -exec chmod a+rx {{}} \; \)').format(
+               options.chromeos_root)
     logger.GetLogger().LogOutput(
-      'Fixing perm issues for chromeos root, this might take some time.')
+        'Fixing perm issues for chromeos root, this might take some time.')
     command_executer.GetCommandExecuter().RunCommand(cmd)
 
-  if options.reset_gcc_ebuild_file:
-    if options.gcc_dir or options.branch:
-      logger.GetLogger().LogWarning('Ignoring "--gcc_dir" or "--branch".')
-    if options.setup_gcc_ebuild_file_only:
-      logger.GetLogger().LogError(
-        ('Conflict options "--reset_gcc_ebuild_file" '
-         'and "--setup_gcc_ebuild_file_only".'))
-      return 1
-    # Reset gcc ebuild file and exit.
-    rv = misc.GetGitChangesAsList(
-      os.path.join(options.chromeos_root,CHROMIUMOS_OVERLAY_PATH),
-      path='sys-devel/gcc/gcc-*.ebuild',
-      staged=False)
-    if rv:
-      cmd = 'cd {0} && git checkout --'.format(os.path.join(
-          options.chromeos_root, CHROMIUMOS_OVERLAY_PATH))
-      for g in rv:
-        cmd += ' ' + g
-      rv = command_executer.GetCommandExecuter().RunCommand(cmd)
-      if rv:
-        logger.GetLogger().LogWarning('Failed to reset gcc ebuild file.')
-      return rv
-    else:
+  if options.reset_tool_ebuild_file:
+    if (options.gcc_dir or options.gcc_branch or
+        options.binutils_dir or options.binutils_branch):
       logger.GetLogger().LogWarning(
-        'Did not find any modified gcc ebuild file.')
+          'Ignoring any "--gcc/binutils_dir" and/or "--gcc/binutils_branch".')
+    if options.setup_tool_ebuild_file_only:
+      logger.GetLogger().LogError(
+          ('Conflict options "--reset_tool_ebuild_file" '
+           'and "--setup_tool_ebuild_file_only".'))
       return 1
+    rv = Bootstrapper.ResetToolEbuildFile(options.chromeos_root, 'gcc')
+    rv1 = Bootstrapper.ResetToolEbuildFile(options.chromeos_root, 'binutils')
+    return 0 if (rv and rv1) else 1
 
   if options.gcc_dir:
     options.gcc_dir = os.path.abspath(os.path.expanduser(options.gcc_dir))
     if not os.path.isdir(options.gcc_dir):
       logger.GetLogger().LogError(
-        '"{0}" does not exist.'.format(options.gcc_dir))
+          '"{0}" does not exist.'.format(options.gcc_dir))
       return 1
 
-  if options.branch and options.gcc_dir:
-    parser.error('Only one of "--gcc" and "--branch" can be specified.')
+  if options.gcc_branch and options.gcc_dir:
+    parser.error('Only one of "--gcc_dir" and "--gcc_branch" can be specified.')
     return 1
-  if not (options.branch or options.gcc_dir):
-    parser.error('At least one of "--gcc" and "--branch" must be specified.')
+
+  if options.binutils_dir:
+    options.binutils_dir = os.path.abspath(
+        os.path.expanduser(options.binutils_dir))
+    if not os.path.isdir(options.binutils_dir):
+      logger.GetLogger().LogError(
+          '"{0}" does not exist.'.format(options.binutils_dir))
+      return 1
+
+  if options.binutils_branch and options.binutils_dir:
+    parser.error('Only one of "--binutils_dir" and '
+                 '"--binutils_branch" can be specified.')
+    return 1
+
+  if (not (options.binutils_branch or options.binutils_dir or
+           options.gcc_branch or options.gcc_dir)):
+    parser.error(('At least one of "--gcc_dir", "--gcc_branch", '
+                  '"--binutils_dir" and "--binutils_branch" must '
+                  'be specified.'))
+    return 1
+
+  if not options.board and not options.bootstrap:
+    parser.error('You must specify either "--board" or "--bootstrap".')
+    return 1
+
+  if options.board and options.bootstrap:
+    parser.error('You must specify only one of "--board" and "--bootstrap".')
     return 1
 
   if Bootstrapper(
-    options.chromeos_root, branch=options.branch, gcc_dir=options.gcc_dir,
-    setup_gcc_ebuild_file_only=options.setup_gcc_ebuild_file_only).Do():
+      options.chromeos_root,
+      gcc_branch=options.gcc_branch, gcc_dir=options.gcc_dir,
+      binutils_branch=options.binutils_branch,
+      binutils_dir=options.binutils_dir,
+      board=options.board,
+      setup_tool_ebuild_file_only=options.setup_tool_ebuild_file_only).Do():
     return 0
   return 1