xref: /aosp_15_r20/external/wayland/import_official_snapshot.py (revision 84e872a0dc482bffdb63672969dd03a827d67c73)
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