123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #!/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 -> list of lines after patch path and before next patch path
- path_comments = dict()
- # patch path -> inline comment for patch
- path_inline_comments = dict()
- previous_path = None
- for partial_path in orig_series:
- if not partial_path or partial_path.startswith('#'):
- if partial_path not in path_comments:
- path_comments[previous_path] = list()
- path_comments[previous_path].append(partial_path)
- else:
- path_parts = partial_path.split(' #', maxsplit=1)
- previous_path = path_parts[0]
- if len(path_parts) == 2:
- path_inline_comments[path_parts[0]] = path_parts[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):
- current_path = new_series[series_index]
- if current_path in path_inline_comments:
- new_series[series_index] = current_path + ' #' + path_inline_comments[current_path]
- if current_path in path_comments:
- new_series.insert(series_index + 1, '\n'.join(path_comments[current_path]))
- 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__':
- sys.exit(main())
|