From 9e8eec4dd3631242e647ab0500a5a7254638a54f Mon Sep 17 00:00:00 2001 From: Eloston Date: Fri, 22 Mar 2019 03:15:27 +0000 Subject: [PATCH] devutils: Add update_platform_patches.py --- devutils/update_platform_patches.py | 178 ++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100755 devutils/update_platform_patches.py diff --git a/devutils/update_platform_patches.py b/devutils/update_platform_patches.py new file mode 100755 index 00000000..5a2d6209 --- /dev/null +++ b/devutils/update_platform_patches.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 The ungoogled-chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +""" +Utility to ease the updating of platform patches against ungoogled-chromium's patches +""" + +import argparse +import os +import shutil +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'utils')) +from _common import ENCODING, get_logger +from patches import merge_patches +sys.path.pop(0) + +_SERIES = 'series' +_SERIES_ORIG = 'series.orig' +_SERIES_PREPEND = 'series.prepend' +_SERIES_MERGED = 'series.merged' + + +def merge_platform_patches(platform_patches_dir, prepend_patches_dir): + ''' + Prepends prepend_patches_dir into platform_patches_dir + + Returns True if successful, False otherwise + ''' + if not (platform_patches_dir / _SERIES).exists(): + get_logger().error('Unable to find platform series file: %s', + platform_patches_dir / _SERIES) + return False + + # Make series.orig file + shutil.copyfile(str(platform_patches_dir / _SERIES), str(platform_patches_dir / _SERIES_ORIG)) + + # Make series.prepend + shutil.copyfile(str(prepend_patches_dir / _SERIES), str(platform_patches_dir / _SERIES_PREPEND)) + + # Merge patches + merge_patches([prepend_patches_dir], platform_patches_dir, prepend=True) + (platform_patches_dir / _SERIES).replace(platform_patches_dir / _SERIES_MERGED) + + return True + + +def _dir_empty(path): + ''' + Returns True if the directory exists and is empty; False otherwise + ''' + try: + next(os.scandir(str(path))) + except StopIteration: + return True + except FileNotFoundError: + pass + return False + + +def _remove_files_with_dirs(root_dir, sorted_file_iter): + ''' + Deletes a list of sorted files relative to root_dir, removing empty directories along the way + ''' + past_parent = None + for partial_path in sorted_file_iter: + complete_path = Path(root_dir, partial_path) + try: + complete_path.unlink() + except FileNotFoundError: + get_logger().warning('Could not remove prepended patch: %s', complete_path) + if past_parent != complete_path.parent: + while past_parent and _dir_empty(past_parent): + past_parent.rmdir() + past_parent = past_parent.parent + past_parent = complete_path.parent + # Handle last path's directory + while _dir_empty(complete_path.parent): + complete_path.parent.rmdir() + complete_path = complete_path.parent + + +def unmerge_platform_patches(platform_patches_dir): + ''' + Undo merge_platform_patches(), adding any new patches from series.merged as necessary + + Returns True if successful, False otherwise + ''' + if not (platform_patches_dir / _SERIES_PREPEND).exists(): + get_logger().error('Unable to find series.prepend at: %s', + platform_patches_dir / _SERIES_PREPEND) + return False + prepend_series = set( + filter(len, + (platform_patches_dir / _SERIES_PREPEND).read_text(encoding=ENCODING).splitlines())) + + # Remove prepended files with directories + _remove_files_with_dirs(platform_patches_dir, sorted(prepend_series)) + + # Determine positions of blank spaces in series.orig + if not (platform_patches_dir / _SERIES_ORIG).exists(): + get_logger().error('Unable to find series.orig at: %s', platform_patches_dir / _SERIES_ORIG) + return False + orig_series = (platform_patches_dir / _SERIES_ORIG).read_text(encoding=ENCODING).splitlines() + # patch path -> number of following blank lines + path_spaces = dict() + previous_path = None + for partial_path in orig_series: + if partial_path: + previous_path = partial_path + elif previous_path in path_spaces: + path_spaces[previous_path] += 1 + else: + path_spaces[previous_path] = 1 + + # Apply changes on series.merged into a modified version of series.orig + if not (platform_patches_dir / _SERIES_MERGED).exists(): + get_logger().error('Unable to find series.merged at: %s', + platform_patches_dir / _SERIES_MERGED) + return False + new_series = filter( + len, (platform_patches_dir / _SERIES_MERGED).read_text(encoding=ENCODING).splitlines()) + new_series = filter((lambda x: x not in prepend_series), new_series) + new_series = list(new_series) + series_index = 0 + while series_index < len(new_series): + if new_series[series_index] in path_spaces: + new_series.insert(series_index + 1, '\n' * (path_spaces[new_series[series_index]] - 1)) + series_index += 1 + series_index += 1 + + # Write series file + with (platform_patches_dir / _SERIES).open('w', encoding=ENCODING) as series_file: + series_file.write('\n'.join(new_series)) + series_file.write('\n') + + # All other operations are successful; remove merging intermediates + (platform_patches_dir / _SERIES_MERGED).unlink() + (platform_patches_dir / _SERIES_ORIG).unlink() + (platform_patches_dir / _SERIES_PREPEND).unlink() + + return True + + +def main(): + """CLI Entrypoint""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + 'command', + choices=('merge', 'unmerge'), + help='Merge or unmerge ungoogled-chromium patches with platform patches') + parser.add_argument( + 'platform_patches', + type=Path, + help='The path to the platform patches in GNU Quilt format to merge into') + args = parser.parse_args() + + repo_dir = Path(__file__).resolve().parent.parent + + success = False + if args.command == 'merge': + success = merge_platform_patches(args.platform_patches, repo_dir / 'patches') + elif args.command == 'unmerge': + success = unmerge_platform_patches(args.platform_patches) + else: + raise NotImplementedError(args.command) + + if success: + return 0 + return 1 + + +if __name__ == '__main__': + exit(main())