xref: /aosp_15_r20/tools/external_updater/external_updater.py (revision 3c875a214f382db1236d28570d1304ce57138f32)
1*3c875a21SAndroid Build Coastguard Worker#
2*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project
3*3c875a21SAndroid Build Coastguard Worker#
4*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*3c875a21SAndroid Build Coastguard Worker#
8*3c875a21SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*3c875a21SAndroid Build Coastguard Worker#
10*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*3c875a21SAndroid Build Coastguard Worker# limitations under the License.
15*3c875a21SAndroid Build Coastguard Worker"""A commandline tool to check and update packages in external/
16*3c875a21SAndroid Build Coastguard Worker
17*3c875a21SAndroid Build Coastguard WorkerExample usage:
18*3c875a21SAndroid Build Coastguard Workerupdater.sh checkall
19*3c875a21SAndroid Build Coastguard Workerupdater.sh update kotlinc
20*3c875a21SAndroid Build Coastguard Workerupdater.sh update --refresh --keep_date rust/crates/libc
21*3c875a21SAndroid Build Coastguard Worker"""
22*3c875a21SAndroid Build Coastguard Worker
23*3c875a21SAndroid Build Coastguard Workerimport argparse
24*3c875a21SAndroid Build Coastguard Workerfrom collections.abc import Iterable
25*3c875a21SAndroid Build Coastguard Workerimport json
26*3c875a21SAndroid Build Coastguard Workerimport logging
27*3c875a21SAndroid Build Coastguard Workerimport os
28*3c875a21SAndroid Build Coastguard Workerimport subprocess
29*3c875a21SAndroid Build Coastguard Workerimport textwrap
30*3c875a21SAndroid Build Coastguard Workerimport time
31*3c875a21SAndroid Build Coastguard Workerfrom typing import Dict, Iterator, List, Union, Tuple, Type
32*3c875a21SAndroid Build Coastguard Workerfrom pathlib import Path
33*3c875a21SAndroid Build Coastguard Worker
34*3c875a21SAndroid Build Coastguard Workerfrom base_updater import Updater
35*3c875a21SAndroid Build Coastguard Workerfrom color import Color, color_string
36*3c875a21SAndroid Build Coastguard Workerfrom crates_updater import CratesUpdater
37*3c875a21SAndroid Build Coastguard Workerfrom git_updater import GitUpdater
38*3c875a21SAndroid Build Coastguard Workerfrom github_archive_updater import GithubArchiveUpdater
39*3c875a21SAndroid Build Coastguard Workerimport fileutils
40*3c875a21SAndroid Build Coastguard Workerimport git_utils
41*3c875a21SAndroid Build Coastguard Worker# pylint: disable=import-error
42*3c875a21SAndroid Build Coastguard Workerimport metadata_pb2  # type: ignore
43*3c875a21SAndroid Build Coastguard Workerimport updater_utils
44*3c875a21SAndroid Build Coastguard Worker
45*3c875a21SAndroid Build Coastguard WorkerUPDATERS: List[Type[Updater]] = [
46*3c875a21SAndroid Build Coastguard Worker    CratesUpdater,
47*3c875a21SAndroid Build Coastguard Worker    GithubArchiveUpdater,
48*3c875a21SAndroid Build Coastguard Worker    GitUpdater,
49*3c875a21SAndroid Build Coastguard Worker]
50*3c875a21SAndroid Build Coastguard Worker
51*3c875a21SAndroid Build Coastguard WorkerTMP_BRANCH_NAME = 'tmp_auto_upgrade'
52*3c875a21SAndroid Build Coastguard Worker
53*3c875a21SAndroid Build Coastguard Worker
54*3c875a21SAndroid Build Coastguard Workerdef build_updater(proj_path: Path) -> Tuple[Updater, metadata_pb2.MetaData]:
55*3c875a21SAndroid Build Coastguard Worker    """Build updater for a project specified by proj_path.
56*3c875a21SAndroid Build Coastguard Worker
57*3c875a21SAndroid Build Coastguard Worker    Reads and parses METADATA file. And builds updater based on the information.
58*3c875a21SAndroid Build Coastguard Worker
59*3c875a21SAndroid Build Coastguard Worker    Args:
60*3c875a21SAndroid Build Coastguard Worker      proj_path: Absolute or relative path to the project.
61*3c875a21SAndroid Build Coastguard Worker
62*3c875a21SAndroid Build Coastguard Worker    Returns:
63*3c875a21SAndroid Build Coastguard Worker      The updater object built. None if there's any error.
64*3c875a21SAndroid Build Coastguard Worker    """
65*3c875a21SAndroid Build Coastguard Worker
66*3c875a21SAndroid Build Coastguard Worker    proj_path = fileutils.get_absolute_project_path(proj_path)
67*3c875a21SAndroid Build Coastguard Worker    metadata = fileutils.read_metadata(proj_path)
68*3c875a21SAndroid Build Coastguard Worker    metadata = fileutils.convert_url_to_identifier(metadata)
69*3c875a21SAndroid Build Coastguard Worker    updater = updater_utils.create_updater(metadata, proj_path, UPDATERS)
70*3c875a21SAndroid Build Coastguard Worker    return updater, metadata
71*3c875a21SAndroid Build Coastguard Worker
72*3c875a21SAndroid Build Coastguard Worker
73*3c875a21SAndroid Build Coastguard Workerdef commit_message_generator(project_name: str, version: str, path: str, bug: int | None = None) -> str:
74*3c875a21SAndroid Build Coastguard Worker    header = f"Upgrade {project_name} to {version}\n"
75*3c875a21SAndroid Build Coastguard Worker    body = textwrap.dedent(f"""
76*3c875a21SAndroid Build Coastguard Worker    This project was upgraded with external_updater.
77*3c875a21SAndroid Build Coastguard Worker    Usage: tools/external_updater/updater.sh update external/{path}
78*3c875a21SAndroid Build Coastguard Worker    For more info, check https://cs.android.com/android/platform/superproject/main/+/main:tools/external_updater/README.md\n\n""")
79*3c875a21SAndroid Build Coastguard Worker    if bug is None:
80*3c875a21SAndroid Build Coastguard Worker        footer = "Test: TreeHugger"
81*3c875a21SAndroid Build Coastguard Worker    else:
82*3c875a21SAndroid Build Coastguard Worker        footer = f"Bug: {bug}\nTest: TreeHugger"
83*3c875a21SAndroid Build Coastguard Worker    return header + body + footer
84*3c875a21SAndroid Build Coastguard Worker
85*3c875a21SAndroid Build Coastguard Worker
86*3c875a21SAndroid Build Coastguard Workerdef _do_update(args: argparse.Namespace, updater: Updater,
87*3c875a21SAndroid Build Coastguard Worker               metadata: metadata_pb2.MetaData) -> None:
88*3c875a21SAndroid Build Coastguard Worker    full_path = updater.project_path
89*3c875a21SAndroid Build Coastguard Worker
90*3c875a21SAndroid Build Coastguard Worker    if not args.keep_local_changes:
91*3c875a21SAndroid Build Coastguard Worker        git_utils.detach_to_android_head(full_path)
92*3c875a21SAndroid Build Coastguard Worker        if TMP_BRANCH_NAME in git_utils.list_local_branches(full_path):
93*3c875a21SAndroid Build Coastguard Worker            git_utils.delete_branch(full_path, TMP_BRANCH_NAME)
94*3c875a21SAndroid Build Coastguard Worker            git_utils.reset_hard(full_path)
95*3c875a21SAndroid Build Coastguard Worker            git_utils.clean(full_path)
96*3c875a21SAndroid Build Coastguard Worker        git_utils.start_branch(full_path, TMP_BRANCH_NAME)
97*3c875a21SAndroid Build Coastguard Worker
98*3c875a21SAndroid Build Coastguard Worker    try:
99*3c875a21SAndroid Build Coastguard Worker        updater.update()
100*3c875a21SAndroid Build Coastguard Worker
101*3c875a21SAndroid Build Coastguard Worker        updated_metadata = updater.update_metadata(metadata)
102*3c875a21SAndroid Build Coastguard Worker        fileutils.write_metadata(full_path, updated_metadata, args.keep_date)
103*3c875a21SAndroid Build Coastguard Worker        git_utils.add_file(full_path, 'METADATA')
104*3c875a21SAndroid Build Coastguard Worker
105*3c875a21SAndroid Build Coastguard Worker        try:
106*3c875a21SAndroid Build Coastguard Worker            rel_proj_path = str(fileutils.get_relative_project_path(full_path))
107*3c875a21SAndroid Build Coastguard Worker        except ValueError:
108*3c875a21SAndroid Build Coastguard Worker            # Absolute paths to other trees will not be relative to our tree. There are
109*3c875a21SAndroid Build Coastguard Worker            # not portable instructions for upgrading that project, since the path will
110*3c875a21SAndroid Build Coastguard Worker            # differ between machines (or checkouts).
111*3c875a21SAndroid Build Coastguard Worker            rel_proj_path = "<absolute path to project>"
112*3c875a21SAndroid Build Coastguard Worker        commit_message = commit_message_generator(metadata.name, updater.latest_version, rel_proj_path, args.bug)
113*3c875a21SAndroid Build Coastguard Worker        git_utils.remove_gitmodules(full_path)
114*3c875a21SAndroid Build Coastguard Worker        git_utils.add_file(full_path, '*')
115*3c875a21SAndroid Build Coastguard Worker        git_utils.commit(full_path, commit_message, args.no_verify)
116*3c875a21SAndroid Build Coastguard Worker
117*3c875a21SAndroid Build Coastguard Worker        if not args.skip_post_update:
118*3c875a21SAndroid Build Coastguard Worker            updater_utils.run_post_update(full_path, full_path)
119*3c875a21SAndroid Build Coastguard Worker            git_utils.add_file(full_path, '*')
120*3c875a21SAndroid Build Coastguard Worker            git_utils.commit_amend(full_path)
121*3c875a21SAndroid Build Coastguard Worker
122*3c875a21SAndroid Build Coastguard Worker        if args.build:
123*3c875a21SAndroid Build Coastguard Worker            try:
124*3c875a21SAndroid Build Coastguard Worker                updater_utils.build(full_path)
125*3c875a21SAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
126*3c875a21SAndroid Build Coastguard Worker                logging.exception("Build failed, aborting upload")
127*3c875a21SAndroid Build Coastguard Worker                return
128*3c875a21SAndroid Build Coastguard Worker    except Exception as err:
129*3c875a21SAndroid Build Coastguard Worker        if updater.rollback():
130*3c875a21SAndroid Build Coastguard Worker            print('Rolled back.')
131*3c875a21SAndroid Build Coastguard Worker        raise err
132*3c875a21SAndroid Build Coastguard Worker
133*3c875a21SAndroid Build Coastguard Worker    if not args.no_upload:
134*3c875a21SAndroid Build Coastguard Worker        git_utils.push(full_path, args.remote_name, updater.has_errors)
135*3c875a21SAndroid Build Coastguard Worker
136*3c875a21SAndroid Build Coastguard Worker
137*3c875a21SAndroid Build Coastguard Workerdef has_new_version(updater: Updater) -> bool:
138*3c875a21SAndroid Build Coastguard Worker    """Checks if a newer version of the project is available."""
139*3c875a21SAndroid Build Coastguard Worker    if updater.latest_version is not None and updater.current_version != updater.latest_version:
140*3c875a21SAndroid Build Coastguard Worker        return True
141*3c875a21SAndroid Build Coastguard Worker    return False
142*3c875a21SAndroid Build Coastguard Worker
143*3c875a21SAndroid Build Coastguard Worker
144*3c875a21SAndroid Build Coastguard Workerdef print_project_status(updater: Updater) -> None:
145*3c875a21SAndroid Build Coastguard Worker    """Prints the current status of the project on console."""
146*3c875a21SAndroid Build Coastguard Worker
147*3c875a21SAndroid Build Coastguard Worker    current_version = updater.current_version
148*3c875a21SAndroid Build Coastguard Worker    latest_version = updater.latest_version
149*3c875a21SAndroid Build Coastguard Worker    alternative_latest_version = updater.alternative_latest_version
150*3c875a21SAndroid Build Coastguard Worker
151*3c875a21SAndroid Build Coastguard Worker    print(f'Current version: {current_version}')
152*3c875a21SAndroid Build Coastguard Worker    print('Latest version: ', end='')
153*3c875a21SAndroid Build Coastguard Worker    if not latest_version:
154*3c875a21SAndroid Build Coastguard Worker        print(color_string('Not available', Color.STALE))
155*3c875a21SAndroid Build Coastguard Worker    else:
156*3c875a21SAndroid Build Coastguard Worker        print(latest_version)
157*3c875a21SAndroid Build Coastguard Worker    if alternative_latest_version is not None:
158*3c875a21SAndroid Build Coastguard Worker        print(f'Alternative latest version: {alternative_latest_version}')
159*3c875a21SAndroid Build Coastguard Worker    if has_new_version(updater):
160*3c875a21SAndroid Build Coastguard Worker        print(color_string('Out of date!', Color.STALE))
161*3c875a21SAndroid Build Coastguard Worker    else:
162*3c875a21SAndroid Build Coastguard Worker        print(color_string('Up to date.', Color.FRESH))
163*3c875a21SAndroid Build Coastguard Worker
164*3c875a21SAndroid Build Coastguard Worker
165*3c875a21SAndroid Build Coastguard Workerdef find_ver_types(current_version: str) -> Tuple[str, str]:
166*3c875a21SAndroid Build Coastguard Worker    if git_utils.is_commit(current_version):
167*3c875a21SAndroid Build Coastguard Worker        alternative_ver_type = 'tag'
168*3c875a21SAndroid Build Coastguard Worker        latest_ver_type = 'sha'
169*3c875a21SAndroid Build Coastguard Worker    else:
170*3c875a21SAndroid Build Coastguard Worker        alternative_ver_type = 'sha'
171*3c875a21SAndroid Build Coastguard Worker        latest_ver_type = 'tag'
172*3c875a21SAndroid Build Coastguard Worker    return latest_ver_type, alternative_ver_type
173*3c875a21SAndroid Build Coastguard Worker
174*3c875a21SAndroid Build Coastguard Worker
175*3c875a21SAndroid Build Coastguard Workerdef use_alternative_version(updater: Updater) -> bool:
176*3c875a21SAndroid Build Coastguard Worker    """This function only runs when there is an alternative version available."""
177*3c875a21SAndroid Build Coastguard Worker
178*3c875a21SAndroid Build Coastguard Worker    latest_ver_type, alternative_ver_type = find_ver_types(updater.current_version)
179*3c875a21SAndroid Build Coastguard Worker    latest_version = updater.latest_version
180*3c875a21SAndroid Build Coastguard Worker    alternative_version = updater.alternative_latest_version
181*3c875a21SAndroid Build Coastguard Worker    new_version_available = has_new_version(updater)
182*3c875a21SAndroid Build Coastguard Worker
183*3c875a21SAndroid Build Coastguard Worker    out_of_date_question = f'Would you like to upgrade to {alternative_ver_type} {alternative_version} instead of {latest_ver_type} {latest_version}? (yes/no)\n'
184*3c875a21SAndroid Build Coastguard Worker    up_to_date_question = f'Would you like to upgrade to {alternative_ver_type} {alternative_version}? (yes/no)\n'
185*3c875a21SAndroid Build Coastguard Worker    recom_message = color_string(f'We recommend upgrading to {alternative_ver_type} {alternative_version} instead. ', Color.FRESH)
186*3c875a21SAndroid Build Coastguard Worker    not_recom_message = color_string(f'We DO NOT recommend upgrading to {alternative_ver_type} {alternative_version}. ', Color.STALE)
187*3c875a21SAndroid Build Coastguard Worker
188*3c875a21SAndroid Build Coastguard Worker    # If alternative_version is not None, there are four possible
189*3c875a21SAndroid Build Coastguard Worker    # scenarios:
190*3c875a21SAndroid Build Coastguard Worker    # Scenario 1, out of date, we recommend switching to tag:
191*3c875a21SAndroid Build Coastguard Worker    # Current version: sha1
192*3c875a21SAndroid Build Coastguard Worker    # Latest version: sha2
193*3c875a21SAndroid Build Coastguard Worker    # Alternative latest version: tag
194*3c875a21SAndroid Build Coastguard Worker
195*3c875a21SAndroid Build Coastguard Worker    # Scenario 2, up to date, we DO NOT recommend switching to sha.
196*3c875a21SAndroid Build Coastguard Worker    # Current version: tag1
197*3c875a21SAndroid Build Coastguard Worker    # Latest version: tag1
198*3c875a21SAndroid Build Coastguard Worker    # Alternative latest version: sha
199*3c875a21SAndroid Build Coastguard Worker
200*3c875a21SAndroid Build Coastguard Worker    # Scenario 3, out of date, we DO NOT recommend switching to sha.
201*3c875a21SAndroid Build Coastguard Worker    # Current version: tag1
202*3c875a21SAndroid Build Coastguard Worker    # Latest version: tag2
203*3c875a21SAndroid Build Coastguard Worker    # Alternative latest version: sha
204*3c875a21SAndroid Build Coastguard Worker
205*3c875a21SAndroid Build Coastguard Worker    # Scenario 4, out of date, no recommendations at all
206*3c875a21SAndroid Build Coastguard Worker    # Current version: sha1
207*3c875a21SAndroid Build Coastguard Worker    # Latest version: No tag found or a tag that doesn't belong to any branch
208*3c875a21SAndroid Build Coastguard Worker    # Alternative latest version: sha
209*3c875a21SAndroid Build Coastguard Worker
210*3c875a21SAndroid Build Coastguard Worker    if alternative_ver_type == 'tag':
211*3c875a21SAndroid Build Coastguard Worker        warning = out_of_date_question + recom_message
212*3c875a21SAndroid Build Coastguard Worker    else:
213*3c875a21SAndroid Build Coastguard Worker        if not new_version_available:
214*3c875a21SAndroid Build Coastguard Worker            warning = up_to_date_question + not_recom_message
215*3c875a21SAndroid Build Coastguard Worker        else:
216*3c875a21SAndroid Build Coastguard Worker            if not latest_version:
217*3c875a21SAndroid Build Coastguard Worker                warning = up_to_date_question
218*3c875a21SAndroid Build Coastguard Worker            else:
219*3c875a21SAndroid Build Coastguard Worker                warning = out_of_date_question + not_recom_message
220*3c875a21SAndroid Build Coastguard Worker
221*3c875a21SAndroid Build Coastguard Worker    answer = input(warning)
222*3c875a21SAndroid Build Coastguard Worker    if "yes".startswith(answer.lower()):
223*3c875a21SAndroid Build Coastguard Worker        return True
224*3c875a21SAndroid Build Coastguard Worker    elif answer.lower().startswith("no"):
225*3c875a21SAndroid Build Coastguard Worker        return False
226*3c875a21SAndroid Build Coastguard Worker    # If user types something that is not "yes" or "no" or something similar, abort.
227*3c875a21SAndroid Build Coastguard Worker    else:
228*3c875a21SAndroid Build Coastguard Worker        raise ValueError(f"Invalid input: {answer}")
229*3c875a21SAndroid Build Coastguard Worker
230*3c875a21SAndroid Build Coastguard Worker
231*3c875a21SAndroid Build Coastguard Workerdef check_and_update(args: argparse.Namespace,
232*3c875a21SAndroid Build Coastguard Worker                     proj_path: Path,
233*3c875a21SAndroid Build Coastguard Worker                     update_lib=False) -> Union[Updater, str]:
234*3c875a21SAndroid Build Coastguard Worker    """Checks updates for a project.
235*3c875a21SAndroid Build Coastguard Worker
236*3c875a21SAndroid Build Coastguard Worker    Args:
237*3c875a21SAndroid Build Coastguard Worker      args: commandline arguments
238*3c875a21SAndroid Build Coastguard Worker      proj_path: Absolute or relative path to the project.
239*3c875a21SAndroid Build Coastguard Worker      update_lib: If false, will only check for new version, but not update.
240*3c875a21SAndroid Build Coastguard Worker    """
241*3c875a21SAndroid Build Coastguard Worker
242*3c875a21SAndroid Build Coastguard Worker    try:
243*3c875a21SAndroid Build Coastguard Worker        canonical_path = fileutils.canonicalize_project_path(proj_path)
244*3c875a21SAndroid Build Coastguard Worker        print(f'Checking {canonical_path}...')
245*3c875a21SAndroid Build Coastguard Worker        updater, metadata = build_updater(proj_path)
246*3c875a21SAndroid Build Coastguard Worker        updater.check()
247*3c875a21SAndroid Build Coastguard Worker
248*3c875a21SAndroid Build Coastguard Worker        new_version_available = has_new_version(updater)
249*3c875a21SAndroid Build Coastguard Worker        print_project_status(updater)
250*3c875a21SAndroid Build Coastguard Worker
251*3c875a21SAndroid Build Coastguard Worker        if update_lib:
252*3c875a21SAndroid Build Coastguard Worker            if args.custom_version is not None:
253*3c875a21SAndroid Build Coastguard Worker                updater.set_custom_version(args.custom_version)
254*3c875a21SAndroid Build Coastguard Worker                print(f"Upgrading to custom version {args.custom_version}")
255*3c875a21SAndroid Build Coastguard Worker            elif args.refresh:
256*3c875a21SAndroid Build Coastguard Worker                updater.refresh_without_upgrading()
257*3c875a21SAndroid Build Coastguard Worker            elif new_version_available:
258*3c875a21SAndroid Build Coastguard Worker                if updater.alternative_latest_version is not None:
259*3c875a21SAndroid Build Coastguard Worker                    if use_alternative_version(updater):
260*3c875a21SAndroid Build Coastguard Worker                        updater.set_new_version(updater.alternative_latest_version)
261*3c875a21SAndroid Build Coastguard Worker            else:
262*3c875a21SAndroid Build Coastguard Worker                return updater
263*3c875a21SAndroid Build Coastguard Worker            _do_update(args, updater, metadata)
264*3c875a21SAndroid Build Coastguard Worker        return updater
265*3c875a21SAndroid Build Coastguard Worker
266*3c875a21SAndroid Build Coastguard Worker    # pylint: disable=broad-except
267*3c875a21SAndroid Build Coastguard Worker    except Exception as err:
268*3c875a21SAndroid Build Coastguard Worker        logging.exception("Failed to check or update %s", proj_path)
269*3c875a21SAndroid Build Coastguard Worker        return str(err)
270*3c875a21SAndroid Build Coastguard Worker
271*3c875a21SAndroid Build Coastguard Worker
272*3c875a21SAndroid Build Coastguard Workerdef check_and_update_path(args: argparse.Namespace, paths: Iterable[Path],
273*3c875a21SAndroid Build Coastguard Worker                          update_lib: bool,
274*3c875a21SAndroid Build Coastguard Worker                          delay: int) -> Dict[str, Dict[str, str]]:
275*3c875a21SAndroid Build Coastguard Worker    results = {}
276*3c875a21SAndroid Build Coastguard Worker    for path in paths:
277*3c875a21SAndroid Build Coastguard Worker        res = {}
278*3c875a21SAndroid Build Coastguard Worker        updater = check_and_update(args, path, update_lib)
279*3c875a21SAndroid Build Coastguard Worker        if isinstance(updater, str):
280*3c875a21SAndroid Build Coastguard Worker            res['error'] = updater
281*3c875a21SAndroid Build Coastguard Worker        else:
282*3c875a21SAndroid Build Coastguard Worker            res['current'] = updater.current_version
283*3c875a21SAndroid Build Coastguard Worker            res['latest'] = updater.latest_version
284*3c875a21SAndroid Build Coastguard Worker        results[str(fileutils.canonicalize_project_path(path))] = res
285*3c875a21SAndroid Build Coastguard Worker        time.sleep(delay)
286*3c875a21SAndroid Build Coastguard Worker    return results
287*3c875a21SAndroid Build Coastguard Worker
288*3c875a21SAndroid Build Coastguard Worker
289*3c875a21SAndroid Build Coastguard Workerdef _list_all_metadata() -> Iterator[str]:
290*3c875a21SAndroid Build Coastguard Worker    for path, dirs, files in os.walk(fileutils.external_path()):
291*3c875a21SAndroid Build Coastguard Worker        if fileutils.METADATA_FILENAME in files:
292*3c875a21SAndroid Build Coastguard Worker            # Skip sub directories.
293*3c875a21SAndroid Build Coastguard Worker            dirs[:] = []
294*3c875a21SAndroid Build Coastguard Worker            yield path
295*3c875a21SAndroid Build Coastguard Worker        dirs.sort(key=lambda d: d.lower())
296*3c875a21SAndroid Build Coastguard Worker
297*3c875a21SAndroid Build Coastguard Worker
298*3c875a21SAndroid Build Coastguard Workerdef write_json(json_file: str, results: Dict[str, Dict[str, str]]) -> None:
299*3c875a21SAndroid Build Coastguard Worker    """Output a JSON report."""
300*3c875a21SAndroid Build Coastguard Worker    with Path(json_file).open('w', encoding='utf-8') as res_file:
301*3c875a21SAndroid Build Coastguard Worker        json.dump(results, res_file, sort_keys=True, indent=4)
302*3c875a21SAndroid Build Coastguard Worker
303*3c875a21SAndroid Build Coastguard Worker
304*3c875a21SAndroid Build Coastguard Workerdef validate(args: argparse.Namespace) -> None:
305*3c875a21SAndroid Build Coastguard Worker    """Handler for validate command."""
306*3c875a21SAndroid Build Coastguard Worker    paths = fileutils.resolve_command_line_paths(args.paths)
307*3c875a21SAndroid Build Coastguard Worker    try:
308*3c875a21SAndroid Build Coastguard Worker        canonical_path = fileutils.canonicalize_project_path(paths[0])
309*3c875a21SAndroid Build Coastguard Worker        print(f'Validating {canonical_path}')
310*3c875a21SAndroid Build Coastguard Worker        updater, _ = build_updater(paths[0])
311*3c875a21SAndroid Build Coastguard Worker        print(updater.validate())
312*3c875a21SAndroid Build Coastguard Worker    except Exception:  # pylint: disable=broad-exception-caught
313*3c875a21SAndroid Build Coastguard Worker        logging.exception("Failed to check or update %s", paths)
314*3c875a21SAndroid Build Coastguard Worker
315*3c875a21SAndroid Build Coastguard Worker
316*3c875a21SAndroid Build Coastguard Workerdef check(args: argparse.Namespace) -> None:
317*3c875a21SAndroid Build Coastguard Worker    """Handler for check command."""
318*3c875a21SAndroid Build Coastguard Worker    if args.all:
319*3c875a21SAndroid Build Coastguard Worker        paths = [Path(p) for p in _list_all_metadata()]
320*3c875a21SAndroid Build Coastguard Worker    else:
321*3c875a21SAndroid Build Coastguard Worker        paths = fileutils.resolve_command_line_paths(args.paths)
322*3c875a21SAndroid Build Coastguard Worker    results = check_and_update_path(args, paths, False, args.delay)
323*3c875a21SAndroid Build Coastguard Worker
324*3c875a21SAndroid Build Coastguard Worker    if args.json_output is not None:
325*3c875a21SAndroid Build Coastguard Worker        write_json(args.json_output, results)
326*3c875a21SAndroid Build Coastguard Worker
327*3c875a21SAndroid Build Coastguard Worker
328*3c875a21SAndroid Build Coastguard Workerdef update(args: argparse.Namespace) -> None:
329*3c875a21SAndroid Build Coastguard Worker    """Handler for update command."""
330*3c875a21SAndroid Build Coastguard Worker    all_paths = fileutils.resolve_command_line_paths(args.paths)
331*3c875a21SAndroid Build Coastguard Worker    # Remove excluded paths.
332*3c875a21SAndroid Build Coastguard Worker    excludes = set() if args.exclude is None else set(args.exclude)
333*3c875a21SAndroid Build Coastguard Worker    filtered_paths = [path for path in all_paths
334*3c875a21SAndroid Build Coastguard Worker                      if not path.name in excludes]
335*3c875a21SAndroid Build Coastguard Worker    # Now we can update each path.
336*3c875a21SAndroid Build Coastguard Worker    results = check_and_update_path(args, filtered_paths, True, 0)
337*3c875a21SAndroid Build Coastguard Worker
338*3c875a21SAndroid Build Coastguard Worker    if args.json_output is not None:
339*3c875a21SAndroid Build Coastguard Worker        write_json(args.json_output, results)
340*3c875a21SAndroid Build Coastguard Worker
341*3c875a21SAndroid Build Coastguard Worker
342*3c875a21SAndroid Build Coastguard Workerdef parse_args() -> argparse.Namespace:
343*3c875a21SAndroid Build Coastguard Worker    """Parses commandline arguments."""
344*3c875a21SAndroid Build Coastguard Worker
345*3c875a21SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
346*3c875a21SAndroid Build Coastguard Worker        description='Check updates for third party projects in external/.')
347*3c875a21SAndroid Build Coastguard Worker    subparsers = parser.add_subparsers(dest='cmd')
348*3c875a21SAndroid Build Coastguard Worker    subparsers.required = True
349*3c875a21SAndroid Build Coastguard Worker
350*3c875a21SAndroid Build Coastguard Worker    diff_parser = subparsers.add_parser('validate',
351*3c875a21SAndroid Build Coastguard Worker                                        help='Check if aosp version is what it claims to be.')
352*3c875a21SAndroid Build Coastguard Worker    diff_parser.add_argument(
353*3c875a21SAndroid Build Coastguard Worker        'paths',
354*3c875a21SAndroid Build Coastguard Worker        nargs='*',
355*3c875a21SAndroid Build Coastguard Worker        help='Paths of the project. '
356*3c875a21SAndroid Build Coastguard Worker             'Relative paths will be resolved from external/.')
357*3c875a21SAndroid Build Coastguard Worker    diff_parser.set_defaults(func=validate)
358*3c875a21SAndroid Build Coastguard Worker
359*3c875a21SAndroid Build Coastguard Worker    # Creates parser for check command.
360*3c875a21SAndroid Build Coastguard Worker    check_parser = subparsers.add_parser('check',
361*3c875a21SAndroid Build Coastguard Worker                                         help='Check update for one project.')
362*3c875a21SAndroid Build Coastguard Worker    check_parser.add_argument(
363*3c875a21SAndroid Build Coastguard Worker        'paths',
364*3c875a21SAndroid Build Coastguard Worker        nargs='*',
365*3c875a21SAndroid Build Coastguard Worker        help='Paths of the project. '
366*3c875a21SAndroid Build Coastguard Worker        'Relative paths will be resolved from external/.')
367*3c875a21SAndroid Build Coastguard Worker    check_parser.add_argument('--json-output',
368*3c875a21SAndroid Build Coastguard Worker                              help='Path of a json file to write result to.')
369*3c875a21SAndroid Build Coastguard Worker    check_parser.add_argument(
370*3c875a21SAndroid Build Coastguard Worker        '--all',
371*3c875a21SAndroid Build Coastguard Worker        action='store_true',
372*3c875a21SAndroid Build Coastguard Worker        help='If set, check updates for all supported projects.')
373*3c875a21SAndroid Build Coastguard Worker    check_parser.add_argument(
374*3c875a21SAndroid Build Coastguard Worker        '--delay',
375*3c875a21SAndroid Build Coastguard Worker        default=0,
376*3c875a21SAndroid Build Coastguard Worker        type=int,
377*3c875a21SAndroid Build Coastguard Worker        help='Time in seconds to wait between checking two projects.')
378*3c875a21SAndroid Build Coastguard Worker    check_parser.set_defaults(func=check)
379*3c875a21SAndroid Build Coastguard Worker
380*3c875a21SAndroid Build Coastguard Worker    # Creates parser for update command.
381*3c875a21SAndroid Build Coastguard Worker    update_parser = subparsers.add_parser('update', help='Update one project.')
382*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument(
383*3c875a21SAndroid Build Coastguard Worker        'paths',
384*3c875a21SAndroid Build Coastguard Worker        nargs='*',
385*3c875a21SAndroid Build Coastguard Worker        help='Paths of the project as globs. '
386*3c875a21SAndroid Build Coastguard Worker        'Relative paths will be resolved from external/.')
387*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--json-output',
388*3c875a21SAndroid Build Coastguard Worker                               help='Path of a json file to write result to.')
389*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument(
390*3c875a21SAndroid Build Coastguard Worker        '--refresh',
391*3c875a21SAndroid Build Coastguard Worker        help='Run update and refresh to the current version.',
392*3c875a21SAndroid Build Coastguard Worker        action='store_true')
393*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument(
394*3c875a21SAndroid Build Coastguard Worker        '--keep-date',
395*3c875a21SAndroid Build Coastguard Worker        help='Run update and do not change date in METADATA.',
396*3c875a21SAndroid Build Coastguard Worker        action='store_true')
397*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--no-upload',
398*3c875a21SAndroid Build Coastguard Worker                               action='store_true',
399*3c875a21SAndroid Build Coastguard Worker                               help='Does not upload to Gerrit after upgrade')
400*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--keep-local-changes',
401*3c875a21SAndroid Build Coastguard Worker                               action='store_true',
402*3c875a21SAndroid Build Coastguard Worker                               help='Updates the current branch')
403*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--skip-post-update',
404*3c875a21SAndroid Build Coastguard Worker                               action='store_true',
405*3c875a21SAndroid Build Coastguard Worker                               help='Skip post_update script')
406*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--no-build',
407*3c875a21SAndroid Build Coastguard Worker                               action='store_false',
408*3c875a21SAndroid Build Coastguard Worker                               dest='build',
409*3c875a21SAndroid Build Coastguard Worker                               help='Skip building')
410*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--no-verify',
411*3c875a21SAndroid Build Coastguard Worker                               action='store_true',
412*3c875a21SAndroid Build Coastguard Worker                               help='Pass --no-verify to git commit')
413*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--bug',
414*3c875a21SAndroid Build Coastguard Worker                               type=int,
415*3c875a21SAndroid Build Coastguard Worker                               help='Bug number for this update')
416*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--custom-version',
417*3c875a21SAndroid Build Coastguard Worker                               type=str,
418*3c875a21SAndroid Build Coastguard Worker                               help='Custom version we want to upgrade to.')
419*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--remote-name',
420*3c875a21SAndroid Build Coastguard Worker                               default='aosp',
421*3c875a21SAndroid Build Coastguard Worker                               required=False,
422*3c875a21SAndroid Build Coastguard Worker                               help='Upstream remote name.')
423*3c875a21SAndroid Build Coastguard Worker    update_parser.add_argument('--exclude',
424*3c875a21SAndroid Build Coastguard Worker                               action='append',
425*3c875a21SAndroid Build Coastguard Worker                               help='Names of projects to exclude. '
426*3c875a21SAndroid Build Coastguard Worker                               'These are just the final part of the path '
427*3c875a21SAndroid Build Coastguard Worker                               'with no directories.')
428*3c875a21SAndroid Build Coastguard Worker    update_parser.set_defaults(func=update)
429*3c875a21SAndroid Build Coastguard Worker
430*3c875a21SAndroid Build Coastguard Worker    return parser.parse_args()
431*3c875a21SAndroid Build Coastguard Worker
432*3c875a21SAndroid Build Coastguard Worker
433*3c875a21SAndroid Build Coastguard Workerdef main() -> None:
434*3c875a21SAndroid Build Coastguard Worker    """The main entry."""
435*3c875a21SAndroid Build Coastguard Worker
436*3c875a21SAndroid Build Coastguard Worker    args = parse_args()
437*3c875a21SAndroid Build Coastguard Worker    args.func(args)
438*3c875a21SAndroid Build Coastguard Worker
439*3c875a21SAndroid Build Coastguard Worker
440*3c875a21SAndroid Build Coastguard Workerif __name__ == '__main__':
441*3c875a21SAndroid Build Coastguard Worker    main()
442