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