• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Updates LLVM_NEXT_HASH and uprevs the build of a package or packages.
8
9For each package, a temporary repo is created and the changes are uploaded
10for review.
11"""
12
13from __future__ import print_function
14
15import argparse
16import os
17import re
18import subprocess
19from collections import namedtuple
20
21from assert_not_in_chroot import VerifyOutsideChroot
22from failure_modes import FailureModes
23from get_llvm_hash import GetLLVMHashAndVersionFromSVNOption, is_svn_option
24import get_llvm_hash
25import llvm_patch_management
26from subprocess_helpers import ChrootRunCommand, ExecCommandAndCaptureOutput
27
28# If set to `True`, then the contents of `stdout` after executing a command will
29# be displayed to the terminal.
30verbose = False
31
32CommitContents = namedtuple('CommitContents', ['url', 'cl_number'])
33
34
35def GetCommandLineArgs():
36  """Parses the command line for the optional command line arguments.
37
38  Returns:
39    The log level to use when retrieving the LLVM hash or google3 LLVM version,
40    the chroot path to use for executing chroot commands,
41    a list of a package or packages to update their LLVM next hash,
42    and the LLVM version to use when retrieving the LLVM hash.
43  """
44
45  # Default path to the chroot if a path is not specified.
46  cros_root = os.path.expanduser('~')
47  cros_root = os.path.join(cros_root, 'chromiumos')
48
49  # Create parser and add optional command-line arguments.
50  parser = argparse.ArgumentParser(
51      description="Updates the build's hash for llvm-next.")
52
53  # Add argument for a specific chroot path.
54  parser.add_argument(
55      '--chroot_path',
56      default=cros_root,
57      help='the path to the chroot (default: %(default)s)')
58
59  # Add argument for specific builds to uprev and update their llvm-next hash.
60  parser.add_argument(
61      '--update_packages',
62      default=['sys-devel/llvm'],
63      required=False,
64      nargs='+',
65      help='the ebuilds to update their hash for llvm-next ' \
66          '(default: %(default)s)')
67
68  # Add argument for whether to display command contents to `stdout`.
69  parser.add_argument(
70      '--verbose',
71      action='store_true',
72      help='display contents of a command to the terminal '
73      '(default: %(default)s)')
74
75  # Add argument for the LLVM version to use.
76  parser.add_argument(
77      '--llvm_version',
78      type=is_svn_option,
79      required=True,
80      help='which git hash of LLVM to find. Either a svn revision, or one '
81      'of %s' % sorted(get_llvm_hash.KNOWN_HASH_SOURCES))
82
83  # Add argument for the mode of the patch management when handling patches.
84  parser.add_argument(
85      '--failure_mode',
86      default=FailureModes.FAIL.value,
87      choices=[FailureModes.FAIL.value, FailureModes.CONTINUE.value,
88               FailureModes.DISABLE_PATCHES.value,
89               FailureModes.REMOVE_PATCHES.value],
90      help='the mode of the patch manager when handling failed patches ' \
91          '(default: %(default)s)')
92
93  # Add argument for the patch metadata file.
94  parser.add_argument(
95      '--patch_metadata_file',
96      default='PATCHES.json',
97      help='the .json file that has all the patches and their '
98      'metadata if applicable (default: PATCHES.json inside $FILESDIR)')
99
100  # Parse the command line.
101  args_output = parser.parse_args()
102
103  # FIXME: We shouldn't be using globals here, but until we fix it, make pylint
104  # stop complaining about it.
105  # pylint: disable=global-statement
106  global verbose
107
108  verbose = args_output.verbose
109
110  return args_output
111
112
113def GetChrootBuildPaths(chromeos_root, package_list):
114  """Gets the chroot path(s) of the package(s).
115
116  Args:
117    chromeos_root: The absolute path to the chroot to
118    use for executing chroot commands.
119    package_list: A list of a package/packages to
120    be used to find their chroot path.
121
122  Returns:
123    A list of a chroot path/chroot paths of the package's ebuild file.
124
125  Raises:
126    ValueError: Failed to get the chroot path of a package.
127  """
128
129  chroot_paths = []
130
131  # Find the chroot path for each package's ebuild.
132  for cur_package in sorted(set(package_list)):
133    # Cmd to find the chroot path for the package.
134    equery_cmd = ['equery', 'w', cur_package]
135
136    chroot_path = ChrootRunCommand(chromeos_root, equery_cmd, verbose=verbose)
137
138    chroot_paths.append(chroot_path.strip())
139
140  return chroot_paths
141
142
143def _ConvertChrootPathsToSymLinkPaths(chromeos_root, chroot_file_paths):
144  """Converts the chroot path(s) to absolute symlink path(s).
145
146  Args:
147    chromeos_root: The absolute path to the chroot.
148    chroot_file_paths: A list of a chroot path/chroot paths to convert to
149    a absolute symlink path/symlink paths.
150
151  Returns:
152    A list of absolute path(s) which are symlinks that point to
153    the ebuild of the package(s).
154
155  Raises:
156    ValueError: Invalid prefix for the chroot path or
157    invalid chroot path(s) were provided.
158  """
159
160  symlink_file_paths = []
161
162  chroot_prefix = '/mnt/host/source/'
163
164  # Iterate through the chroot paths.
165  #
166  # For each chroot file path, remove '/mnt/host/source/' prefix
167  # and combine the chroot path with the result and add it to the list.
168  for cur_chroot_file_path in chroot_file_paths:
169    if not cur_chroot_file_path.startswith(chroot_prefix):
170      raise ValueError(
171          'Invalid prefix for the chroot path: %s' % cur_chroot_file_path)
172
173    rel_path = cur_chroot_file_path[len(chroot_prefix):]
174
175    # combine the chromeos root path + '/src/...'
176    absolute_symlink_path = os.path.join(chromeos_root, rel_path)
177
178    symlink_file_paths.append(absolute_symlink_path)
179
180  return symlink_file_paths
181
182
183def GetEbuildPathsFromSymLinkPaths(symlinks):
184  """Reads the symlink(s) to get the ebuild path(s) to the package(s).
185
186  Args:
187    symlinks: A list of absolute path symlink/symlinks that point
188    to the package's ebuild.
189
190  Returns:
191    A dictionary where the key is the absolute path of the symlink and the value
192    is the absolute path to the ebuild that was read from the symlink.
193
194  Raises:
195    ValueError: Invalid symlink(s) were provided.
196  """
197
198  # A dictionary that holds:
199  #   key: absolute symlink path
200  #   value: absolute ebuild path
201  resolved_paths = {}
202
203  # Iterate through each symlink.
204  #
205  # For each symlink, check that it is a valid symlink,
206  # and then construct the ebuild path, and
207  # then add the ebuild path to the dict.
208  for cur_symlink in symlinks:
209    if not os.path.islink(cur_symlink):
210      raise ValueError('Invalid symlink provided: %s' % cur_symlink)
211
212    # Construct the absolute path to the ebuild.
213    ebuild_path = os.path.realpath(cur_symlink)
214
215    if cur_symlink not in resolved_paths:
216      resolved_paths[cur_symlink] = ebuild_path
217
218  return resolved_paths
219
220
221def UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version):
222  """Updates the build's LLVM_NEXT_HASH.
223
224  The build changes are staged for commit in the temporary repo.
225
226  Args:
227    ebuild_path: The absolute path to the ebuild.
228    llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH.
229    llvm_version: The revision number of 'llvm_hash'.
230
231  Raises:
232    ValueError: Invalid ebuild path provided or failed to stage the commit
233    of the changes or failed to update the LLVM hash.
234  """
235
236  # Iterate through each ebuild.
237  #
238  # For each ebuild, read the file in
239  # advance and then create a temporary file
240  # that gets updated with the new LLVM hash
241  # and revision number and then the ebuild file
242  # gets updated to the temporary file.
243
244  if not os.path.isfile(ebuild_path):
245    raise ValueError('Invalid ebuild path provided: %s' % ebuild_path)
246
247  # Create regex that finds 'LLVM_NEXT_HASH'.
248  llvm_regex = re.compile('^LLVM_NEXT_HASH=\"[a-z0-9]+\"')
249
250  temp_ebuild_file = '%s.temp' % ebuild_path
251
252  # A flag for whether 'LLVM_NEXT_HASH=...' was updated.
253  is_updated = False
254
255  with open(ebuild_path) as ebuild_file:
256    # write updates to a temporary file in case of interrupts
257    with open(temp_ebuild_file, 'w') as temp_file:
258      for cur_line in ReplaceLLVMNextHash(ebuild_file, is_updated, llvm_regex,
259                                          llvm_hash, llvm_version):
260        temp_file.write(cur_line)
261
262  os.rename(temp_ebuild_file, ebuild_path)
263
264  # Get the path to the parent directory.
265  parent_dir = os.path.dirname(ebuild_path)
266
267  # Stage the changes.
268  stage_changes_cmd = ['git', '-C', parent_dir, 'add', ebuild_path]
269
270  ExecCommandAndCaptureOutput(stage_changes_cmd, verbose=verbose)
271
272
273def ReplaceLLVMNextHash(ebuild_lines, is_updated, llvm_regex, llvm_hash,
274                        llvm_version):
275  """Iterates through the ebuild file and updates the 'LLVM_NEXT_HASH'.
276
277  Args:
278    ebuild_lines: The contents of the ebuild file.
279    is_updated: A flag for whether 'LLVM_NEXT_HASH' was updated.
280    llvm_regex: The regex object for finding 'LLVM_NEXT_HASH=...' when
281    iterating through the contents of the file.
282    llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH.
283    llvm_version: The revision number of 'llvm_hash'.
284  """
285
286  for cur_line in ebuild_lines:
287    if not is_updated and llvm_regex.search(cur_line):
288      # Update the LLVM next hash and revision number.
289      cur_line = 'LLVM_NEXT_HASH=\"%s\" # r%d\n' % (llvm_hash, llvm_version)
290
291      is_updated = True
292
293    yield cur_line
294
295  if not is_updated:  # failed to update 'LLVM_NEXT_HASH'
296    raise ValueError('Failed to update the LLVM hash.')
297
298
299def UprevEbuild(symlink):
300  """Uprevs the ebuild's revision number.
301
302  Increases the revision number by 1 and stages the change in
303  the temporary repo.
304
305  Args:
306    symlink: The absolute path of the symlink that points to
307    the ebuild of the package.
308
309  Raises:
310    ValueError: Failed to uprev the symlink or failed to stage the changes.
311  """
312
313  if not os.path.islink(symlink):
314    raise ValueError('Invalid symlink provided: %s' % symlink)
315
316  # Find the revision number and increment it by 1.
317  new_symlink, is_changed = re.subn(
318      r'r([0-9]+).ebuild',
319      lambda match: 'r%s.ebuild' % str(int(match.group(1)) + 1),
320      symlink,
321      count=1)
322
323  if not is_changed:  # failed to increment the revision number
324    raise ValueError('Failed to uprev the ebuild.')
325
326  path_to_symlink_dir = os.path.dirname(symlink)
327
328  # Stage the new symlink for commit.
329  stage_symlink_cmd = [
330      'git', '-C', path_to_symlink_dir, 'mv', symlink, new_symlink
331  ]
332
333  ExecCommandAndCaptureOutput(stage_symlink_cmd, verbose=verbose)
334
335
336def _CreateRepo(path_to_repo_dir, llvm_hash):
337  """Creates a temporary repo for the changes.
338
339  Args:
340    path_to_repo_dir: The absolute path to the repo.
341    llvm_hash: The LLVM hash to use for the name of the repo.
342
343  Raises:
344    ValueError: Failed to create a repo in that directory.
345  """
346
347  if not os.path.isdir(path_to_repo_dir):
348    raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir)
349
350  reset_changes_cmd = [
351      'git',
352      '-C',
353      path_to_repo_dir,
354      'reset',
355      'HEAD',
356      '--hard',
357  ]
358
359  ExecCommandAndCaptureOutput(reset_changes_cmd, verbose=verbose)
360
361  create_repo_cmd = ['repo', 'start', 'llvm-next-update-%s' % llvm_hash]
362
363  ExecCommandAndCaptureOutput(
364      create_repo_cmd, cwd=path_to_repo_dir, verbose=verbose)
365
366
367def _DeleteRepo(path_to_repo_dir, llvm_hash):
368  """Deletes the temporary repo.
369
370  Args:
371    path_to_repo_dir: The absolute path of the repo.
372    llvm_hash: The LLVM hash used for the name of the repo.
373
374  Raises:
375    ValueError: Failed to delete the repo in that directory.
376  """
377
378  if not os.path.isdir(path_to_repo_dir):
379    raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir)
380
381  checkout_to_master_cmd = [
382      'git', '-C', path_to_repo_dir, 'checkout', 'cros/master'
383  ]
384
385  ExecCommandAndCaptureOutput(checkout_to_master_cmd, verbose=verbose)
386
387  reset_head_cmd = ['git', '-C', path_to_repo_dir, 'reset', 'HEAD', '--hard']
388
389  ExecCommandAndCaptureOutput(reset_head_cmd, verbose=verbose)
390
391  delete_repo_cmd = [
392      'git', '-C', path_to_repo_dir, 'branch', '-D',
393      'llvm-next-update-%s' % llvm_hash
394  ]
395
396  ExecCommandAndCaptureOutput(delete_repo_cmd, verbose=verbose)
397
398
399def GetGerritRepoUploadContents(repo_upload_contents):
400  """Parses 'repo upload' to get the Gerrit commit URL and CL number.
401
402  Args:
403    repo_upload_contents: The contents of the 'repo upload' command.
404
405  Returns:
406    A nametuple that has two (key, value) pairs, where the first pair is the
407    Gerrit commit URL and the second pair is the change list number.
408
409  Raises:
410    ValueError: The contents of the 'repo upload' command did not contain a
411    Gerrit commit URL.
412  """
413
414  found_url = re.search(
415      r'https://chromium-review.googlesource.com/c/'
416      r'chromiumos/overlays/chromiumos-overlay/\+/([0-9]+)',
417      repo_upload_contents)
418
419  if not found_url:
420    raise ValueError('Failed to find change list URL.')
421
422  cl_number = int(found_url.group(1))
423
424  return CommitContents(url=found_url.group(0), cl_number=cl_number)
425
426
427def UploadChanges(path_to_repo_dir, llvm_hash, commit_messages):
428  """Uploads the changes (updating LLVM next hash and uprev symlink) for review.
429
430  Args:
431    path_to_repo_dir: The absolute path to the repo where changes were made.
432    llvm_hash: The LLVM hash used for the name of the repo.
433    commit_messages: A string of commit message(s) (i.e. '-m [message]'
434    of the changes made.
435
436  Returns:
437    A nametuple that has two (key, value) pairs, where the first pair is the
438    Gerrit commit URL and the second pair is the change list number.
439
440  Raises:
441    ValueError: Failed to create a commit or failed to upload the
442    changes for review.
443  """
444
445  if not os.path.isdir(path_to_repo_dir):
446    raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir)
447
448  commit_cmd = [
449      'git',
450      'commit',
451  ]
452  commit_cmd.extend(commit_messages)
453
454  ExecCommandAndCaptureOutput(commit_cmd, cwd=path_to_repo_dir, verbose=verbose)
455
456  # Upload the changes for review.
457  upload_change_cmd = (
458      'yes | repo upload --br=llvm-next-update-%s --no-verify' % llvm_hash)
459
460  # Pylint currently doesn't lint things in py3 mode, and py2 didn't allow
461  # users to specify `encoding`s for Popen. Hence, pylint is "wrong" here.
462  # pylint: disable=unexpected-keyword-arg
463
464  # NOTE: Need `shell=True` in order to pipe `yes` into `repo upload ...`.
465  #
466  # The CL URL is sent to 'stderr', so need to redirect 'stderr' to 'stdout'.
467  upload_changes_obj = subprocess.Popen(
468      upload_change_cmd,
469      cwd=path_to_repo_dir,
470      shell=True,
471      encoding='UTF-8',
472      stdout=subprocess.PIPE,
473      stderr=subprocess.STDOUT)
474
475  out, _ = upload_changes_obj.communicate()
476
477  if upload_changes_obj.returncode:  # Failed to upload changes.
478    print(out)
479    raise ValueError('Failed to upload changes for review')
480
481  return GetGerritRepoUploadContents(out.rstrip())
482
483
484def CreatePathDictionaryFromPackages(chroot_path, update_packages):
485  """Creates a symlink and ebuild path pair dictionary from the packages.
486
487  Args:
488    chroot_path: The absolute path to the chroot.
489    update_packages: The filtered packages to be updated.
490
491  Returns:
492    A dictionary where the key is the absolute path to the symlink
493    of the package and the value is the absolute path to the ebuild of
494    the package.
495  """
496
497  # Construct a list containing the chroot file paths of the package(s).
498  chroot_file_paths = GetChrootBuildPaths(chroot_path, update_packages)
499
500  # Construct a list containing the symlink(s) of the package(s).
501  symlink_file_paths = _ConvertChrootPathsToSymLinkPaths(
502      chroot_path, chroot_file_paths)
503
504  # Create a dictionary where the key is the absolute path of the symlink to
505  # the package and the value is the absolute path to the ebuild of the package.
506  return GetEbuildPathsFromSymLinkPaths(symlink_file_paths)
507
508
509def RemovePatchesFromFilesDir(patches_to_remove):
510  """Removes the patches from $FILESDIR of a package.
511
512  Args:
513    patches_to_remove: A list where each entry is the absolute path to a patch.
514
515  Raises:
516    ValueError: Failed to remove a patch in $FILESDIR.
517  """
518
519  for cur_patch in patches_to_remove:
520    remove_patch_cmd = [
521        'git', '-C',
522        os.path.dirname(cur_patch), 'rm', '-f', cur_patch
523    ]
524
525    ExecCommandAndCaptureOutput(remove_patch_cmd, verbose=verbose)
526
527
528def StagePatchMetadataFileForCommit(patch_metadata_file_path):
529  """Stages the updated patch metadata file for commit.
530
531  Args:
532    patch_metadata_file_path: The absolute path to the patch metadata file.
533
534  Raises:
535    ValueError: Failed to stage the patch metadata file for commit or invalid
536    patch metadata file.
537  """
538
539  if not os.path.isfile(patch_metadata_file_path):
540    raise ValueError(
541        'Invalid patch metadata file provided: %s' % patch_metadata_file_path)
542
543  # Cmd to stage the patch metadata file for commit.
544  stage_patch_file = [
545      'git', '-C',
546      os.path.dirname(patch_metadata_file_path), 'add', patch_metadata_file_path
547  ]
548
549  ExecCommandAndCaptureOutput(stage_patch_file, verbose=verbose)
550
551
552def StagePackagesPatchResultsForCommit(package_info_dict, commit_messages):
553  """Stages the patch results of the packages to the commit message.
554
555  Args:
556    package_info_dict: A dictionary where the key is the package name and the
557    value is a dictionary that contains information about the patches of the
558    package (key).
559    commit_messages: The commit message that has the updated ebuilds and
560    upreving information.
561  """
562
563  # For each package, check if any patches for that package have
564  # changed, if so, add which patches have changed to the commit
565  # message.
566  for package_name, patch_info_dict in package_info_dict.items():
567    if patch_info_dict['disabled_patches'] or \
568        patch_info_dict['removed_patches'] or \
569        patch_info_dict['modified_metadata']:
570      cur_package_header = 'For the package %s:' % package_name
571      commit_messages.append('-m %s' % cur_package_header)
572
573    # Add to the commit message that the patch metadata file was modified.
574    if patch_info_dict['modified_metadata']:
575      patch_metadata_path = patch_info_dict['modified_metadata']
576      commit_messages.append('-m %s' % 'The patch metadata file %s was '
577                             'modified' % os.path.basename(patch_metadata_path))
578
579      StagePatchMetadataFileForCommit(patch_metadata_path)
580
581    # Add each disabled patch to the commit message.
582    if patch_info_dict['disabled_patches']:
583      commit_messages.append('-m %s' % 'The following patches were disabled:')
584
585      for patch_path in patch_info_dict['disabled_patches']:
586        commit_messages.append('-m %s' % os.path.basename(patch_path))
587
588    # Add each removed patch to the commit message.
589    if patch_info_dict['removed_patches']:
590      commit_messages.append('-m %s' % 'The following patches were removed:')
591
592      for patch_path in patch_info_dict['removed_patches']:
593        commit_messages.append('-m %s' % os.path.basename(patch_path))
594
595      RemovePatchesFromFilesDir(patch_info_dict['removed_patches'])
596
597  return commit_messages
598
599
600def UpdatePackages(packages, llvm_hash, llvm_version, chroot_path,
601                   patch_metadata_file, mode, svn_option):
602  """Updates the package's LLVM_NEXT_HASH and uprevs the ebuild.
603
604  A temporary repo is created for the changes. The changes are
605  then uploaded for review.
606
607  Args:
608    packages: A list of all the packages that are going to be updated.
609    llvm_hash: The LLVM hash to use for 'LLVM_NEXT_HASH'.
610    llvm_version: The LLVM version of the 'llvm_hash'.
611    chroot_path: The absolute path to the chroot.
612    patch_metadata_file: The name of the .json file in '$FILESDIR/' that has
613    the patches and its metadata.
614    mode: The mode of the patch manager when handling an applicable patch
615    that failed to apply.
616      Ex: 'FailureModes.FAIL'
617    svn_option: The git hash to use based off of the svn option.
618      Ex: 'google3', 'tot', or <svn_version> such as 365123
619
620  Returns:
621    A nametuple that has two (key, value) pairs, where the first pair is the
622    Gerrit commit URL and the second pair is the change list number.
623  """
624
625  # Determines whether to print the result of each executed command.
626  llvm_patch_management.verbose = verbose
627
628  # Construct a dictionary where the key is the absolute path of the symlink to
629  # the package and the value is the absolute path to the ebuild of the package.
630  paths_dict = CreatePathDictionaryFromPackages(chroot_path, packages)
631
632  repo_path = os.path.dirname(next(iter(paths_dict.values())))
633
634  _CreateRepo(repo_path, llvm_hash)
635
636  try:
637    if svn_option in get_llvm_hash.KNOWN_HASH_SOURCES:
638      commit_message_header = ('llvm-next/%s: upgrade to %s (r%d)' %
639                               (svn_option, llvm_hash, llvm_version))
640    else:
641      commit_message_header = (
642          'llvm-next: upgrade to %s (r%d)' % (llvm_hash, llvm_version))
643
644    commit_messages = ['-m %s' % commit_message_header]
645
646    commit_messages.append('-m The following packages have been updated:')
647
648    # Holds the list of packages that are updating.
649    packages = []
650
651    # Iterate through the dictionary.
652    #
653    # For each iteration:
654    # 1) Update the ebuild's LLVM_NEXT_HASH.
655    # 2) Uprev the ebuild (symlink).
656    # 3) Add the modified package to the commit message.
657    for symlink_path, ebuild_path in paths_dict.items():
658      path_to_ebuild_dir = os.path.dirname(ebuild_path)
659
660      UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version)
661
662      UprevEbuild(symlink_path)
663
664      cur_dir_name = os.path.basename(path_to_ebuild_dir)
665      parent_dir_name = os.path.basename(os.path.dirname(path_to_ebuild_dir))
666
667      packages.append('%s/%s' % (parent_dir_name, cur_dir_name))
668
669      new_commit_message = '%s/%s' % (parent_dir_name, cur_dir_name)
670
671      commit_messages.append('-m %s' % new_commit_message)
672
673    # Handle the patches for each package.
674    package_info_dict = llvm_patch_management.UpdatePackagesPatchMetadataFile(
675        chroot_path, llvm_version, patch_metadata_file, packages, mode)
676
677    # Update the commit message if changes were made to a package's patches.
678    commit_messages = StagePackagesPatchResultsForCommit(
679        package_info_dict, commit_messages)
680
681    change_list = UploadChanges(repo_path, llvm_hash, commit_messages)
682
683  finally:
684    _DeleteRepo(repo_path, llvm_hash)
685
686  return change_list
687
688
689def main():
690  """Updates the LLVM next hash for each package.
691
692  Raises:
693    AssertionError: The script was run inside the chroot.
694  """
695
696  VerifyOutsideChroot()
697
698  args_output = GetCommandLineArgs()
699
700  svn_option = args_output.llvm_version
701
702  llvm_hash, llvm_version = GetLLVMHashAndVersionFromSVNOption(svn_option)
703
704  change_list = UpdatePackages(
705      args_output.update_packages, llvm_hash, llvm_version,
706      args_output.chroot_path, args_output.patch_metadata_file,
707      FailureModes(args_output.failure_mode), svn_option)
708
709  print('Successfully updated packages to %d' % llvm_version)
710  print('Gerrit URL: %s' % change_list.url)
711  print('Change list number: %d' % change_list.cl_number)
712
713
714if __name__ == '__main__':
715  main()
716