diff options
Diffstat (limited to 'llvm_tools')
-rw-r--r-- | llvm_tools/README.md | 1 | ||||
-rwxr-xr-x | llvm_tools/get_upstream_patch.py | 249 | ||||
-rwxr-xr-x | llvm_tools/llvm_bisection.py | 151 | ||||
-rwxr-xr-x | llvm_tools/modify_a_tryjob.py | 75 | ||||
-rwxr-xr-x | llvm_tools/nightly_revert_checker.py | 97 | ||||
-rw-r--r-- | llvm_tools/patch_sync/.gitignore | 1 | ||||
-rw-r--r-- | llvm_tools/patch_sync/Cargo.lock | 453 | ||||
-rw-r--r-- | llvm_tools/patch_sync/Cargo.toml | 20 | ||||
-rw-r--r-- | llvm_tools/patch_sync/src/main.rs | 173 | ||||
-rw-r--r-- | llvm_tools/patch_sync/src/patch_parsing.rs | 313 | ||||
-rw-r--r-- | llvm_tools/patch_sync/src/version_control.rs | 238 | ||||
-rwxr-xr-x | llvm_tools/update_chromeos_llvm_hash.py | 75 | ||||
-rwxr-xr-x | llvm_tools/update_packages_and_run_tests.py | 100 |
13 files changed, 1616 insertions, 330 deletions
diff --git a/llvm_tools/README.md b/llvm_tools/README.md index 43c80ad6..74fad6c9 100644 --- a/llvm_tools/README.md +++ b/llvm_tools/README.md @@ -119,6 +119,7 @@ For example, to create a roll CL to the git hash of revision 367622: $ ./update_chromeos_llvm_hash.py \ --update_packages sys-devel/llvm sys-libs/compiler-rt \ sys-libs/libcxx sys-libs/libcxxabi sys-libs/llvm-libunwind \ + 'dev-util/lldb-server' \ --llvm_version 367622 \ --failure_mode disable_patches ``` diff --git a/llvm_tools/get_upstream_patch.py b/llvm_tools/get_upstream_patch.py index 77783d41..7a4be3eb 100755 --- a/llvm_tools/get_upstream_patch.py +++ b/llvm_tools/get_upstream_patch.py @@ -6,8 +6,6 @@ """Get an upstream patch to LLVM's PATCHES.json.""" -from __future__ import print_function - import argparse import json import logging @@ -18,12 +16,20 @@ import sys import typing as t from datetime import datetime +import dataclasses + import chroot import get_llvm_hash import git import git_llvm_rev import update_chromeos_llvm_hash +__DOC_EPILOGUE = """ +Example Usage: + get_upstream_patch --chroot_path ~/chromiumos --platform chromiumos \ +--sha 1234567 --sha 890abdc +""" + class CherrypickError(ValueError): """A ValueError that highlights the cherry-pick has been seen before""" @@ -32,7 +38,7 @@ class CherrypickError(ValueError): def add_patch(patches_json_path: str, patches_dir: str, relative_patches_dir: str, start_version: git_llvm_rev.Rev, llvm_dir: str, rev: t.Union[git_llvm_rev.Rev, str], sha: str, - package: str): + package: str, platforms: t.List[str]): """Gets the start and end intervals in 'json_file'. Args: @@ -47,6 +53,7 @@ def add_patch(patches_json_path: str, patches_dir: str, revisions, the git sha from the local commit created by 'arc patch' is used. package: The LLVM project name this patch applies to. + platforms: List of platforms this patch applies to. Raises: CherrypickError: A ValueError that highlights the cherry-pick has been @@ -85,20 +92,29 @@ def add_patch(patches_json_path: str, patches_dir: str, subprocess.check_call(cmd, stdout=f, cwd=llvm_dir) commit_subject = subprocess.check_output( - ['git', 'log', '-n1', '--format=%s', sha], cwd=llvm_dir, encoding='utf-8') + ['git', 'log', '-n1', '--format=%s', sha], + cwd=llvm_dir, + encoding='utf-8') - patch_metadata = { - 'comment': commit_subject.strip(), + patch_props = { 'rel_patch_path': rel_patch_path, 'start_version': start_version.number, + 'metadata': { + 'title': commit_subject.strip(), + 'info': [], + }, + 'platforms': sorted(platforms), + 'end_version': rev.number if isinstance(rev, git_llvm_rev.Rev) else None, } - if isinstance(rev, git_llvm_rev.Rev): - patch_metadata['end_version'] = rev.number - patches_json.append(patch_metadata) + patches_json.append(patch_props) temp_file = patches_json_path + '.tmp' with open(temp_file, 'w', encoding='utf-8') as f: - json.dump(patches_json, f, indent=4, separators=(',', ': ')) + json.dump(patches_json, + f, + indent=4, + separators=(',', ': '), + sort_keys=True) f.write('\n') os.rename(temp_file, patches_json_path) @@ -178,7 +194,7 @@ def get_package_names(sha: str, llvm_dir: str) -> list: def create_patch_for_packages(packages: t.List[str], symlinks: t.List[str], start_rev: git_llvm_rev.Rev, rev: t.Union[git_llvm_rev.Rev, str], sha: str, - llvm_dir: str): + llvm_dir: str, platforms: t.List[str]): """Create a patch and add its metadata for each package""" for package, symlink in zip(packages, symlinks): symlink_dir = os.path.dirname(symlink) @@ -186,8 +202,15 @@ def create_patch_for_packages(packages: t.List[str], symlinks: t.List[str], relative_patches_dir = 'cherry' if package == 'llvm' else '' patches_dir = os.path.join(symlink_dir, 'files', relative_patches_dir) logging.info('Getting %s (%s) into %s', rev, sha, package) - add_patch(patches_json_path, patches_dir, relative_patches_dir, start_rev, - llvm_dir, rev, sha, package) + add_patch(patches_json_path, + patches_dir, + relative_patches_dir, + start_rev, + llvm_dir, + rev, + sha, + package, + platforms=platforms) def make_cl(symlinks_to_uprev: t.List[str], llvm_symlink_dir: str, branch: str, @@ -212,35 +235,35 @@ def resolve_symbolic_sha(start_sha: str, llvm_symlink_dir: str) -> str: return start_sha -def find_patches_and_make_cl(chroot_path: str, patches: t.List[str], - start_rev: git_llvm_rev.Rev, - llvm_config: git_llvm_rev.LLVMConfig, - llvm_symlink_dir: str, create_cl: bool, - reviewers: t.Optional[t.List[str]], - cc: t.Optional[t.List[str]]): +def find_patches_and_make_cl( + chroot_path: str, patches: t.List[str], start_rev: git_llvm_rev.Rev, + llvm_config: git_llvm_rev.LLVMConfig, llvm_symlink_dir: str, + create_cl: bool, skip_dependencies: bool, + reviewers: t.Optional[t.List[str]], cc: t.Optional[t.List[str]], + platforms: t.List[str]): + + converted_patches = [ + _convert_patch(llvm_config, skip_dependencies, p) for p in patches + ] + potential_duplicates = _get_duplicate_shas(converted_patches) + if potential_duplicates: + err_msg = '\n'.join(f'{a.patch} == {b.patch}' + for a, b in potential_duplicates) + raise RuntimeError(f'Found Duplicate SHAs:\n{err_msg}') + + # CL Related variables, only used if `create_cl` + symlinks_to_uprev = [] + commit_messages = [ + 'llvm: get patches from upstream\n', + ] + branch = f'get-upstream-{datetime.now().strftime("%Y%m%d%H%M%S%f")}' + if create_cl: - branch = f'get-upstream-{datetime.now().strftime("%Y%m%d%H%M%S%f")}' git.CreateBranch(llvm_symlink_dir, branch) - symlinks_to_uprev = [] - commit_messages = [ - 'llvm: get patches from upstream\n', - ] - - for patch in patches: - # git hash should only have lower-case letters - is_differential = patch.startswith('D') - if is_differential: - subprocess.check_output( - ['arc', 'patch', '--nobranch', '--revision', patch], - cwd=llvm_config.dir, - ) - sha = resolve_llvm_ref(llvm_config.dir, 'HEAD') - rev = patch - else: - sha = resolve_llvm_ref(llvm_config.dir, patch) - rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha) + + for parsed_patch in converted_patches: # Find out the llvm projects changed in this commit - packages = get_package_names(sha, llvm_config.dir) + packages = get_package_names(parsed_patch.sha, llvm_config.dir) # Find out the ebuild symlinks of the corresponding ChromeOS packages symlinks = chroot.GetChrootEbuildPaths(chroot_path, [ 'sys-devel/llvm' if package == 'llvm' else 'sys-libs/' + package @@ -248,23 +271,25 @@ def find_patches_and_make_cl(chroot_path: str, patches: t.List[str], ]) symlinks = chroot.ConvertChrootPathsToAbsolutePaths(chroot_path, symlinks) # Create a local patch for all the affected llvm projects - create_patch_for_packages(packages, symlinks, start_rev, rev, sha, - llvm_config.dir) + create_patch_for_packages(packages, + symlinks, + start_rev, + parsed_patch.rev, + parsed_patch.sha, + llvm_config.dir, + platforms=platforms) if create_cl: symlinks_to_uprev.extend(symlinks) - if is_differential: - msg = f'\n\nreviews.llvm.org/{patch}\n' - else: - msg = f'\n\nreviews.llvm.org/rG{sha}\n' commit_messages.extend([ - msg, - subprocess.check_output(['git', 'log', '-n1', '--oneline', sha], - cwd=llvm_config.dir, - encoding='utf-8') + parsed_patch.git_msg(), + subprocess.check_output( + ['git', 'log', '-n1', '--oneline', parsed_patch.sha], + cwd=llvm_config.dir, + encoding='utf-8') ]) - if is_differential: + if parsed_patch.is_differential: subprocess.check_output(['git', 'reset', '--hard', 'HEAD^'], cwd=llvm_config.dir) @@ -273,10 +298,67 @@ def find_patches_and_make_cl(chroot_path: str, patches: t.List[str], reviewers, cc) +@dataclasses.dataclass(frozen=True) +class ParsedPatch: + """Class to keep track of bundled patch info.""" + patch: str + sha: str + is_differential: bool + rev: t.Union[git_llvm_rev.Rev, str] + + def git_msg(self) -> str: + if self.is_differential: + return f'\n\nreviews.llvm.org/{self.patch}\n' + return f'\n\nreviews.llvm.org/rG{self.sha}\n' + + +def _convert_patch(llvm_config: git_llvm_rev.LLVMConfig, + skip_dependencies: bool, patch: str) -> ParsedPatch: + """Extract git revision info from a patch. + + Args: + llvm_config: LLVM configuration object. + skip_dependencies: Pass --skip-dependecies for to `arc` + patch: A single patch referent string. + + Returns: + A [ParsedPatch] object. + """ + + # git hash should only have lower-case letters + is_differential = patch.startswith('D') + if is_differential: + subprocess.check_output( + [ + 'arc', 'patch', '--nobranch', + '--skip-dependencies' if skip_dependencies else '--revision', patch + ], + cwd=llvm_config.dir, + ) + sha = resolve_llvm_ref(llvm_config.dir, 'HEAD') + rev = patch + else: + sha = resolve_llvm_ref(llvm_config.dir, patch) + rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha) + return ParsedPatch(patch=patch, + sha=sha, + rev=rev, + is_differential=is_differential) + + +def _get_duplicate_shas( + patches: t.List[ParsedPatch]) -> t.List[t.Tuple[ParsedPatch, ParsedPatch]]: + """Return a list of Patches which have duplicate SHA's""" + return [(left, right) for i, left in enumerate(patches) + for right in patches[i + 1:] if left.sha == right.sha] + + def get_from_upstream(chroot_path: str, create_cl: bool, start_sha: str, patches: t.List[str], + platforms: t.List[str], + skip_dependencies: bool = False, reviewers: t.List[str] = None, cc: t.List[str] = None): llvm_symlink = chroot.ConvertChrootPathsToAbsolutePaths( @@ -299,15 +381,17 @@ def get_from_upstream(chroot_path: str, remote='origin', dir=get_llvm_hash.GetAndUpdateLLVMProjectInLLVMTools()) start_sha = resolve_llvm_ref(llvm_config.dir, start_sha) - find_patches_and_make_cl( - chroot_path=chroot_path, - patches=patches, - start_rev=git_llvm_rev.translate_sha_to_rev(llvm_config, start_sha), - llvm_config=llvm_config, - llvm_symlink_dir=llvm_symlink_dir, - create_cl=create_cl, - reviewers=reviewers, - cc=cc) + find_patches_and_make_cl(chroot_path=chroot_path, + patches=patches, + platforms=platforms, + start_rev=git_llvm_rev.translate_sha_to_rev( + llvm_config, start_sha), + llvm_config=llvm_config, + llvm_symlink_dir=llvm_symlink_dir, + create_cl=create_cl, + skip_dependencies=skip_dependencies, + reviewers=reviewers, + cc=cc) logging.info('Complete.') @@ -318,41 +402,60 @@ def main(): level=logging.INFO, ) - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--chroot_path', - default=os.path.join(os.path.expanduser('~'), 'chromiumos'), - help='the path to the chroot (default: %(default)s)') + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__DOC_EPILOGUE) + parser.add_argument('--chroot_path', + default=os.path.join(os.path.expanduser('~'), + 'chromiumos'), + help='the path to the chroot (default: %(default)s)') parser.add_argument( '--start_sha', default='llvm-next', help='LLVM SHA that the patch should start applying at. You can specify ' '"llvm" or "llvm-next", as well. Defaults to %(default)s.') - parser.add_argument( - '--sha', - action='append', - default=[], - help='The LLVM git SHA to cherry-pick.') + parser.add_argument('--sha', + action='append', + default=[], + help='The LLVM git SHA to cherry-pick.') parser.add_argument( '--differential', action='append', default=[], help='The LLVM differential revision to apply. Example: D1234') parser.add_argument( - '--create_cl', - default=False, + '--platform', + action='append', + required=True, + help='Apply this patch to the give platform. Common options include ' + '"chromiumos" and "android". Can be specified multiple times to ' + 'apply to multiple platforms') + parser.add_argument('--create_cl', + action='store_true', + help='Automatically create a CL if specified') + parser.add_argument( + '--skip_dependencies', action='store_true', - help='Automatically create a CL if specified') + help="Skips a LLVM differential revision's dependencies. Only valid " + 'when --differential appears exactly once.') args = parser.parse_args() if not (args.sha or args.differential): parser.error('--sha or --differential required') + if args.skip_dependencies and len(args.differential) != 1: + parser.error("--skip_dependencies is only valid when there's exactly one " + 'supplied differential') + get_from_upstream( chroot_path=args.chroot_path, create_cl=args.create_cl, start_sha=args.start_sha, - patches=args.sha + args.differential) + patches=args.sha + args.differential, + skip_dependencies=args.skip_dependencies, + platforms=args.platform, + ) if __name__ == '__main__': diff --git a/llvm_tools/llvm_bisection.py b/llvm_tools/llvm_bisection.py index b1898ea9..0148efd2 100755 --- a/llvm_tools/llvm_bisection.py +++ b/llvm_tools/llvm_bisection.py @@ -20,6 +20,7 @@ import chroot import get_llvm_hash import git_llvm_rev import modify_a_tryjob +import update_chromeos_llvm_hash import update_tryjob_status @@ -51,18 +52,16 @@ def GetCommandLineArgs(): 'the first bad version (default: %(default)s)') # Add argument for the good LLVM revision for bisection. - parser.add_argument( - '--start_rev', - required=True, - type=int, - help='The good revision for the bisection.') + parser.add_argument('--start_rev', + required=True, + type=int, + help='The good revision for the bisection.') # Add argument for the bad LLVM revision for bisection. - parser.add_argument( - '--end_rev', - required=True, - type=int, - help='The bad revision for the bisection.') + parser.add_argument('--end_rev', + required=True, + type=int, + help='The bad revision for the bisection.') # Add argument for the absolute path to the file that contains information on # the previous tested svn version. @@ -88,42 +87,38 @@ def GetCommandLineArgs(): 'of updating the packages') # Add argument for custom options for the tryjob. - parser.add_argument( - '--options', - required=False, - nargs='+', - help='options to use for the tryjob testing') + parser.add_argument('--options', + required=False, + nargs='+', + help='options to use for the tryjob testing') # Add argument for the builder to use for the tryjob. - parser.add_argument( - '--builder', required=True, help='builder to use for the tryjob testing') + parser.add_argument('--builder', + required=True, + help='builder to use for the tryjob testing') # Add argument for the description of the tryjob. - parser.add_argument( - '--description', - required=False, - nargs='+', - help='the description of the tryjob') + parser.add_argument('--description', + required=False, + nargs='+', + help='the description of the tryjob') # Add argument for a specific chroot path. - parser.add_argument( - '--chroot_path', - default=cros_root, - help='the path to the chroot (default: %(default)s)') + parser.add_argument('--chroot_path', + default=cros_root, + help='the path to the chroot (default: %(default)s)') # Add argument for whether to display command contents to `stdout`. - parser.add_argument( - '--verbose', - action='store_true', - help='display contents of a command to the terminal ' - '(default: %(default)s)') + parser.add_argument('--verbose', + action='store_true', + help='display contents of a command to the terminal ' + '(default: %(default)s)') # Add argument for whether to display command contents to `stdout`. - parser.add_argument( - '--nocleanup', - action='store_false', - dest='cleanup', - help='Abandon CLs created for bisectoin') + parser.add_argument('--nocleanup', + action='store_false', + dest='cleanup', + help='Abandon CLs created for bisectoin') args_output = parser.parse_args() @@ -174,8 +169,7 @@ def GetRemainingRange(start, end, tryjobs): all_bad_revisions = [end] all_bad_revisions.extend( - cur_tryjob['rev'] - for cur_tryjob in tryjobs + cur_tryjob['rev'] for cur_tryjob in tryjobs if cur_tryjob['status'] == update_tryjob_status.TryjobStatus.BAD.value) # The minimum value for the 'bad' field in the tryjobs is the new end @@ -184,8 +178,7 @@ def GetRemainingRange(start, end, tryjobs): all_good_revisions = [start] all_good_revisions.extend( - cur_tryjob['rev'] - for cur_tryjob in tryjobs + cur_tryjob['rev'] for cur_tryjob in tryjobs if cur_tryjob['status'] == update_tryjob_status.TryjobStatus.GOOD.value) # The maximum value for the 'good' field in the tryjobs is the new start @@ -205,8 +198,8 @@ def GetRemainingRange(start, end, tryjobs): pending_revisions = { tryjob['rev'] for tryjob in tryjobs - if tryjob['status'] == update_tryjob_status.TryjobStatus.PENDING.value and - good_rev < tryjob['rev'] < bad_rev + if tryjob['status'] == update_tryjob_status.TryjobStatus.PENDING.value + and good_rev < tryjob['rev'] < bad_rev } # Find all revisions that are to be skipped within 'good_rev' and 'bad_rev'. @@ -217,8 +210,8 @@ def GetRemainingRange(start, end, tryjobs): skip_revisions = { tryjob['rev'] for tryjob in tryjobs - if tryjob['status'] == update_tryjob_status.TryjobStatus.SKIP.value and - good_rev < tryjob['rev'] < bad_rev + if tryjob['status'] == update_tryjob_status.TryjobStatus.SKIP.value + and good_rev < tryjob['rev'] < bad_rev } return good_rev, bad_rev, pending_revisions, skip_revisions @@ -295,66 +288,62 @@ def main(args_output): """ chroot.VerifyOutsideChroot() - update_packages = [ - 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', - 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' - ] patch_metadata_file = 'PATCHES.json' start = args_output.start_rev end = args_output.end_rev bisect_state = LoadStatusFile(args_output.last_tested, start, end) if start != bisect_state['start'] or end != bisect_state['end']: - raise ValueError(f'The start {start} or the end {end} version provided is ' - f'different than "start" {bisect_state["start"]} or "end" ' - f'{bisect_state["end"]} in the .JSON file') + raise ValueError( + f'The start {start} or the end {end} version provided is ' + f'different than "start" {bisect_state["start"]} or "end" ' + f'{bisect_state["end"]} in the .JSON file') - # Pending and skipped revisions are between 'start_revision' and - # 'end_revision'. - start_revision, end_revision, pending_revisions, skip_revisions = \ - GetRemainingRange(start, end, bisect_state['jobs']) + # Pending and skipped revisions are between 'start_rev' and 'end_rev'. + start_rev, end_rev, pending_revs, skip_revs = GetRemainingRange( + start, end, bisect_state['jobs']) - revisions, git_hashes = GetCommitsBetween(start_revision, end_revision, + revisions, git_hashes = GetCommitsBetween(start_rev, end_rev, args_output.parallel, - args_output.src_path, - pending_revisions, skip_revisions) + args_output.src_path, pending_revs, + skip_revs) - # No more revisions between 'start_revision' and 'end_revision', so + # No more revisions between 'start_rev' and 'end_rev', so # bisection is complete. # - # This is determined by finding all valid revisions between 'start_revision' - # and 'end_revision' and that are NOT in the 'pending' and 'skipped' set. + # This is determined by finding all valid revisions between 'start_rev' + # and 'end_rev' and that are NOT in the 'pending' and 'skipped' set. if not revisions: - if pending_revisions: + if pending_revs: # Some tryjobs are not finished which may change the actual bad # commit/revision when those tryjobs are finished. - no_revisions_message = (f'No revisions between start {start_revision} ' - f'and end {end_revision} to create tryjobs\n') + no_revisions_message = (f'No revisions between start {start_rev} ' + f'and end {end_rev} to create tryjobs\n') - if pending_revisions: - no_revisions_message += ( - 'The following tryjobs are pending:\n' + - '\n'.join(str(rev) for rev in pending_revisions) + '\n') + if pending_revs: + no_revisions_message += ('The following tryjobs are pending:\n' + + '\n'.join(str(rev) + for rev in pending_revs) + '\n') - if skip_revisions: + if skip_revs: no_revisions_message += ('The following tryjobs were skipped:\n' + - '\n'.join(str(rev) for rev in skip_revisions) + - '\n') + '\n'.join(str(rev) + for rev in skip_revs) + '\n') raise ValueError(no_revisions_message) print(f'Finished bisecting for {args_output.last_tested}') if args_output.src_path: bad_llvm_hash = get_llvm_hash.GetGitHashFrom(args_output.src_path, - end_revision) + end_rev) else: - bad_llvm_hash = get_llvm_hash.LLVMHash().GetLLVMHash(end_revision) - print(f'The bad revision is {end_revision} and its commit hash is ' + bad_llvm_hash = get_llvm_hash.LLVMHash().GetLLVMHash(end_rev) + print(f'The bad revision is {end_rev} and its commit hash is ' f'{bad_llvm_hash}') - if skip_revisions: - skip_revisions_message = ('\nThe following revisions were skipped:\n' + - '\n'.join(str(rev) for rev in skip_revisions)) - print(skip_revisions_message) + if skip_revs: + skip_revs_message = ('\nThe following revisions were skipped:\n' + + '\n'.join(str(rev) for rev in skip_revs)) + print(skip_revs_message) if args_output.cleanup: # Abandon all the CLs created for bisection @@ -378,9 +367,9 @@ def main(args_output): raise ValueError(f'Revision {rev} exists already in "jobs"') Bisect(revisions, git_hashes, bisect_state, args_output.last_tested, - update_packages, args_output.chroot_path, patch_metadata_file, - args_output.extra_change_lists, args_output.options, - args_output.builder, args_output.verbose) + update_chromeos_llvm_hash.DEFAULT_PACKAGES, args_output.chroot_path, + patch_metadata_file, args_output.extra_change_lists, + args_output.options, args_output.builder, args_output.verbose) if __name__ == '__main__': diff --git a/llvm_tools/modify_a_tryjob.py b/llvm_tools/modify_a_tryjob.py index 4d41e6b2..519fb51e 100755 --- a/llvm_tools/modify_a_tryjob.py +++ b/llvm_tools/modify_a_tryjob.py @@ -17,9 +17,9 @@ import sys import chroot import failure_modes import get_llvm_hash +import update_chromeos_llvm_hash import update_packages_and_run_tests import update_tryjob_status -import update_chromeos_llvm_hash class ModifyTryjob(enum.Enum): @@ -57,11 +57,10 @@ def GetCommandLineArgs(): # Add argument that determines which revision to search for in the list of # tryjobs. - parser.add_argument( - '--revision', - required=True, - type=int, - help='The revision to either remove or relaunch.') + parser.add_argument('--revision', + required=True, + type=int, + help='The revision to either remove or relaunch.') # Add argument for other change lists that want to run alongside the tryjob. parser.add_argument( @@ -72,40 +71,38 @@ def GetCommandLineArgs(): 'of updating the packages') # Add argument for custom options for the tryjob. - parser.add_argument( - '--options', - required=False, - nargs='+', - help='options to use for the tryjob testing') + parser.add_argument('--options', + required=False, + nargs='+', + help='options to use for the tryjob testing') # Add argument for the builder to use for the tryjob. - parser.add_argument('--builder', help='builder to use for the tryjob testing') + parser.add_argument('--builder', + help='builder to use for the tryjob testing') # Add argument for a specific chroot path. - parser.add_argument( - '--chroot_path', - default=cros_root, - help='the path to the chroot (default: %(default)s)') + parser.add_argument('--chroot_path', + default=cros_root, + help='the path to the chroot (default: %(default)s)') # Add argument for whether to display command contents to `stdout`. - parser.add_argument( - '--verbose', - action='store_true', - help='display contents of a command to the terminal ' - '(default: %(default)s)') + parser.add_argument('--verbose', + action='store_true', + help='display contents of a command to the terminal ' + '(default: %(default)s)') args_output = parser.parse_args() - if not os.path.isfile(args_output.status_file) or \ - not args_output.status_file.endswith('.json'): + if (not os.path.isfile(args_output.status_file) + or not args_output.status_file.endswith('.json')): raise ValueError('File does not exist or does not ending in ".json" ' ': %s' % args_output.status_file) - if args_output.modify_tryjob == ModifyTryjob.ADD.value and \ - not args_output.builder: + if (args_output.modify_tryjob == ModifyTryjob.ADD.value + and not args_output.builder): raise ValueError('A builder is required for adding a tryjob.') - elif args_output.modify_tryjob != ModifyTryjob.ADD.value and \ - args_output.builder: + elif (args_output.modify_tryjob != ModifyTryjob.ADD.value + and args_output.builder): raise ValueError('Specifying a builder is only available when adding a ' 'tryjob.') @@ -234,13 +231,13 @@ def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls, bisect_contents['jobs'][tryjob_index]['cl'], bisect_contents['jobs'][tryjob_index]['extra_cls'], bisect_contents['jobs'][tryjob_index]['options'], - bisect_contents['jobs'][tryjob_index]['builder'], chroot_path, verbose) + bisect_contents['jobs'][tryjob_index]['builder'], chroot_path) bisect_contents['jobs'][tryjob_index][ 'status'] = update_tryjob_status.TryjobStatus.PENDING.value bisect_contents['jobs'][tryjob_index]['link'] = tryjob_results[0]['link'] - bisect_contents['jobs'][tryjob_index]['buildbucket_id'] = tryjob_results[0][ - 'buildbucket_id'] + bisect_contents['jobs'][tryjob_index]['buildbucket_id'] = tryjob_results[ + 0]['buildbucket_id'] print('Successfully relaunched the tryjob for revision %d and updated ' 'the tryjob link to %s' % (revision, tryjob_results[0]['link'])) @@ -253,17 +250,14 @@ def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls, # Make sure the revision is within the bounds of the start and end of the # bisection. elif bisect_contents['start'] < revision < bisect_contents['end']: - update_packages = [ - 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', - 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' - ] patch_metadata_file = 'PATCHES.json' git_hash, revision = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption( revision) - tryjob_dict = AddTryjob(update_packages, git_hash, revision, chroot_path, + tryjob_dict = AddTryjob(update_chromeos_llvm_hash.DEFAULT_PACKAGES, + git_hash, revision, chroot_path, patch_metadata_file, extra_cls, options, builder, verbose, revision) @@ -277,7 +271,10 @@ def PerformTryjobModification(revision, modify_tryjob, status_file, extra_cls, modify_tryjob) with open(status_file, 'w') as update_tryjobs: - json.dump(bisect_contents, update_tryjobs, indent=4, separators=(',', ': ')) + json.dump(bisect_contents, + update_tryjobs, + indent=4, + separators=(',', ': ')) def main(): @@ -290,9 +287,9 @@ def main(): PerformTryjobModification(args_output.revision, ModifyTryjob(args_output.modify_tryjob), args_output.status_file, - args_output.extra_change_lists, args_output.options, - args_output.builder, args_output.chroot_path, - args_output.verbose) + args_output.extra_change_lists, + args_output.options, args_output.builder, + args_output.chroot_path, args_output.verbose) if __name__ == '__main__': diff --git a/llvm_tools/nightly_revert_checker.py b/llvm_tools/nightly_revert_checker.py index 6941f3d6..5e878816 100755 --- a/llvm_tools/nightly_revert_checker.py +++ b/llvm_tools/nightly_revert_checker.py @@ -33,7 +33,7 @@ State = t.Any def _find_interesting_android_shas(android_llvm_toolchain_dir: str - ) -> t.List[t.Tuple[str, str]]: + ) -> t.List[t.Tuple[str, str]]: llvm_project = os.path.join(android_llvm_toolchain_dir, 'toolchain/llvm-project') @@ -44,7 +44,7 @@ def _find_interesting_android_shas(android_llvm_toolchain_dir: str encoding='utf-8', ).strip() - main_legacy = get_llvm_merge_base('aosp/master-legacy') + main_legacy = get_llvm_merge_base('aosp/master-legacy') # nocheck testing_upstream = get_llvm_merge_base('aosp/testing-upstream') result = [('main-legacy', main_legacy)] @@ -55,8 +55,7 @@ def _find_interesting_android_shas(android_llvm_toolchain_dir: str def _parse_llvm_ebuild_for_shas(ebuild_file: io.TextIOWrapper - ) -> t.List[t.Tuple[str, str]]: - + ) -> t.List[t.Tuple[str, str]]: def parse_ebuild_assignment(line: str) -> str: no_comments = line.split('#')[0] no_assign = no_comments.split('=', 1)[1].strip() @@ -84,12 +83,11 @@ def _parse_llvm_ebuild_for_shas(ebuild_file: io.TextIOWrapper def _find_interesting_chromeos_shas(chromeos_base: str - ) -> t.List[t.Tuple[str, str]]: + ) -> t.List[t.Tuple[str, str]]: llvm_dir = os.path.join(chromeos_base, 'src/third_party/chromiumos-overlay/sys-devel/llvm') candidate_ebuilds = [ - os.path.join(llvm_dir, x) - for x in os.listdir(llvm_dir) + os.path.join(llvm_dir, x) for x in os.listdir(llvm_dir) if '_pre' in x and not os.path.islink(os.path.join(llvm_dir, x)) ] @@ -197,8 +195,10 @@ def find_shas(llvm_dir: str, interesting_shas: t.List[t.Tuple[str, str]], state: State, new_state: State): for friendly_name, sha in interesting_shas: logging.info('Finding reverts across %s (%s)', friendly_name, sha) - all_reverts = revert_checker.find_reverts( - llvm_dir, sha, root='origin/' + git_llvm_rev.MAIN_BRANCH) + all_reverts = revert_checker.find_reverts(llvm_dir, + sha, + root='origin/' + + git_llvm_rev.MAIN_BRANCH) logging.info('Detected the following revert(s) across %s:\n%s', friendly_name, pprint.pformat(all_reverts)) @@ -230,13 +230,12 @@ def do_cherrypick(chroot_path: str, llvm_dir: str, seen.add(friendly_name) for sha, reverted_sha in reverts: try: - get_upstream_patch.get_from_upstream( - chroot_path=chroot_path, - create_cl=True, - start_sha=reverted_sha, - patches=[sha], - reviewers=reviewers, - cc=cc) + get_upstream_patch.get_from_upstream(chroot_path=chroot_path, + create_cl=True, + start_sha=reverted_sha, + patches=[sha], + reviewers=reviewers, + cc=cc) except get_upstream_patch.CherrypickError as e: logging.info('%s, skipping...', str(e)) return new_state @@ -245,7 +244,6 @@ def do_cherrypick(chroot_path: str, llvm_dir: str, def do_email(is_dry_run: bool, llvm_dir: str, repository: str, interesting_shas: t.List[t.Tuple[str, str]], state: State, recipients: _EmailRecipients) -> State: - def prettify_sha(sha: str) -> tiny_render.Piece: rev = get_llvm_hash.GetVersionFrom(llvm_dir, sha) @@ -253,8 +251,8 @@ def do_email(is_dry_run: bool, llvm_dir: str, repository: str, short_sha = sha[:12] return tiny_render.Switch( text=f'r{rev} ({short_sha})', - html=tiny_render.Link( - href='https://reviews.llvm.org/rG' + sha, inner='r' + str(rev)), + html=tiny_render.Link(href='https://reviews.llvm.org/rG' + sha, + inner='r' + str(rev)), ) def get_sha_description(sha: str) -> tiny_render.Piece: @@ -267,11 +265,12 @@ def do_email(is_dry_run: bool, llvm_dir: str, repository: str, new_state: State = {} for friendly_name, sha, new_reverts in find_shas(llvm_dir, interesting_shas, state, new_state): - email = _generate_revert_email(repository, friendly_name, sha, prettify_sha, - get_sha_description, new_reverts) + email = _generate_revert_email(repository, friendly_name, sha, + prettify_sha, get_sha_description, + new_reverts) if is_dry_run: - logging.info('Would send email:\nSubject: %s\nBody:\n%s\n', email.subject, - tiny_render.render_text_pieces(email.body)) + logging.info('Would send email:\nSubject: %s\nBody:\n%s\n', + email.subject, tiny_render.render_text_pieces(email.body)) else: logging.info('Sending email with subject %r...', email.subject) _send_revert_email(recipients, email) @@ -281,16 +280,19 @@ def do_email(is_dry_run: bool, llvm_dir: str, repository: str, def parse_args(argv: t.List[str]) -> t.Any: parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( 'action', choices=['cherry-pick', 'email', 'dry-run'], help='Automatically cherry-pick upstream reverts, send an email, or ' 'write to stdout.') - parser.add_argument( - '--state_file', required=True, help='File to store persistent state in.') - parser.add_argument( - '--llvm_dir', required=True, help='Up-to-date LLVM directory to use.') + parser.add_argument('--state_file', + required=True, + help='File to store persistent state in.') + parser.add_argument('--llvm_dir', + required=True, + help='Up-to-date LLVM directory to use.') parser.add_argument('--debug', action='store_true') parser.add_argument( '--reviewers', @@ -309,8 +311,9 @@ def parse_args(argv: t.List[str]) -> t.Any: subparsers.required = True chromeos_subparser = subparsers.add_parser('chromeos') - chromeos_subparser.add_argument( - '--chromeos_dir', required=True, help='Up-to-date CrOS directory to use.') + chromeos_subparser.add_argument('--chromeos_dir', + required=True, + help='Up-to-date CrOS directory to use.') android_subparser = subparsers.add_parser('android') android_subparser.add_argument( @@ -322,7 +325,7 @@ def parse_args(argv: t.List[str]) -> t.Any: def find_chroot(opts: t.Any, reviewers: t.List[str], cc: t.List[str] - ) -> t.Tuple[str, t.List[t.Tuple[str, str]], _EmailRecipients]: + ) -> t.Tuple[str, t.List[t.Tuple[str, str]], _EmailRecipients]: recipients = reviewers + cc if opts.repository == 'chromeos': chroot_path = opts.chromeos_dir @@ -335,9 +338,9 @@ def find_chroot(opts: t.Any, reviewers: t.List[str], cc: t.List[str] chroot_path = opts.android_llvm_toolchain_dir return (chroot_path, _find_interesting_android_shas(chroot_path), - _EmailRecipients( - well_known=[], - direct=['android-llvm-dev@google.com'] + recipients)) + _EmailRecipients(well_known=[], + direct=['android-llvm-dev@google.com'] + + recipients)) else: raise ValueError(f'Unknown repository {opts.repository}') @@ -366,21 +369,19 @@ def main(argv: t.List[str]) -> int: # We want to be as free of obvious side-effects as possible in case something # above breaks. Hence, action as late as possible. if action == 'cherry-pick': - new_state = do_cherrypick( - chroot_path=chroot_path, - llvm_dir=llvm_dir, - interesting_shas=interesting_shas, - state=state, - reviewers=reviewers, - cc=cc) + new_state = do_cherrypick(chroot_path=chroot_path, + llvm_dir=llvm_dir, + interesting_shas=interesting_shas, + state=state, + reviewers=reviewers, + cc=cc) else: - new_state = do_email( - is_dry_run=action == 'dry-run', - llvm_dir=llvm_dir, - repository=repository, - interesting_shas=interesting_shas, - state=state, - recipients=recipients) + new_state = do_email(is_dry_run=action == 'dry-run', + llvm_dir=llvm_dir, + repository=repository, + interesting_shas=interesting_shas, + state=state, + recipients=recipients) _write_state(state_file, new_state) return 0 diff --git a/llvm_tools/patch_sync/.gitignore b/llvm_tools/patch_sync/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/llvm_tools/patch_sync/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/llvm_tools/patch_sync/Cargo.lock b/llvm_tools/patch_sync/Cargo.lock new file mode 100644 index 00000000..63a9fcf8 --- /dev/null +++ b/llvm_tools/patch_sync/Cargo.lock @@ -0,0 +1,453 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "patch_sync" +version = "0.1.0" +dependencies = [ + "anyhow", + "rand", + "regex", + "serde", + "serde_json", + "sha2", + "structopt", + "time", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "serde" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a1dfb999630e338648c83e91c59a4e9fb7620f520c3194b6b89e276f2f1959" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "time" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +dependencies = [ + "libc", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/llvm_tools/patch_sync/Cargo.toml b/llvm_tools/patch_sync/Cargo.toml new file mode 100644 index 00000000..43082627 --- /dev/null +++ b/llvm_tools/patch_sync/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "patch_sync" +version = "0.1.0" +authors = ["Jordan R Abrahams-Whitehead <ajordanr@google.com>"] +edition = "2018" + +[profile.release] +panic = "abort" + +[dependencies] +anyhow = "1.0" +regex = "1.5" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +sha2 = "0.9" +structopt = "0.3" +time = "0.3" + +[dev-dependencies] +rand = "0.8" diff --git a/llvm_tools/patch_sync/src/main.rs b/llvm_tools/patch_sync/src/main.rs new file mode 100644 index 00000000..081ce01a --- /dev/null +++ b/llvm_tools/patch_sync/src/main.rs @@ -0,0 +1,173 @@ +mod patch_parsing; +mod version_control; + +use anyhow::{Context, Result}; +use std::path::PathBuf; +use structopt::StructOpt; + +fn main() -> Result<()> { + match Opt::from_args() { + Opt::Show { + cros_checkout_path, + android_checkout_path, + sync, + } => show_subcmd(cros_checkout_path, android_checkout_path, sync), + Opt::Transpose { + cros_checkout_path, + old_cros_ref, + android_checkout_path, + old_android_ref, + sync, + verbose, + dry_run, + no_commit, + } => transpose_subcmd(TransposeOpt { + cros_checkout_path, + old_cros_ref, + android_checkout_path, + old_android_ref, + sync, + verbose, + dry_run, + no_commit, + }), + } +} + +fn show_subcmd( + cros_checkout_path: PathBuf, + android_checkout_path: PathBuf, + sync: bool, +) -> Result<()> { + let ctx = version_control::RepoSetupContext { + cros_checkout: cros_checkout_path, + android_checkout: android_checkout_path, + sync_before: sync, + }; + ctx.setup()?; + let cros_patches_path = ctx.cros_patches_path(); + let android_patches_path = ctx.android_patches_path(); + let cur_cros_collection = patch_parsing::PatchCollection::parse_from_file(&cros_patches_path) + .context("could not parse cros PATCHES.json")?; + let cur_android_collection = + patch_parsing::PatchCollection::parse_from_file(&android_patches_path) + .context("could not parse android PATCHES.json")?; + let merged = cur_cros_collection.union(&cur_android_collection)?; + println!("{}", merged.serialize_patches()?); + Ok(()) +} + +#[allow(dead_code)] +struct TransposeOpt { + cros_checkout_path: PathBuf, + old_cros_ref: String, + android_checkout_path: PathBuf, + old_android_ref: String, + sync: bool, + verbose: bool, + dry_run: bool, + no_commit: bool, +} + +fn transpose_subcmd(args: TransposeOpt) -> Result<()> { + let ctx = version_control::RepoSetupContext { + cros_checkout: args.cros_checkout_path, + android_checkout: args.android_checkout_path, + sync_before: args.sync, + }; + ctx.setup()?; + let cros_patches_path = ctx.cros_patches_path(); + let android_patches_path = ctx.android_patches_path(); + + // Chromium OS Patches ---------------------------------------------------- + let mut cur_cros_collection = + patch_parsing::PatchCollection::parse_from_file(&cros_patches_path) + .context("parsing cros PATCHES.json")?; + let new_cros_patches: patch_parsing::PatchCollection = { + let cros_old_patches_json = ctx.old_cros_patch_contents(&args.old_cros_ref)?; + let old_cros_collection = patch_parsing::PatchCollection::parse_from_str( + cros_patches_path.parent().unwrap().to_path_buf(), + &cros_old_patches_json, + )?; + cur_cros_collection.subtract(&old_cros_collection)? + }; + + // Android Patches ------------------------------------------------------- + let mut cur_android_collection = + patch_parsing::PatchCollection::parse_from_file(&android_patches_path) + .context("parsing android PATCHES.json")?; + let new_android_patches: patch_parsing::PatchCollection = { + let android_old_patches_json = ctx.old_android_patch_contents(&args.old_android_ref)?; + let old_android_collection = patch_parsing::PatchCollection::parse_from_str( + android_patches_path.parent().unwrap().to_path_buf(), + &android_old_patches_json, + )?; + cur_android_collection.subtract(&old_android_collection)? + }; + + // Transpose Patches ----------------------------------------------------- + new_cros_patches.transpose_write(&mut cur_cros_collection)?; + new_android_patches.transpose_write(&mut cur_android_collection)?; + + if !args.no_commit { + return Ok(()); + } + // Commit and upload for review ------------------------------------------ + ctx.cros_repo_upload() + .context("uploading chromiumos changes")?; + ctx.android_repo_upload() + .context("uploading android changes")?; + Ok(()) +} + +#[derive(Debug, structopt::StructOpt)] +#[structopt(name = "patch_sync", about = "A pipeline for syncing the patch code")] +enum Opt { + /// Show a combined view of the PATCHES.json file, without making any changes. + #[allow(dead_code)] + Show { + #[structopt(parse(from_os_str))] + cros_checkout_path: PathBuf, + #[structopt(parse(from_os_str))] + android_checkout_path: PathBuf, + #[structopt(short, long)] + sync: bool, + }, + /// Transpose patches from two PATCHES.json files + /// to each other. + Transpose { + /// Path to the ChromiumOS source repo checkout. + #[structopt(long = "cros-checkout", parse(from_os_str))] + cros_checkout_path: PathBuf, + + /// Git ref (e.g. hash) for the ChromiumOS overlay to use as the base. + #[structopt(long = "overlay-base-ref")] + old_cros_ref: String, + + /// Path to the Android Open Source Project source repo checkout. + #[structopt(long = "aosp-checkout", parse(from_os_str))] + android_checkout_path: PathBuf, + + /// Git ref (e.g. hash) for the llvm_android repo to use as the base. + #[structopt(long = "aosp-base-ref")] + old_android_ref: String, + + /// Run repo sync before transposing. + #[structopt(short, long)] + sync: bool, + + /// Print information to stdout + #[structopt(short, long)] + verbose: bool, + + /// Do not change any files. Useful in combination with `--verbose` + /// Implies `--no-commit` and `--no-upload`. + #[structopt(long)] + dry_run: bool, + + /// Do not commit any changes made. + /// Implies `--no-upload`. + #[structopt(long)] + no_commit: bool, + }, +} diff --git a/llvm_tools/patch_sync/src/patch_parsing.rs b/llvm_tools/patch_sync/src/patch_parsing.rs new file mode 100644 index 00000000..733451ae --- /dev/null +++ b/llvm_tools/patch_sync/src/patch_parsing.rs @@ -0,0 +1,313 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{copy, File}; +use std::io::{BufRead, BufReader, Read, Write}; +use std::path::{Path, PathBuf}; + +use anyhow::{anyhow, Context, Result}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +/// JSON serde struct. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PatchDictSchema { + pub rel_patch_path: String, + pub start_version: Option<u64>, + pub end_version: Option<u64>, + pub platforms: BTreeSet<String>, + pub metadata: Option<BTreeMap<String, serde_json::Value>>, +} + +/// Struct to keep track of patches and their relative paths. +#[derive(Debug, Clone)] +pub struct PatchCollection { + pub patches: Vec<PatchDictSchema>, + pub workdir: PathBuf, +} + +impl PatchCollection { + /// Create a `PatchCollection` from a PATCHES. + pub fn parse_from_file(json_file: &Path) -> Result<Self> { + Ok(Self { + patches: serde_json::from_reader(File::open(json_file)?)?, + workdir: json_file + .parent() + .ok_or_else(|| anyhow!("failed to get json_file parent"))? + .to_path_buf(), + }) + } + + /// Create a `PatchCollection` from a string literal and a workdir. + pub fn parse_from_str(workdir: PathBuf, contents: &str) -> Result<Self> { + Ok(Self { + patches: serde_json::from_str(contents).context("parsing from str")?, + workdir, + }) + } + + #[allow(dead_code)] + /// Return true if the collection is tracking any patches. + pub fn is_empty(&self) -> bool { + self.patches.is_empty() + } + + /// Compute the set-set subtraction, returning a new `PatchCollection` which + /// keeps the minuend's wordir. + pub fn subtract(&self, subtrahend: &Self) -> Result<Self> { + let mut new_patches = Vec::new(); + // This is O(n^2) when it could be much faster, but n is always going to be less + // than 1k and speed is not important here. + for our_patch in &self.patches { + let found_in_sub = subtrahend.patches.iter().any(|sub_patch| { + let hash1 = subtrahend + .hash_from_rel_patch(sub_patch) + .expect("getting hash from subtrahend patch"); + let hash2 = self + .hash_from_rel_patch(our_patch) + .expect("getting hash from our patch"); + hash1 == hash2 + }); + if !found_in_sub { + new_patches.push(our_patch.clone()); + } + } + Ok(Self { + patches: new_patches, + workdir: self.workdir.clone(), + }) + } + + pub fn union(&self, other: &Self) -> Result<Self> { + self.union_helper( + other, + |p| self.hash_from_rel_patch(p), + |p| other.hash_from_rel_patch(p), + ) + } + + fn union_helper( + &self, + other: &Self, + our_hash_f: impl Fn(&PatchDictSchema) -> Result<String>, + their_hash_f: impl Fn(&PatchDictSchema) -> Result<String>, + ) -> Result<Self> { + // 1. For all our patches: + // a. If there exists a matching patch hash from `other`: + // i. Create a new patch with merged platform info, + // ii. add the new patch to our new collection. + // iii. Mark the other patch as "merged" + // b. Otherwise, copy our patch to the new collection + // 2. For all unmerged patches from the `other` + // a. Copy their patch into the new collection + let mut combined_patches = Vec::new(); + let mut other_merged = vec![false; other.patches.len()]; + + // 1. + for p in &self.patches { + let our_hash = our_hash_f(p)?; + let mut found = false; + // a. + for (idx, merged) in other_merged.iter_mut().enumerate() { + if !*merged { + let other_p = &other.patches[idx]; + let their_hash = their_hash_f(other_p)?; + if our_hash == their_hash { + // i. + let new_platforms = + p.platforms.union(&other_p.platforms).cloned().collect(); + // ii. + combined_patches.push(PatchDictSchema { + rel_patch_path: p.rel_patch_path.clone(), + start_version: p.start_version, + end_version: p.end_version, + platforms: new_platforms, + metadata: p.metadata.clone(), + }); + // iii. + *merged = true; + found = true; + break; + } + } + } + // b. + if !found { + combined_patches.push(p.clone()); + } + } + // 2. + // Add any remaining, other-only patches. + for (idx, merged) in other_merged.iter().enumerate() { + if !*merged { + combined_patches.push(other.patches[idx].clone()); + } + } + + Ok(Self { + workdir: self.workdir.clone(), + patches: combined_patches, + }) + } + + /// Copy all patches from this collection into another existing collection, and write that + /// to the existing collection's file. + pub fn transpose_write(&self, existing_collection: &mut Self) -> Result<()> { + for p in &self.patches { + let original_file_path = self.workdir.join(&p.rel_patch_path); + let copy_file_path = existing_collection.workdir.join(&p.rel_patch_path); + copy_create_parents(&original_file_path, ©_file_path)?; + existing_collection.patches.push(p.clone()); + } + existing_collection.write_patches_json("PATCHES.json") + } + + /// Write out the patch collection contents to a PATCHES.json file. + fn write_patches_json(&self, filename: &str) -> Result<()> { + let write_path = self.workdir.join(filename); + let mut new_patches_file = File::create(&write_path) + .with_context(|| format!("writing to {}", write_path.display()))?; + new_patches_file.write_all(self.serialize_patches()?.as_bytes())?; + Ok(()) + } + + pub fn serialize_patches(&self) -> Result<String> { + let mut serialization_buffer = Vec::<u8>::new(); + // Four spaces to indent json serialization. + let mut serializer = serde_json::Serializer::with_formatter( + &mut serialization_buffer, + serde_json::ser::PrettyFormatter::with_indent(b" "), + ); + self.patches + .serialize(&mut serializer) + .context("serializing patches to JSON")?; + // Append a newline at the end if not present. This is necessary to get + // past some pre-upload hooks. + if serialization_buffer.last() != Some(&b'\n') { + serialization_buffer.push(b'\n'); + } + Ok(std::str::from_utf8(&serialization_buffer)?.to_string()) + } + + fn hash_from_rel_patch(&self, patch: &PatchDictSchema) -> Result<String> { + hash_from_patch_path(&self.workdir.join(&patch.rel_patch_path)) + } +} + +/// Get the hash from the patch file contents. +/// +/// Not every patch file actually contains its own hash, +/// we must compute the hash ourselves when it's not found. +fn hash_from_patch(patch_contents: impl Read) -> Result<String> { + let mut reader = BufReader::new(patch_contents); + let mut buf = String::new(); + reader.read_line(&mut buf)?; + let mut first_line_iter = buf.trim().split(' ').fuse(); + let (fst_word, snd_word) = (first_line_iter.next(), first_line_iter.next()); + if let (Some("commit" | "From"), Some(hash_str)) = (fst_word, snd_word) { + // If the first line starts with either "commit" or "From", the following + // text is almost certainly a commit hash. + Ok(hash_str.to_string()) + } else { + // This is an annoying case where the patch isn't actually a commit. + // So we'll hash the entire file, and hope that's sufficient. + let mut hasher = Sha256::new(); + hasher.update(&buf); // Have to hash the first line. + reader.read_to_string(&mut buf)?; + hasher.update(buf); // Hash the rest of the file. + let sha = hasher.finalize(); + Ok(format!("{:x}", &sha)) + } +} + +fn hash_from_patch_path(patch: &Path) -> Result<String> { + let f = File::open(patch)?; + hash_from_patch(f) +} + +/// Copy a file from one path to another, and create any parent +/// directories along the way. +fn copy_create_parents(from: &Path, to: &Path) -> Result<()> { + let to_parent = to + .parent() + .with_context(|| format!("getting parent of {}", to.display()))?; + if !to_parent.exists() { + std::fs::create_dir_all(to_parent)?; + } + + copy(&from, &to) + .with_context(|| format!("copying file from {} to {}", &from.display(), &to.display()))?; + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + /// Test we can extract the hash from patch files. + #[test] + fn test_hash_from_patch() { + // Example git patch from Gerrit + let desired_hash = "004be4037e1e9c6092323c5c9268acb3ecf9176c"; + let test_file_contents = "commit 004be4037e1e9c6092323c5c9268acb3ecf9176c\n\ + Author: An Author <some_email>\n\ + Date: Thu Aug 6 12:34:16 2020 -0700"; + assert_eq!( + &hash_from_patch(test_file_contents.as_bytes()).unwrap(), + desired_hash + ); + + // Example git patch from upstream + let desired_hash = "6f85225ef3791357f9b1aa097b575b0a2b0dff48"; + let test_file_contents = "From 6f85225ef3791357f9b1aa097b575b0a2b0dff48\n\ + Mon Sep 17 00:00:00 2001\n\ + From: Another Author <another_email>\n\ + Date: Wed, 18 Aug 2021 15:03:03 -0700"; + assert_eq!( + &hash_from_patch(test_file_contents.as_bytes()).unwrap(), + desired_hash + ); + } + + #[test] + fn test_union() { + let patch1 = PatchDictSchema { + start_version: Some(0), + end_version: Some(1), + rel_patch_path: "a".into(), + metadata: None, + platforms: BTreeSet::from(["x".into()]), + }; + let patch2 = PatchDictSchema { + rel_patch_path: "b".into(), + platforms: BTreeSet::from(["x".into(), "y".into()]), + ..patch1.clone() + }; + let patch3 = PatchDictSchema { + platforms: BTreeSet::from(["z".into(), "x".into()]), + ..patch1.clone() + }; + let collection1 = PatchCollection { + workdir: PathBuf::new(), + patches: vec![patch1, patch2], + }; + let collection2 = PatchCollection { + workdir: PathBuf::new(), + patches: vec![patch3], + }; + let union = collection1 + .union_helper( + &collection2, + |p| Ok(p.rel_patch_path.to_string()), + |p| Ok(p.rel_patch_path.to_string()), + ) + .expect("could not create union"); + assert_eq!(union.patches.len(), 2); + assert_eq!( + union.patches[0].platforms.iter().collect::<Vec<&String>>(), + vec!["x", "z"] + ); + assert_eq!( + union.patches[1].platforms.iter().collect::<Vec<&String>>(), + vec!["x", "y"] + ); + } +} diff --git a/llvm_tools/patch_sync/src/version_control.rs b/llvm_tools/patch_sync/src/version_control.rs new file mode 100644 index 00000000..3dc5aae9 --- /dev/null +++ b/llvm_tools/patch_sync/src/version_control.rs @@ -0,0 +1,238 @@ +use anyhow::{anyhow, bail, ensure, Context, Result}; +use regex::Regex; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +const CHROMIUMOS_OVERLAY_REL_PATH: &str = "src/third_party/chromiumos-overlay"; +const ANDROID_LLVM_REL_PATH: &str = "toolchain/llvm_android"; + +/// Context struct to keep track of both Chromium OS and Android checkouts. +#[derive(Debug)] +pub struct RepoSetupContext { + pub cros_checkout: PathBuf, + pub android_checkout: PathBuf, + /// Run `repo sync` before doing any comparisons. + pub sync_before: bool, +} + +impl RepoSetupContext { + pub fn setup(&self) -> Result<()> { + if self.sync_before { + repo_cd_cmd(&self.cros_checkout, &["sync", CHROMIUMOS_OVERLAY_REL_PATH])?; + repo_cd_cmd(&self.android_checkout, &["sync", ANDROID_LLVM_REL_PATH])?; + } + Ok(()) + } + + pub fn cros_repo_upload(&self) -> Result<()> { + let llvm_dir = self + .cros_checkout + .join(&CHROMIUMOS_OVERLAY_REL_PATH) + .join("sys-devel/llvm"); + ensure!( + llvm_dir.is_dir(), + "CrOS LLVM dir {} is not a directory", + llvm_dir.display() + ); + Self::rev_bump_llvm(&llvm_dir)?; + Self::repo_upload( + &self.cros_checkout, + CHROMIUMOS_OVERLAY_REL_PATH, + &Self::build_commit_msg("android", "chromiumos", "BUG=None\nTEST=CQ"), + ) + } + + pub fn android_repo_upload(&self) -> Result<()> { + Self::repo_upload( + &self.android_checkout, + ANDROID_LLVM_REL_PATH, + &Self::build_commit_msg("chromiumos", "android", "Test: N/A"), + ) + } + + fn repo_upload(path: &Path, git_wd: &str, commit_msg: &str) -> Result<()> { + // TODO(ajordanr): Need to clean up if there's any failures during upload. + let git_path = &path.join(&git_wd); + ensure!( + git_path.is_dir(), + "git_path {} is not a directory", + git_path.display() + ); + repo_cd_cmd(path, &["start", "patch_sync_branch", git_wd])?; + git_cd_cmd(git_path, &["add", "."])?; + git_cd_cmd(git_path, &["commit", "-m", commit_msg])?; + repo_cd_cmd(path, &["upload", "-y", "--verify", git_wd])?; + Ok(()) + } + + pub fn android_patches_path(&self) -> PathBuf { + self.android_checkout + .join(&ANDROID_LLVM_REL_PATH) + .join("patches/PATCHES.json") + } + + pub fn cros_patches_path(&self) -> PathBuf { + self.cros_checkout + .join(&CHROMIUMOS_OVERLAY_REL_PATH) + .join("sys-devel/llvm/files/PATCHES.json") + } + + /// Increment LLVM's revision number + fn rev_bump_llvm(llvm_dir: &Path) -> Result<PathBuf> { + let ebuild = find_ebuild(llvm_dir) + .with_context(|| format!("finding ebuild in {} to rev bump", llvm_dir.display()))?; + let ebuild_dir = ebuild.parent().unwrap(); + let suffix_matcher = Regex::new(r"-r([0-9]+)\.ebuild").unwrap(); + let ebuild_name = ebuild + .file_name() + .unwrap() + .to_str() + .ok_or_else(|| anyhow!("converting ebuild filename to utf-8"))?; + let new_path = if let Some(captures) = suffix_matcher.captures(ebuild_name) { + let full_suffix = captures.get(0).unwrap().as_str(); + let cur_version = captures.get(1).unwrap().as_str().parse::<u32>().unwrap(); + let new_filename = + ebuild_name.replace(full_suffix, &format!("-r{}.ebuild", cur_version + 1_u32)); + let new_path = ebuild_dir.join(new_filename); + fs::rename(&ebuild, &new_path)?; + new_path + } else { + // File did not end in a revision. We should append -r1 to the end. + let new_filename = ebuild.file_stem().unwrap().to_string_lossy() + "-r1.ebuild"; + let new_path = ebuild_dir.join(new_filename.as_ref()); + fs::rename(&ebuild, &new_path)?; + new_path + }; + Ok(new_path) + } + + /// Return the contents of the old PATCHES.json from Chromium OS + #[allow(dead_code)] + pub fn old_cros_patch_contents(&self, hash: &str) -> Result<String> { + Self::old_file_contents( + hash, + &self.cros_checkout.join(CHROMIUMOS_OVERLAY_REL_PATH), + Path::new("sys-devel/llvm/files/PATCHES.json"), + ) + } + + /// Return the contents of the old PATCHES.json from android + #[allow(dead_code)] + pub fn old_android_patch_contents(&self, hash: &str) -> Result<String> { + Self::old_file_contents( + hash, + &self.android_checkout.join(ANDROID_LLVM_REL_PATH), + Path::new("patches/PATCHES.json"), + ) + } + + /// Return the contents of an old file in git + #[allow(dead_code)] + fn old_file_contents(hash: &str, pwd: &Path, file: &Path) -> Result<String> { + let git_ref = format!( + "{}:{}", + hash, + file.to_str() + .ok_or_else(|| anyhow!("failed to convert filepath to str"))? + ); + let output = git_cd_cmd(pwd, &["show", &git_ref])?; + if !output.status.success() { + bail!("could not get old file contents for {}", &git_ref) + } + String::from_utf8(output.stdout) + .with_context(|| format!("converting {} file contents to UTF-8", &git_ref)) + } + + /// Create the commit message + fn build_commit_msg(from: &str, to: &str, footer: &str) -> String { + format!( + "[patch_sync] Synchronize patches from {}\n\n\ + Copies new PATCHES.json changes from {} to {}\n\n{}", + from, from, to, footer + ) + } +} + +/// Return the path of an ebuild located within the given directory. +fn find_ebuild(dir: &Path) -> Result<PathBuf> { + // TODO(ajordanr): Maybe use OnceCell for this regex? + let ebuild_matcher = Regex::new(r"(-r[0-9]+)?\.ebuild").unwrap(); + for entry in fs::read_dir(dir)? { + let path = entry?.path(); + if let Some(name) = path.file_name() { + if ebuild_matcher.is_match( + name.to_str() + .ok_or_else(|| anyhow!("converting filepath to UTF-8"))?, + ) { + return Ok(path); + } + } + } + bail!("could not find ebuild") +} + +/// Run a given git command from inside a specified git dir. +pub fn git_cd_cmd<I, S>(pwd: &Path, args: I) -> Result<Output> +where + I: IntoIterator<Item = S>, + S: AsRef<OsStr>, +{ + let output = Command::new("git").current_dir(&pwd).args(args).output()?; + if !output.status.success() { + bail!("git command failed") + } + Ok(output) +} + +pub fn repo_cd_cmd<I, S>(pwd: &Path, args: I) -> Result<()> +where + I: IntoIterator<Item = S>, + S: AsRef<OsStr>, +{ + let status = Command::new("repo").current_dir(&pwd).args(args).status()?; + if !status.success() { + bail!("repo command failed") + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use rand::prelude::Rng; + use std::env; + use std::fs::File; + + #[test] + fn test_revbump_ebuild() { + // Random number to append at the end of the test folder to prevent conflicts. + let rng: u32 = rand::thread_rng().gen(); + let llvm_dir = env::temp_dir().join(format!("patch_sync_test_{}", rng)); + fs::create_dir(&llvm_dir).expect("creating llvm dir in temp directory"); + + { + // With revision + let ebuild_name = "llvm-13.0_pre433403_p20211019-r10.ebuild"; + let ebuild_path = llvm_dir.join(ebuild_name); + File::create(&ebuild_path).expect("creating test ebuild file"); + let new_ebuild_path = + RepoSetupContext::rev_bump_llvm(&llvm_dir).expect("rev bumping the ebuild"); + assert!(new_ebuild_path.ends_with("llvm-13.0_pre433403_p20211019-r11.ebuild")); + fs::remove_file(new_ebuild_path).expect("removing renamed ebuild file"); + } + { + // Without revision + let ebuild_name = "llvm-13.0_pre433403_p20211019.ebuild"; + let ebuild_path = llvm_dir.join(ebuild_name); + File::create(&ebuild_path).expect("creating test ebuild file"); + let new_ebuild_path = + RepoSetupContext::rev_bump_llvm(&llvm_dir).expect("rev bumping the ebuild"); + assert!(new_ebuild_path.ends_with("llvm-13.0_pre433403_p20211019-r1.ebuild")); + fs::remove_file(new_ebuild_path).expect("removing renamed ebuild file"); + } + + fs::remove_dir(&llvm_dir).expect("removing temp test dir"); + } +} diff --git a/llvm_tools/update_chromeos_llvm_hash.py b/llvm_tools/update_chromeos_llvm_hash.py index ab83321a..4e9b9104 100755 --- a/llvm_tools/update_chromeos_llvm_hash.py +++ b/llvm_tools/update_chromeos_llvm_hash.py @@ -25,6 +25,15 @@ import get_llvm_hash import git import llvm_patch_management +DEFAULT_PACKAGES = [ + 'dev-util/lldb-server', + 'sys-devel/llvm', + 'sys-libs/compiler-rt', + 'sys-libs/libcxx', + 'sys-libs/libcxxabi', + 'sys-libs/llvm-libunwind', +] + # Specify which LLVM hash to update class LLVMVariant(enum.Enum): @@ -69,26 +78,23 @@ def GetCommandLineArgs(): description="Updates the build's hash for llvm-next.") # Add argument for a specific chroot path. - parser.add_argument( - '--chroot_path', - default=defaultCrosRoot(), - help='the path to the chroot (default: %(default)s)') + parser.add_argument('--chroot_path', + default=defaultCrosRoot(), + help='the path to the chroot (default: %(default)s)') # Add argument for specific builds to uprev and update their llvm-next hash. - parser.add_argument( - '--update_packages', - default=['sys-devel/llvm'], - required=False, - nargs='+', - help='the ebuilds to update their hash for llvm-next ' - '(default: %(default)s)') + parser.add_argument('--update_packages', + default=DEFAULT_PACKAGES, + required=False, + nargs='+', + help='the ebuilds to update their hash for llvm-next ' + '(default: %(default)s)') # Add argument for whether to display command contents to `stdout`. - parser.add_argument( - '--verbose', - action='store_true', - help='display contents of a command to the terminal ' - '(default: %(default)s)') + parser.add_argument('--verbose', + action='store_true', + help='display contents of a command to the terminal ' + '(default: %(default)s)') # Add argument for the LLVM hash to update parser.add_argument( @@ -314,9 +320,11 @@ def UprevEbuildToVersion(symlink, svn_version, git_hash): count=1) # any other package else: - new_ebuild, is_changed = re.subn( - r'(\d+)\.(\d+)_pre([0-9]+)', - '%s.\\2_pre%s' % (llvm_major_version, svn_version), ebuild, count=1) + new_ebuild, is_changed = re.subn(r'(\d+)\.(\d+)_pre([0-9]+)', + '%s.\\2_pre%s' % + (llvm_major_version, svn_version), + ebuild, + count=1) if not is_changed: # failed to increment the revision number raise ValueError('Failed to uprev the ebuild.') @@ -397,7 +405,8 @@ def StagePatchMetadataFileForCommit(patch_metadata_file_path): # Cmd to stage the patch metadata file for commit. subprocess.check_output([ 'git', '-C', - os.path.dirname(patch_metadata_file_path), 'add', patch_metadata_file_path + os.path.dirname(patch_metadata_file_path), 'add', + patch_metadata_file_path ]) @@ -419,9 +428,9 @@ def StagePackagesPatchResultsForCommit(package_info_dict, commit_messages): # changed, if so, add which patches have changed to the commit # message. for package_name, patch_info_dict in package_info_dict.items(): - if (patch_info_dict['disabled_patches'] or - patch_info_dict['removed_patches'] or - patch_info_dict['modified_metadata']): + if (patch_info_dict['disabled_patches'] + or patch_info_dict['removed_patches'] + or patch_info_dict['modified_metadata']): cur_package_header = '\nFor the package %s:' % package_name commit_messages.append(cur_package_header) @@ -600,16 +609,16 @@ def main(): git_hash, svn_version = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption( git_hash_source) - change_list = UpdatePackages( - args_output.update_packages, - llvm_variant, - git_hash, - svn_version, - args_output.chroot_path, - args_output.patch_metadata_file, - failure_modes.FailureModes(args_output.failure_mode), - git_hash_source, - extra_commit_msg=None) + change_list = UpdatePackages(args_output.update_packages, + llvm_variant, + git_hash, + svn_version, + args_output.chroot_path, + args_output.patch_metadata_file, + failure_modes.FailureModes( + args_output.failure_mode), + git_hash_source, + extra_commit_msg=None) print('Successfully updated packages to %s (%d)' % (git_hash, svn_version)) print('Gerrit URL: %s' % change_list.url) diff --git a/llvm_tools/update_packages_and_run_tests.py b/llvm_tools/update_packages_and_run_tests.py index dd01253e..2e4a9058 100755 --- a/llvm_tools/update_packages_and_run_tests.py +++ b/llvm_tools/update_packages_and_run_tests.py @@ -51,10 +51,9 @@ def GetCommandLineArgs(): 'of updating the packages') # Add argument for a specific chroot path. - parser.add_argument( - '--chroot_path', - default=cros_root, - help='the path to the chroot (default: %(default)s)') + parser.add_argument('--chroot_path', + default=cros_root, + help='the path to the chroot (default: %(default)s)') # Add argument to choose between llvm and llvm-next. parser.add_argument( @@ -71,65 +70,58 @@ def GetCommandLineArgs(): 'arguments.') # Add argument for the LLVM version to use. - parser.add_argument( - '--llvm_version', - type=get_llvm_hash.IsSvnOption, - required=True, - help='which git hash of LLVM to find ' - '{google3, ToT, <svn_version>} ' - '(default: finds the git hash of the google3 LLVM ' - 'version)') + parser.add_argument('--llvm_version', + type=get_llvm_hash.IsSvnOption, + required=True, + help='which git hash of LLVM to find ' + '{google3, ToT, <svn_version>} ' + '(default: finds the git hash of the google3 LLVM ' + 'version)') # Add argument to add reviewers for the created CL. - parser.add_argument( - '--reviewers', - nargs='+', - default=[], - help='The reviewers for the package update changelist') + parser.add_argument('--reviewers', + nargs='+', + default=[], + help='The reviewers for the package update changelist') # Add argument for whether to display command contents to `stdout`. - parser.add_argument( - '--verbose', - action='store_true', - help='display contents of a command to the terminal ' - '(default: %(default)s)') + parser.add_argument('--verbose', + action='store_true', + help='display contents of a command to the terminal ' + '(default: %(default)s)') subparsers = parser.add_subparsers(dest='subparser_name') subparser_names = [] # Testing with the tryjobs. tryjob_subparser = subparsers.add_parser('tryjobs') subparser_names.append('tryjobs') - tryjob_subparser.add_argument( - '--builders', - required=True, - nargs='+', - default=[], - help='builders to use for the tryjob testing') + tryjob_subparser.add_argument('--builders', + required=True, + nargs='+', + default=[], + help='builders to use for the tryjob testing') # Add argument for custom options for the tryjob. - tryjob_subparser.add_argument( - '--options', - required=False, - nargs='+', - default=[], - help='options to use for the tryjob testing') + tryjob_subparser.add_argument('--options', + required=False, + nargs='+', + default=[], + help='options to use for the tryjob testing') # Testing with the recipe builders recipe_subparser = subparsers.add_parser('recipe') subparser_names.append('recipe') - recipe_subparser.add_argument( - '--options', - required=False, - nargs='+', - default=[], - help='options passed to the recipe builders') - - recipe_subparser.add_argument( - '--builders', - required=True, - nargs='+', - default=[], - help='recipe builders to launch') + recipe_subparser.add_argument('--options', + required=False, + nargs='+', + default=[], + help='options passed to the recipe builders') + + recipe_subparser.add_argument('--builders', + required=True, + nargs='+', + default=[], + help='recipe builders to launch') # Testing with CQ. cq_subparser = subparsers.add_parser('cq') @@ -360,7 +352,8 @@ def GetCQDependString(dependent_cls): return None # Cq-Depend must start a new paragraph prefixed with "Cq-Depend". - return '\nCq-Depend: ' + ', '.join(('chromium:%s' % i) for i in dependent_cls) + return '\nCq-Depend: ' + ', '.join( + ('chromium:%s' % i) for i in dependent_cls) def GetCQIncludeTrybotsString(trybot): @@ -402,11 +395,6 @@ def main(): args_output = GetCommandLineArgs() - update_packages = [ - 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', - 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' - ] - patch_metadata_file = 'PATCHES.json' svn_option = args_output.llvm_version @@ -420,8 +408,8 @@ def main(): # If --last_tested is specified, check if the current run has the same # arguments last time --last_tested is used. if args_output.last_tested: - chroot_file_paths = chroot.GetChrootEbuildPaths(args_output.chroot_path, - update_packages) + chroot_file_paths = chroot.GetChrootEbuildPaths( + args_output.chroot_path, update_chromeos_llvm_hash.DEFAULT_PACKAGES) arg_dict = { 'svn_version': svn_version, 'ebuilds': chroot_file_paths, @@ -449,7 +437,7 @@ def main(): extra_commit_msg += cq_trybot_msg change_list = update_chromeos_llvm_hash.UpdatePackages( - update_packages, + update_chromeos_llvm_hash.DEFAULT_PACKAGES, llvm_variant, git_hash, svn_version, |