1#!/usr/bin/python2
2"""Script to bootstrap the chroot using new toolchain.
3
4This script allows you to build/install a customized version of gcc/binutils,
5either by specifying branch or a local directory.
6
7This script must be executed outside chroot.
8
9Below 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
28from __future__ import print_function
29
30__author__ = 'shenhan@google.com (Han Shen)'
31
32import argparse
33import os
34import re
35import sys
36
37
38from cros_utils import command_executer
39from cros_utils import logger
40from cros_utils import misc
41import repo_to_repo
42
43REPO_PATH_PATTERN = 'src/third_party/{0}'
44TEMP_BRANCH_NAME = 'internal_testing_branch_no_use'
45CHROMIUMOS_OVERLAY_PATH = 'src/third_party/chromiumos-overlay'
46EBUILD_PATH_PATTERN = 'src/third_party/chromiumos-overlay/sys-devel/{0}'
47
48
49class 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
643def 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
851if __name__ == '__main__':
852  retval = Main(sys.argv[1:])
853  sys.exit(retval)
854