Files
helium/devutils/update_platform_patches.py
jj aebf0a4387 devutils/platform_patches: update shared patches instead of deleting
Instead of deleting copies of generic patches when unmerging them
from platform patches, move them back into the shared repository.

This makes it much more pleasant and ergonomic to work on patches
as a merged series, and allows to e.g. refresh existing patches
without having to move them back to the original folder manually.
2025-11-21 17:45:08 +00:00

189 lines
6.8 KiB
Python
Executable File

#!/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 _rename_files_with_dirs(root_dir, source_dir, sorted_file_iter):
'''
Moves a list of sorted files back to their original location,
removing empty directories along the way
'''
past_parent = None
for partial_path in sorted_file_iter:
complete_path = Path(root_dir, partial_path)
complete_source_path = Path(source_dir, partial_path)
try:
complete_path.rename(complete_source_path)
except FileNotFoundError:
get_logger().warning('Could not move 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, prepend_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()))
# Move prepended files back to original location, preserving changes
_rename_files_with_dirs(platform_patches_dir, prepend_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 = {}
# patch path -> inline comment for patch
path_inline_comments = {}
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] = []
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
prepend_patches_dir = repo_dir / 'patches'
if args.command == 'merge':
success = merge_platform_patches(args.platform_patches, prepend_patches_dir)
elif args.command == 'unmerge':
success = unmerge_platform_patches(args.platform_patches, prepend_patches_dir)
else:
raise NotImplementedError(args.command)
if success:
return 0
return 1
if __name__ == '__main__':
sys.exit(main())