1*84e872a0SLloyd Pique#!/usr/bin/python3 2*84e872a0SLloyd Pique# Copyright 2021 The Android Open Source Project 3*84e872a0SLloyd Pique# 4*84e872a0SLloyd Pique# Licensed under the Apache License, Version 2.0 (the "License"); 5*84e872a0SLloyd Pique# you may not use this file except in compliance with the License. 6*84e872a0SLloyd Pique# You may obtain a copy of the License at 7*84e872a0SLloyd Pique# 8*84e872a0SLloyd Pique# http://www.apache.org/licenses/LICENSE-2.0 9*84e872a0SLloyd Pique# 10*84e872a0SLloyd Pique# Unless required by applicable law or agreed to in writing, software 11*84e872a0SLloyd Pique# distributed under the License is distributed on an "AS IS" BASIS, 12*84e872a0SLloyd Pique# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*84e872a0SLloyd Pique# See the License for the specific language governing permissions and 14*84e872a0SLloyd Pique# limitations under the License. 15*84e872a0SLloyd Pique 16*84e872a0SLloyd Piqueimport argparse 17*84e872a0SLloyd Piqueimport datetime 18*84e872a0SLloyd Piqueimport os 19*84e872a0SLloyd Piqueimport os.path 20*84e872a0SLloyd Piqueimport re 21*84e872a0SLloyd Piqueimport subprocess 22*84e872a0SLloyd Piqueimport sys 23*84e872a0SLloyd Pique""" 24*84e872a0SLloyd PiqueHelper script for importing a new snapshot of the official Wayland sources. 25*84e872a0SLloyd Pique 26*84e872a0SLloyd PiqueUsage: ./import_official_snapshot.py 1.18.0 27*84e872a0SLloyd Pique""" 28*84e872a0SLloyd Pique 29*84e872a0SLloyd Pique 30*84e872a0SLloyd Piquedef git(cmd, check=True): 31*84e872a0SLloyd Pique return subprocess.run(['git'] + cmd, 32*84e872a0SLloyd Pique capture_output=True, 33*84e872a0SLloyd Pique check=check, 34*84e872a0SLloyd Pique text=True) 35*84e872a0SLloyd Pique 36*84e872a0SLloyd Pique 37*84e872a0SLloyd Piquedef git_get_hash(commit): 38*84e872a0SLloyd Pique return git(['show-ref', '--head', '--hash', commit]).stdout.strip() 39*84e872a0SLloyd Pique 40*84e872a0SLloyd Pique 41*84e872a0SLloyd Piquedef git_is_ref(ref): 42*84e872a0SLloyd Pique return git( 43*84e872a0SLloyd Pique ['show-ref', '--head', '--hash', '--verify', f'refs/heads/{ref}'], 44*84e872a0SLloyd Pique check=False).returncode == 0 45*84e872a0SLloyd Pique 46*84e872a0SLloyd Pique 47*84e872a0SLloyd Piquedef get_git_files(version): 48*84e872a0SLloyd Pique return set( 49*84e872a0SLloyd Pique git(['ls-tree', '-r', '--name-only', 50*84e872a0SLloyd Pique f'{version}^{{tree}}']).stdout.split()) 51*84e872a0SLloyd Pique 52*84e872a0SLloyd Pique 53*84e872a0SLloyd Piquedef assert_no_uncommitted_changes(): 54*84e872a0SLloyd Pique r = git(['diff-files', '--quiet', '--ignore-submodules'], check=False) 55*84e872a0SLloyd Pique if r.returncode: 56*84e872a0SLloyd Pique sys.exit('Error: Your tree is dirty') 57*84e872a0SLloyd Pique 58*84e872a0SLloyd Pique r = git( 59*84e872a0SLloyd Pique ['diff-index', '--quiet', '--ignore-submodules', '--cached', 'HEAD'], 60*84e872a0SLloyd Pique check=False) 61*84e872a0SLloyd Pique if r.returncode: 62*84e872a0SLloyd Pique sys.exit('Error: You have staged changes') 63*84e872a0SLloyd Pique 64*84e872a0SLloyd Pique 65*84e872a0SLloyd Piquedef metadata_read_current_version(): 66*84e872a0SLloyd Pique with open('METADATA', 'rt') as metadata_file: 67*84e872a0SLloyd Pique metadata = metadata_file.read() 68*84e872a0SLloyd Pique 69*84e872a0SLloyd Pique match = re.search(r'version: "([^"]*)"', metadata) 70*84e872a0SLloyd Pique if not match: 71*84e872a0SLloyd Pique sys.exit('Error: Unable to determine current version from METADATA') 72*84e872a0SLloyd Pique return match.group(1) 73*84e872a0SLloyd Pique 74*84e872a0SLloyd Pique 75*84e872a0SLloyd Piquedef metadata_read_git_url(): 76*84e872a0SLloyd Pique with open('METADATA', 'rt') as metadata_file: 77*84e872a0SLloyd Pique metadata = metadata_file.read() 78*84e872a0SLloyd Pique 79*84e872a0SLloyd Pique match = re.search(r'url\s*{\s*type:\s*GIT\s*value:\s*"([^"]*)"\s*}', 80*84e872a0SLloyd Pique metadata) 81*84e872a0SLloyd Pique if not match: 82*84e872a0SLloyd Pique sys.exit('Error: Unable to determine GIT url from METADATA') 83*84e872a0SLloyd Pique return match.group(1) 84*84e872a0SLloyd Pique 85*84e872a0SLloyd Pique 86*84e872a0SLloyd Piquedef setup_and_update_official_source_remote(official_source_git_url): 87*84e872a0SLloyd Pique r = git(['remote', 'get-url', 'official-source'], check=False) 88*84e872a0SLloyd Pique if r.returncode or r.stdout != official_source_git_url: 89*84e872a0SLloyd Pique # Not configured as expected. 90*84e872a0SLloyd Pique print( 91*84e872a0SLloyd Pique f' Configuring official-source remote {official_source_git_url}') 92*84e872a0SLloyd Pique git(['remote', 'remove', 'official-source'], check=False) 93*84e872a0SLloyd Pique git(['remote', 'add', 'official-source', official_source_git_url]) 94*84e872a0SLloyd Pique 95*84e872a0SLloyd Pique print(' Syncing official-source') 96*84e872a0SLloyd Pique git(['remote', 'update', 'official-source']) 97*84e872a0SLloyd Pique 98*84e872a0SLloyd Pique 99*84e872a0SLloyd Piquedef get_local_files(): 100*84e872a0SLloyd Pique result = [] 101*84e872a0SLloyd Pique for root, dirs, files in os.walk('.'): 102*84e872a0SLloyd Pique # Don't include ./.git 103*84e872a0SLloyd Pique if root == '.' and '.git' in dirs: 104*84e872a0SLloyd Pique dirs.remove('.git') 105*84e872a0SLloyd Pique for name in files: 106*84e872a0SLloyd Pique result.append(os.path.join(root, name)[2:]) 107*84e872a0SLloyd Pique return result 108*84e872a0SLloyd Pique 109*84e872a0SLloyd Pique 110*84e872a0SLloyd Piquedef determine_files_to_preserve(current_version): 111*84e872a0SLloyd Pique local_files = set(get_local_files()) 112*84e872a0SLloyd Pique 113*84e872a0SLloyd Pique current_official_files = get_git_files(current_version) 114*84e872a0SLloyd Pique 115*84e872a0SLloyd Pique android_added_files = local_files - current_official_files 116*84e872a0SLloyd Pique 117*84e872a0SLloyd Pique return android_added_files 118*84e872a0SLloyd Pique 119*84e872a0SLloyd Pique 120*84e872a0SLloyd Piquedef update_metadata_version_and_import_date(version): 121*84e872a0SLloyd Pique now = datetime.datetime.now() 122*84e872a0SLloyd Pique 123*84e872a0SLloyd Pique with open('METADATA', 'rt') as metadata_file: 124*84e872a0SLloyd Pique metadata = metadata_file.read() 125*84e872a0SLloyd Pique 126*84e872a0SLloyd Pique metadata = re.sub(r'version: "[^"]*"', f'version: "{version}"', metadata) 127*84e872a0SLloyd Pique metadata = re.sub( 128*84e872a0SLloyd Pique r'last_upgrade_date {[^}]*}', 129*84e872a0SLloyd Pique (f'last_upgrade_date {{ year: {now.year} month: {now.month} ' 130*84e872a0SLloyd Pique f'day: {now.day} }}'), metadata) 131*84e872a0SLloyd Pique 132*84e872a0SLloyd Pique with open('METADATA', 'wt') as metadata_file: 133*84e872a0SLloyd Pique metadata_file.write(metadata) 134*84e872a0SLloyd Pique 135*84e872a0SLloyd Pique 136*84e872a0SLloyd Piquedef configure_wayland_version_header(version): 137*84e872a0SLloyd Pique with open('./src/wayland-version.h.in', 'rt') as template_file: 138*84e872a0SLloyd Pique content = template_file.read() 139*84e872a0SLloyd Pique 140*84e872a0SLloyd Pique (major, minor, micro) = version.split('.') 141*84e872a0SLloyd Pique 142*84e872a0SLloyd Pique content = re.sub(r'@WAYLAND_VERSION_MAJOR@', major, content) 143*84e872a0SLloyd Pique content = re.sub(r'@WAYLAND_VERSION_MINOR@', minor, content) 144*84e872a0SLloyd Pique content = re.sub(r'@WAYLAND_VERSION_MICRO@', micro, content) 145*84e872a0SLloyd Pique content = re.sub(r'@WAYLAND_VERSION@', version, content) 146*84e872a0SLloyd Pique 147*84e872a0SLloyd Pique with open('./src/wayland-version.h', 'wt') as version_header: 148*84e872a0SLloyd Pique version_header.write(content) 149*84e872a0SLloyd Pique 150*84e872a0SLloyd Pique # wayland-version.h is in .gitignore, so we explicitly have to force-add it. 151*84e872a0SLloyd Pique git(['add', '-f', './src/wayland-version.h']) 152*84e872a0SLloyd Pique 153*84e872a0SLloyd Pique 154*84e872a0SLloyd Piquedef import_sources(version, preserve_files, update_metdata=True): 155*84e872a0SLloyd Pique start_hash = git_get_hash('HEAD') 156*84e872a0SLloyd Pique 157*84e872a0SLloyd Pique # Use `git-read-tree` to start with a pure copy of the imported version 158*84e872a0SLloyd Pique git(['read-tree', '-m', '-u', f'{version}^{{tree}}']) 159*84e872a0SLloyd Pique 160*84e872a0SLloyd Pique git(['commit', '-m', f'To squash: Clean import of {version}']) 161*84e872a0SLloyd Pique 162*84e872a0SLloyd Pique print(' Adding Android metadata') 163*84e872a0SLloyd Pique 164*84e872a0SLloyd Pique # Restore the needed Android files 165*84e872a0SLloyd Pique git(['restore', '--staged', '--worktree', '--source', start_hash] + 166*84e872a0SLloyd Pique list(sorted(preserve_files))) 167*84e872a0SLloyd Pique 168*84e872a0SLloyd Pique if update_metdata: 169*84e872a0SLloyd Pique update_metadata_version_and_import_date(version) 170*84e872a0SLloyd Pique configure_wayland_version_header(version) 171*84e872a0SLloyd Pique 172*84e872a0SLloyd Pique git(['commit', '-a', '-m', f'To squash: Update versions {version}']) 173*84e872a0SLloyd Pique 174*84e872a0SLloyd Pique 175*84e872a0SLloyd Piquedef apply_and_reexport_patches(version, patches, use_cherry_pick=False): 176*84e872a0SLloyd Pique if not patches: 177*84e872a0SLloyd Pique return 178*84e872a0SLloyd Pique 179*84e872a0SLloyd Pique print(f' Applying {len(patches)} Android patches') 180*84e872a0SLloyd Pique 181*84e872a0SLloyd Pique try: 182*84e872a0SLloyd Pique if use_cherry_pick: 183*84e872a0SLloyd Pique git(['cherry-pick'] + patches) 184*84e872a0SLloyd Pique else: 185*84e872a0SLloyd Pique git(['am'] + patches) 186*84e872a0SLloyd Pique except subprocess.CalledProcessError as e: 187*84e872a0SLloyd Pique if 'patch failed' not in e.stderr: 188*84e872a0SLloyd Pique raise 189*84e872a0SLloyd Pique # Print out the captured error mess 190*84e872a0SLloyd Pique sys.stderr.write(f''' 191*84e872a0SLloyd PiqueFailure applying patches to Wayland {version} via: 192*84e872a0SLloyd Pique {e.cmd} 193*84e872a0SLloyd Pique 194*84e872a0SLloyd PiqueOnce the patches have been resolved, please re-export the patches with: 195*84e872a0SLloyd Pique 196*84e872a0SLloyd Pique git rm patches/*.diff 197*84e872a0SLloyd Pique git format-patch HEAD~{len(patches)}..HEAD --no-stat --no-signature \\ 198*84e872a0SLloyd Pique --numbered --zero-commit --suffix=.diff --output-directory patches 199*84e872a0SLloyd Pique 200*84e872a0SLloyd Pique... and also add them to the final squashed commit. 201*84e872a0SLloyd Pique '''.strip()) 202*84e872a0SLloyd Pique 203*84e872a0SLloyd Pique sys.stdout.write(e.stdout) 204*84e872a0SLloyd Pique sys.exit(e.stderr) 205*84e872a0SLloyd Pique 206*84e872a0SLloyd Pique patch_hashes = list( 207*84e872a0SLloyd Pique reversed( 208*84e872a0SLloyd Pique git(['log', f'-{len(patches)}', 209*84e872a0SLloyd Pique '--pretty=format:%H']).stdout.split())) 210*84e872a0SLloyd Pique 211*84e872a0SLloyd Pique # Clean out the existing patches 212*84e872a0SLloyd Pique git(['rm', 'patches/*.diff']) 213*84e872a0SLloyd Pique 214*84e872a0SLloyd Pique # Re-export the patches, omitting information that might change 215*84e872a0SLloyd Pique git([ 216*84e872a0SLloyd Pique 'format-patch', f'HEAD~{len(patches)}..HEAD', '--no-stat', 217*84e872a0SLloyd Pique '--no-signature', '--numbered', '--zero-commit', '--suffix=.diff', 218*84e872a0SLloyd Pique '--output-directory', 'patches' 219*84e872a0SLloyd Pique ]) 220*84e872a0SLloyd Pique 221*84e872a0SLloyd Pique # Add back all the exported patches 222*84e872a0SLloyd Pique git(['add', 'patches/*.diff']) 223*84e872a0SLloyd Pique 224*84e872a0SLloyd Pique # Create a commit for the exported patches if there are any differences. 225*84e872a0SLloyd Pique r = git(['diff-files', '--quiet', '--ignore-submodules'], check=False) 226*84e872a0SLloyd Pique if r.returncode: 227*84e872a0SLloyd Pique git(['commit', '-a', '-m', f'To squash: Update patches for {version}']) 228*84e872a0SLloyd Pique 229*84e872a0SLloyd Pique return patch_hashes 230*84e872a0SLloyd Pique 231*84e872a0SLloyd Pique 232*84e872a0SLloyd Piquedef main(): 233*84e872a0SLloyd Pique parser = argparse.ArgumentParser(description=( 234*84e872a0SLloyd Pique "Helper script for importing a snapshot of the Wayland sources.")) 235*84e872a0SLloyd Pique parser.add_argument('version', 236*84e872a0SLloyd Pique nargs='?', 237*84e872a0SLloyd Pique default=None, 238*84e872a0SLloyd Pique help='The official version to import') 239*84e872a0SLloyd Pique parser.add_argument( 240*84e872a0SLloyd Pique '--no-validate-existing', 241*84e872a0SLloyd Pique dest='validate_existing', 242*84e872a0SLloyd Pique default=True, 243*84e872a0SLloyd Pique action='store_false', 244*84e872a0SLloyd Pique help='Whether to validate the current tree against upstream + patches') 245*84e872a0SLloyd Pique parser.add_argument('--no-squash', 246*84e872a0SLloyd Pique dest='squash', 247*84e872a0SLloyd Pique default=True, 248*84e872a0SLloyd Pique action='store_false', 249*84e872a0SLloyd Pique help='Whether to squash the import to a single commit') 250*84e872a0SLloyd Pique args = parser.parse_args() 251*84e872a0SLloyd Pique 252*84e872a0SLloyd Pique print( 253*84e872a0SLloyd Pique f'Preparing to importing Wayland core sources version {args.version}') 254*84e872a0SLloyd Pique 255*84e872a0SLloyd Pique assert_no_uncommitted_changes() 256*84e872a0SLloyd Pique 257*84e872a0SLloyd Pique official_source_git_url = metadata_read_git_url() 258*84e872a0SLloyd Pique current_version = metadata_read_current_version() 259*84e872a0SLloyd Pique 260*84e872a0SLloyd Pique setup_and_update_official_source_remote(official_source_git_url) 261*84e872a0SLloyd Pique 262*84e872a0SLloyd Pique # Get the list of Android added files to preserve 263*84e872a0SLloyd Pique preserve_files = determine_files_to_preserve(current_version) 264*84e872a0SLloyd Pique 265*84e872a0SLloyd Pique # Filter the list to get all patches that we will need to apply 266*84e872a0SLloyd Pique patch_files = sorted(path for path in preserve_files 267*84e872a0SLloyd Pique if path.startswith('patches/')) 268*84e872a0SLloyd Pique 269*84e872a0SLloyd Pique # Detect any add/add conflicts before we begin 270*84e872a0SLloyd Pique new_files = get_git_files(args.version or current_version) 271*84e872a0SLloyd Pique add_add_conflicts = preserve_files.intersection(new_files) 272*84e872a0SLloyd Pique if add_add_conflicts: 273*84e872a0SLloyd Pique sys.exit(f''' 274*84e872a0SLloyd PiqueError: The new version of Wayland adds files that are also added for Android: 275*84e872a0SLloyd Pique{add_add_conflicts} 276*84e872a0SLloyd Pique '''.strip()) 277*84e872a0SLloyd Pique 278*84e872a0SLloyd Pique import_branch_name = f'import_{args.version}' if args.version else None 279*84e872a0SLloyd Pique 280*84e872a0SLloyd Pique if import_branch_name and git_is_ref(import_branch_name): 281*84e872a0SLloyd Pique sys.exit(f''' 282*84e872a0SLloyd PiqueError: Branch name {import_branch_name} already exists. Please delete or rename. 283*84e872a0SLloyd Pique '''.strip()) 284*84e872a0SLloyd Pique 285*84e872a0SLloyd Pique initial_commit_hash = git_get_hash('HEAD') 286*84e872a0SLloyd Pique 287*84e872a0SLloyd Pique # Begin a branch for the version import, if a new version is being imported 288*84e872a0SLloyd Pique if import_branch_name: 289*84e872a0SLloyd Pique git(['checkout', '-b', import_branch_name]) 290*84e872a0SLloyd Pique git([ 291*84e872a0SLloyd Pique 'commit', '--allow-empty', '-m', 292*84e872a0SLloyd Pique f'Update to Wayland {args.version}' 293*84e872a0SLloyd Pique ]) 294*84e872a0SLloyd Pique 295*84e872a0SLloyd Pique patch_hashes = None 296*84e872a0SLloyd Pique 297*84e872a0SLloyd Pique if args.validate_existing: 298*84e872a0SLloyd Pique print(f'Importing {current_version} to validate all current fixups') 299*84e872a0SLloyd Pique import_sources(current_version, preserve_files, update_metdata=False) 300*84e872a0SLloyd Pique patch_hashes = apply_and_reexport_patches(current_version, patch_files) 301*84e872a0SLloyd Pique 302*84e872a0SLloyd Pique r = git( 303*84e872a0SLloyd Pique ['diff', '--quiet', '--ignore-submodules', initial_commit_hash], 304*84e872a0SLloyd Pique check=False) 305*84e872a0SLloyd Pique if r.returncode: 306*84e872a0SLloyd Pique sys.exit(f''' 307*84e872a0SLloyd PiqueFailed to recreate the pre-import tree by importing the prior Wayland version 308*84e872a0SLloyd Pique{current_version} and applying the Android fixups. 309*84e872a0SLloyd Pique 310*84e872a0SLloyd PiqueThis is likely due to changes having been made to the Wayland sources without 311*84e872a0SLloyd Piquea corresponding patch file in patches/. 312*84e872a0SLloyd Pique 313*84e872a0SLloyd PiqueTo see the differences detected, run: 314*84e872a0SLloyd Pique 315*84e872a0SLloyd Piquegit diff {initial_commit_hash} 316*84e872a0SLloyd Pique '''.strip()) 317*84e872a0SLloyd Pique 318*84e872a0SLloyd Pique if args.version: 319*84e872a0SLloyd Pique print(f'Importing {args.version}') 320*84e872a0SLloyd Pique import_sources(args.version, preserve_files) 321*84e872a0SLloyd Pique apply_and_reexport_patches( 322*84e872a0SLloyd Pique args.version, 323*84e872a0SLloyd Pique (patch_hashes if patch_hashes is not None else patch_files), 324*84e872a0SLloyd Pique use_cherry_pick=(patch_hashes is not None)) 325*84e872a0SLloyd Pique 326*84e872a0SLloyd Pique if args.squash: 327*84e872a0SLloyd Pique print('Squashing to one commit') 328*84e872a0SLloyd Pique git(['reset', '--soft', initial_commit_hash]) 329*84e872a0SLloyd Pique git([ 330*84e872a0SLloyd Pique 'commit', '--allow-empty', '-m', f''' 331*84e872a0SLloyd PiqueUpdate to Wayland {args.version} 332*84e872a0SLloyd Pique 333*84e872a0SLloyd PiqueAutomatic import using "./import_official_snapshot.py {args.version}" 334*84e872a0SLloyd Pique '''.strip() 335*84e872a0SLloyd Pique ]) 336*84e872a0SLloyd Pique 337*84e872a0SLloyd Pique 338*84e872a0SLloyd Piqueif __name__ == '__main__': 339*84e872a0SLloyd Pique main() 340