blob: d37a29e185bf362e5af9b7cd4b80fc2a3683972c [file] [log] [blame]
#!/usr/bin/python3
# Copyright 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import datetime
import os
import os.path
import re
import subprocess
import sys
"""
Helper script for importing a new snapshot of the official Wayland sources.
Usage: ./import_official_snapshot.py 1.18.0
"""
def git(cmd, check=True):
return subprocess.run(['git'] + cmd,
capture_output=True,
check=check,
text=True)
def git_get_hash(commit):
return git(['show-ref', '--head', '--hash', commit]).stdout.strip()
def git_is_ref(ref):
return git(
['show-ref', '--head', '--hash', '--verify', f'refs/heads/{ref}'],
check=False).returncode == 0
def get_git_files(version):
return set(
git(['ls-tree', '-r', '--name-only',
f'{version}^{{tree}}']).stdout.split())
def assert_no_uncommitted_changes():
r = git(['diff-files', '--quiet', '--ignore-submodules'], check=False)
if r.returncode:
sys.exit('Error: Your tree is dirty')
r = git(
['diff-index', '--quiet', '--ignore-submodules', '--cached', 'HEAD'],
check=False)
if r.returncode:
sys.exit('Error: You have staged changes')
def metadata_read_current_version():
with open('METADATA', 'rt') as metadata_file:
metadata = metadata_file.read()
match = re.search(r'version: "([^"]*)"', metadata)
if not match:
sys.exit('Error: Unable to determine current version from METADATA')
return match.group(1)
def metadata_read_git_url():
with open('METADATA', 'rt') as metadata_file:
metadata = metadata_file.read()
match = re.search(r'url\s*{\s*type:\s*GIT\s*value:\s*"([^"]*)"\s*}',
metadata)
if not match:
sys.exit('Error: Unable to determine GIT url from METADATA')
return match.group(1)
def setup_and_update_official_source_remote(official_source_git_url):
r = git(['remote', 'get-url', 'official-source'], check=False)
if r.returncode or r.stdout != official_source_git_url:
# Not configured as expected.
print(
f' Configuring official-source remote {official_source_git_url}')
git(['remote', 'remove', 'official-source'], check=False)
git(['remote', 'add', 'official-source', official_source_git_url])
print(' Syncing official-source')
git(['remote', 'update', 'official-source'])
def get_local_files():
result = []
for root, dirs, files in os.walk('.'):
# Don't include ./.git
if root == '.' and '.git' in dirs:
dirs.remove('.git')
for name in files:
result.append(os.path.join(root, name)[2:])
return result
def determine_files_to_preserve(current_version):
local_files = set(get_local_files())
current_official_files = get_git_files(current_version)
android_added_files = local_files - current_official_files
return android_added_files
def update_metadata_version_and_import_date(version):
now = datetime.datetime.now()
with open('METADATA', 'rt') as metadata_file:
metadata = metadata_file.read()
metadata = re.sub(r'version: "[^"]*"', f'version: "{version}"', metadata)
metadata = re.sub(
r'last_upgrade_date {[^}]*}',
(f'last_upgrade_date {{ year: {now.year} month: {now.month} '
f'day: {now.day} }}'), metadata)
with open('METADATA', 'wt') as metadata_file:
metadata_file.write(metadata)
def configure_wayland_version_header(version):
with open('./src/wayland-version.h.in', 'rt') as template_file:
content = template_file.read()
(major, minor, micro) = version.split('.')
content = re.sub(r'@WAYLAND_VERSION_MAJOR@', major, content)
content = re.sub(r'@WAYLAND_VERSION_MINOR@', minor, content)
content = re.sub(r'@WAYLAND_VERSION_MICRO@', micro, content)
content = re.sub(r'@WAYLAND_VERSION@', version, content)
with open('./src/wayland-version.h', 'wt') as version_header:
version_header.write(content)
# wayland-version.h is in .gitignore, so we explicitly have to force-add it.
git(['add', '-f', './src/wayland-version.h'])
def import_sources(version, preserve_files, update_metdata=True):
start_hash = git_get_hash('HEAD')
# Use `git-read-tree` to start with a pure copy of the imported version
git(['read-tree', '-m', '-u', f'{version}^{{tree}}'])
git(['commit', '-m', f'To squash: Clean import of {version}'])
print(' Adding Android metadata')
# Restore the needed Android files
git(['restore', '--staged', '--worktree', '--source', start_hash] +
list(sorted(preserve_files)))
if update_metdata:
update_metadata_version_and_import_date(version)
configure_wayland_version_header(version)
git(['commit', '-a', '-m', f'To squash: Update versions {version}'])
def apply_and_reexport_patches(version, patches, use_cherry_pick=False):
if not patches:
return
print(f' Applying {len(patches)} Android patches')
try:
if use_cherry_pick:
git(['cherry-pick'] + patches)
else:
git(['am'] + patches)
except subprocess.CalledProcessError as e:
if 'patch failed' not in e.stderr:
raise
# Print out the captured error mess
sys.stderr.write(f'''
Failure applying patches to Wayland {version} via:
{e.cmd}
Once the patches have been resolved, please re-export the patches with:
git rm patches/*.diff
git format-patch HEAD~{len(patches)}..HEAD --no-stat --no-signature \\
--numbered --zero-commit --suffix=.diff --output-directory patches
... and also add them to the final squashed commit.
'''.strip())
sys.stdout.write(e.stdout)
sys.exit(e.stderr)
patch_hashes = list(
reversed(
git(['log', f'-{len(patches)}',
'--pretty=format:%H']).stdout.split()))
# Clean out the existing patches
git(['rm', 'patches/*.diff'])
# Re-export the patches, omitting information that might change
git([
'format-patch', f'HEAD~{len(patches)}..HEAD', '--no-stat',
'--no-signature', '--numbered', '--zero-commit', '--suffix=.diff',
'--output-directory', 'patches'
])
# Add back all the exported patches
git(['add', 'patches/*.diff'])
# Create a commit for the exported patches if there are any differences.
r = git(['diff-files', '--quiet', '--ignore-submodules'], check=False)
if r.returncode:
git(['commit', '-a', '-m', f'To squash: Update patches for {version}'])
return patch_hashes
def main():
parser = argparse.ArgumentParser(description=(
"Helper script for importing a snapshot of the Wayland sources."))
parser.add_argument('version',
nargs='?',
default=None,
help='The official version to import')
parser.add_argument(
'--no-validate-existing',
dest='validate_existing',
default=True,
action='store_false',
help='Whether to validate the current tree against upstream + patches')
parser.add_argument('--no-squash',
dest='squash',
default=True,
action='store_false',
help='Whether to squash the import to a single commit')
args = parser.parse_args()
print(
f'Preparing to importing Wayland core sources version {args.version}')
assert_no_uncommitted_changes()
official_source_git_url = metadata_read_git_url()
current_version = metadata_read_current_version()
setup_and_update_official_source_remote(official_source_git_url)
# Get the list of Android added files to preserve
preserve_files = determine_files_to_preserve(current_version)
# Filter the list to get all patches that we will need to apply
patch_files = sorted(path for path in preserve_files
if path.startswith('patches/'))
# Detect any add/add conflicts before we begin
new_files = get_git_files(args.version or current_version)
add_add_conflicts = preserve_files.intersection(new_files)
if add_add_conflicts:
sys.exit(f'''
Error: The new version of Wayland adds files that are also added for Android:
{add_add_conflicts}
'''.strip())
import_branch_name = f'import_{args.version}' if args.version else None
if import_branch_name and git_is_ref(import_branch_name):
sys.exit(f'''
Error: Branch name {import_branch_name} already exists. Please delete or rename.
'''.strip())
initial_commit_hash = git_get_hash('HEAD')
# Begin a branch for the version import, if a new version is being imported
if import_branch_name:
git(['checkout', '-b', import_branch_name])
git([
'commit', '--allow-empty', '-m',
f'Update to Wayland {args.version}'
])
patch_hashes = None
if args.validate_existing:
print(f'Importing {current_version} to validate all current fixups')
import_sources(current_version, preserve_files, update_metdata=False)
patch_hashes = apply_and_reexport_patches(current_version, patch_files)
r = git(
['diff', '--quiet', '--ignore-submodules', initial_commit_hash],
check=False)
if r.returncode:
sys.exit(f'''
Failed to recreate the pre-import tree by importing the prior Wayland version
{current_version} and applying the Android fixups.
This is likely due to changes having been made to the Wayland sources without
a corresponding patch file in patches/.
To see the differences detected, run:
git diff {initial_commit_hash}
'''.strip())
if args.version:
print(f'Importing {args.version}')
import_sources(args.version, preserve_files)
apply_and_reexport_patches(
args.version,
(patch_hashes if patch_hashes is not None else patch_files),
use_cherry_pick=(patch_hashes is not None))
if args.squash:
print('Squashing to one commit')
git(['reset', '--soft', initial_commit_hash])
git([
'commit', '--allow-empty', '-m', f'''
Update to Wayland {args.version}
Automatic import using "./import_official_snapshot.py {args.version}"
'''.strip()
])
if __name__ == '__main__':
main()