blob: c15e92dbb476e29e9ab12f66deb1b6b55fe5a0fe [file] [log] [blame]
Haibo Huang329e6812020-05-29 14:12:20 -07001#
(raulenrique)dfdda472018-06-04 12:02:29 -07002# Copyright (C) 2018 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""A commandline tool to check and update packages in external/
16
17Example usage:
18updater.sh checkall
19updater.sh update kotlinc
Chih-Hung Hsiehdd1915d2020-09-29 14:22:12 -070020updater.sh update --refresh --keep_date rust/crates/libc
(raulenrique)dfdda472018-06-04 12:02:29 -070021"""
22
23import argparse
Haibo Huang329e6812020-05-29 14:12:20 -070024import enum
Joel Galenson9ca1a862021-05-19 10:58:20 -070025import glob
Haibo Huang39aaab62019-01-25 12:23:03 -080026import json
(raulenrique)dfdda472018-06-04 12:02:29 -070027import os
Haibo Huang57a3bdc2019-07-09 17:08:14 -070028import sys
Haibo Huang24950e72018-06-29 14:53:39 -070029import subprocess
Haibo Huang39aaab62019-01-25 12:23:03 -080030import time
Haibo Huang329e6812020-05-29 14:12:20 -070031from typing import Dict, Iterator, List, Union, Tuple, Type
32from pathlib import Path
(raulenrique)dfdda472018-06-04 12:02:29 -070033
Haibo Huang329e6812020-05-29 14:12:20 -070034from base_updater import Updater
Chih-Hung Hsieh11cf9962020-03-19 02:02:25 -070035from crates_updater import CratesUpdater
Haibo Huang24950e72018-06-29 14:53:39 -070036from git_updater import GitUpdater
(raulenrique)dfdda472018-06-04 12:02:29 -070037from github_archive_updater import GithubArchiveUpdater
Haibo Huangc3c0cd42019-01-29 15:24:45 -080038import fileutils
39import git_utils
ThiƩbaud Weksteen4ac289b2020-09-28 15:23:29 +020040# pylint: disable=import-error
Haibo Huang329e6812020-05-29 14:12:20 -070041import metadata_pb2 # type: ignore
(raulenrique)dfdda472018-06-04 12:02:29 -070042import updater_utils
43
Haibo Huang329e6812020-05-29 14:12:20 -070044UPDATERS: List[Type[Updater]] = [
45 CratesUpdater,
46 GithubArchiveUpdater,
47 GitUpdater,
48]
(raulenrique)dfdda472018-06-04 12:02:29 -070049
Haibo Huang329e6812020-05-29 14:12:20 -070050TMP_BRANCH_NAME = 'tmp_auto_upgrade'
Haibo Huang57a3bdc2019-07-09 17:08:14 -070051USE_COLOR = sys.stdout.isatty()
(raulenrique)dfdda472018-06-04 12:02:29 -070052
Haibo Huang329e6812020-05-29 14:12:20 -070053
54@enum.unique
55class Color(enum.Enum):
56 """Colors for output to console."""
57 FRESH = '\x1b[32m'
58 STALE = '\x1b[31;1m'
59 ERROR = '\x1b[31m'
60
61
62END_COLOR = '\033[0m'
63
64
Haibo Huanga08fb602020-05-29 16:24:13 -070065def color_string(string: str, color: Color) -> str:
(raulenrique)dfdda472018-06-04 12:02:29 -070066 """Changes the color of a string when print to terminal."""
Haibo Huang57a3bdc2019-07-09 17:08:14 -070067 if not USE_COLOR:
68 return string
Haibo Huang329e6812020-05-29 14:12:20 -070069 return color.value + string + END_COLOR
(raulenrique)dfdda472018-06-04 12:02:29 -070070
71
Haibo Huang329e6812020-05-29 14:12:20 -070072def build_updater(proj_path: Path) -> Tuple[Updater, metadata_pb2.MetaData]:
(raulenrique)dfdda472018-06-04 12:02:29 -070073 """Build updater for a project specified by proj_path.
74
75 Reads and parses METADATA file. And builds updater based on the information.
76
77 Args:
78 proj_path: Absolute or relative path to the project.
79
80 Returns:
81 The updater object built. None if there's any error.
82 """
83
84 proj_path = fileutils.get_absolute_project_path(proj_path)
Haibo Huang329e6812020-05-29 14:12:20 -070085 metadata = fileutils.read_metadata(proj_path)
86 updater = updater_utils.create_updater(metadata, proj_path, UPDATERS)
87 return (updater, metadata)
(raulenrique)dfdda472018-06-04 12:02:29 -070088
89
Haibo Huang329e6812020-05-29 14:12:20 -070090def _do_update(args: argparse.Namespace, updater: Updater,
91 metadata: metadata_pb2.MetaData) -> None:
92 full_path = updater.project_path
Haibo Huang39aaab62019-01-25 12:23:03 -080093
Haibo Huangc3c0cd42019-01-29 15:24:45 -080094 if args.branch_and_commit:
95 git_utils.checkout(full_path, args.remote_name + '/master')
Haibo Huangc3c0cd42019-01-29 15:24:45 -080096 git_utils.start_branch(full_path, TMP_BRANCH_NAME)
97
Joel Galenson40a5a4a2021-05-20 09:50:44 -070098 try:
99 updater.update()
Haibo Huangc3c0cd42019-01-29 15:24:45 -0800100
Joel Galenson40a5a4a2021-05-20 09:50:44 -0700101 updated_metadata = metadata_pb2.MetaData()
102 updated_metadata.CopyFrom(metadata)
103 updated_metadata.third_party.version = updater.latest_version
104 for metadata_url in updated_metadata.third_party.url:
105 if metadata_url == updater.current_url:
106 metadata_url.CopyFrom(updater.latest_url)
107 # For Rust crates, replace GIT url with ARCHIVE url
108 if isinstance(updater, CratesUpdater):
109 updater.update_metadata(updated_metadata, full_path)
110 fileutils.write_metadata(full_path, updated_metadata, args.keep_date)
111 git_utils.add_file(full_path, 'METADATA')
Haibo Huang329e6812020-05-29 14:12:20 -0700112
Joel Galenson40a5a4a2021-05-20 09:50:44 -0700113 if args.branch_and_commit:
114 rel_proj_path = fileutils.get_relative_project_path(full_path)
115 msg = 'Upgrade {} to {}\n\nTest: make\n'.format(
116 rel_proj_path, updater.latest_version)
Joel Galenson58f0ddf2021-07-14 14:47:40 -0700117 git_utils.remove_gitmodules(full_path)
Joel Galenson40a5a4a2021-05-20 09:50:44 -0700118 git_utils.add_file(full_path, '*')
119 git_utils.commit(full_path, msg)
120 except Exception as err:
121 if updater.rollback():
122 print('Rolled back.')
123 raise err
Haibo Huangc3c0cd42019-01-29 15:24:45 -0800124
125 if args.push_change:
Joel Galenson58eeae72021-04-07 12:44:31 -0700126 git_utils.push(full_path, args.remote_name, updater.has_errors)
(raulenrique)dfdda472018-06-04 12:02:29 -0700127
Haibo Huang39287b12019-01-30 15:48:27 -0800128 if args.branch_and_commit:
Haibo Huang38dda432019-02-01 15:45:35 -0800129 git_utils.checkout(full_path, args.remote_name + '/master')
Haibo Huang39287b12019-01-30 15:48:27 -0800130
(raulenrique)dfdda472018-06-04 12:02:29 -0700131
Haibo Huang329e6812020-05-29 14:12:20 -0700132def check_and_update(args: argparse.Namespace,
133 proj_path: Path,
134 update_lib=False) -> Union[Updater, str]:
135 """Checks updates for a project. Prints result on console.
136
137 Args:
138 args: commandline arguments
139 proj_path: Absolute or relative path to the project.
140 update: If false, will only check for new version, but not update.
141 """
142
143 try:
144 rel_proj_path = fileutils.get_relative_project_path(proj_path)
145 print(f'Checking {rel_proj_path}. ', end='')
146 updater, metadata = build_updater(proj_path)
147 updater.check()
148
149 current_ver = updater.current_version
150 latest_ver = updater.latest_version
151 print('Current version: {}. Latest version: {}'.format(
152 current_ver, latest_ver),
153 end='')
154
155 has_new_version = current_ver != latest_ver
156 if has_new_version:
157 print(color_string(' Out of date!', Color.STALE))
158 else:
159 print(color_string(' Up to date.', Color.FRESH))
160
Chih-Hung Hsiehdd1915d2020-09-29 14:22:12 -0700161 if update_lib and args.refresh:
162 print('Refreshing the current version')
163 updater.use_current_as_latest()
164 if update_lib and (has_new_version or args.force or args.refresh):
Haibo Huang329e6812020-05-29 14:12:20 -0700165 _do_update(args, updater, metadata)
166 return updater
ThiƩbaud Weksteen4ac289b2020-09-28 15:23:29 +0200167 # pylint: disable=broad-except
Haibo Huang329e6812020-05-29 14:12:20 -0700168 except Exception as err:
169 print('{} {}.'.format(color_string('Failed.', Color.ERROR), err))
170 return str(err)
171
172
Joel Galenson9ca1a862021-05-19 10:58:20 -0700173def check_and_update_path(args: argparse.Namespace, paths: Iterator[str],
174 update_lib: bool,
175 delay: int) -> Dict[str, Dict[str, str]]:
Haibo Huang329e6812020-05-29 14:12:20 -0700176 results = {}
177 for path in paths:
178 res = {}
Joel Galenson9ca1a862021-05-19 10:58:20 -0700179 updater = check_and_update(args, Path(path), update_lib)
Haibo Huang329e6812020-05-29 14:12:20 -0700180 if isinstance(updater, str):
181 res['error'] = updater
182 else:
183 res['current'] = updater.current_version
184 res['latest'] = updater.latest_version
185 relative_path = fileutils.get_relative_project_path(Path(path))
186 results[str(relative_path)] = res
187 time.sleep(delay)
188 return results
189
190
191def _list_all_metadata() -> Iterator[str]:
192 for path, dirs, files in os.walk(fileutils.EXTERNAL_PATH):
193 if fileutils.METADATA_FILENAME in files:
194 # Skip sub directories.
195 dirs[:] = []
196 yield path
197 dirs.sort(key=lambda d: d.lower())
198
199
Joel Galenson0c510252021-09-02 13:58:51 -0700200def get_paths(paths: List[str]) -> List[str]:
201 """Expand paths via globs."""
202 # We want to use glob to get all the paths, so we first convert to absolute.
203 abs_paths = [fileutils.get_absolute_project_path(Path(path))
204 for path in paths]
Joel Galenson35646582021-09-30 13:32:19 -0700205 result = [path for abs_path in abs_paths
206 for path in sorted(glob.glob(str(abs_path)))]
207 if paths and not result:
208 print('Could not find any valid paths in %s' % str(paths))
209 return result
Joel Galenson0c510252021-09-02 13:58:51 -0700210
211
212def write_json(json_file: str, results: Dict[str, Dict[str, str]]) -> List[str]:
213 """Output a JSON report."""
214 with Path(json_file).open('w') as res_file:
215 json.dump(results, res_file, sort_keys=True, indent=4)
216
217
Haibo Huanga08fb602020-05-29 16:24:13 -0700218def check(args: argparse.Namespace) -> None:
Haibo Huang329e6812020-05-29 14:12:20 -0700219 """Handler for check command."""
Joel Galenson0c510252021-09-02 13:58:51 -0700220 paths = _list_all_metadata() if args.all else get_paths(args.paths)
Joel Galenson9ca1a862021-05-19 10:58:20 -0700221 results = check_and_update_path(args, paths, False, args.delay)
Haibo Huang329e6812020-05-29 14:12:20 -0700222
223 if args.json_output is not None:
Joel Galenson0c510252021-09-02 13:58:51 -0700224 write_json(args.json_output, results)
Haibo Huang329e6812020-05-29 14:12:20 -0700225
226
227def update(args: argparse.Namespace) -> None:
228 """Handler for update command."""
Joel Galenson0c510252021-09-02 13:58:51 -0700229 all_paths = get_paths(args.paths)
Joel Galenson473af4f2021-05-19 11:15:37 -0700230 # Remove excluded paths.
231 excludes = set() if args.exclude is None else set(args.exclude)
232 filtered_paths = [path for path in all_paths
233 if not Path(path).name in excludes]
Joel Galenson9ca1a862021-05-19 10:58:20 -0700234 # Now we can update each path.
Joel Galenson473af4f2021-05-19 11:15:37 -0700235 results = check_and_update_path(args, filtered_paths, True, 0)
Joel Galenson9ca1a862021-05-19 10:58:20 -0700236
237 if args.json_output is not None:
Joel Galenson0c510252021-09-02 13:58:51 -0700238 write_json(args.json_output, results)
Haibo Huang329e6812020-05-29 14:12:20 -0700239
240
241def parse_args() -> argparse.Namespace:
(raulenrique)dfdda472018-06-04 12:02:29 -0700242 """Parses commandline arguments."""
243
244 parser = argparse.ArgumentParser(
245 description='Check updates for third party projects in external/.')
246 subparsers = parser.add_subparsers(dest='cmd')
247 subparsers.required = True
248
249 # Creates parser for check command.
Haibo Huang329e6812020-05-29 14:12:20 -0700250 check_parser = subparsers.add_parser('check',
251 help='Check update for one project.')
(raulenrique)dfdda472018-06-04 12:02:29 -0700252 check_parser.add_argument(
Haibo Huang329e6812020-05-29 14:12:20 -0700253 'paths',
254 nargs='*',
Haibo Huang39aaab62019-01-25 12:23:03 -0800255 help='Paths of the project. '
(raulenrique)dfdda472018-06-04 12:02:29 -0700256 'Relative paths will be resolved from external/.')
Haibo Huang329e6812020-05-29 14:12:20 -0700257 check_parser.add_argument('--json_output',
258 help='Path of a json file to write result to.')
Haibo Huang39aaab62019-01-25 12:23:03 -0800259 check_parser.add_argument(
Haibo Huang329e6812020-05-29 14:12:20 -0700260 '--all',
261 action='store_true',
Haibo Huang39aaab62019-01-25 12:23:03 -0800262 help='If set, check updates for all supported projects.')
263 check_parser.add_argument(
Haibo Huang329e6812020-05-29 14:12:20 -0700264 '--delay',
265 default=0,
266 type=int,
Haibo Huang39aaab62019-01-25 12:23:03 -0800267 help='Time in seconds to wait between checking two projects.')
(raulenrique)dfdda472018-06-04 12:02:29 -0700268 check_parser.set_defaults(func=check)
269
(raulenrique)dfdda472018-06-04 12:02:29 -0700270 # Creates parser for update command.
271 update_parser = subparsers.add_parser('update', help='Update one project.')
272 update_parser.add_argument(
Joel Galenson9ca1a862021-05-19 10:58:20 -0700273 'paths',
274 nargs='*',
275 help='Paths of the project as globs. '
(raulenrique)dfdda472018-06-04 12:02:29 -0700276 'Relative paths will be resolved from external/.')
Joel Galenson9ca1a862021-05-19 10:58:20 -0700277 update_parser.add_argument('--json_output',
278 help='Path of a json file to write result to.')
(raulenrique)dfdda472018-06-04 12:02:29 -0700279 update_parser.add_argument(
280 '--force',
281 help='Run update even if there\'s no new version.',
282 action='store_true')
Chih-Hung Hsiehdd1915d2020-09-29 14:22:12 -0700283 update_parser.add_argument(
284 '--refresh',
285 help='Run update and refresh to the current version.',
286 action='store_true')
287 update_parser.add_argument(
288 '--keep_date',
289 help='Run update and do not change date in METADATA.',
290 action='store_true')
Haibo Huang329e6812020-05-29 14:12:20 -0700291 update_parser.add_argument('--branch_and_commit',
292 action='store_true',
293 help='Starts a new branch and commit changes.')
294 update_parser.add_argument('--push_change',
295 action='store_true',
296 help='Pushes change to Gerrit.')
297 update_parser.add_argument('--remote_name',
298 default='aosp',
299 required=False,
300 help='Upstream remote name.')
Joel Galenson473af4f2021-05-19 11:15:37 -0700301 update_parser.add_argument('--exclude',
302 action='append',
303 help='Names of projects to exclude. '
304 'These are just the final part of the path '
305 'with no directories.')
(raulenrique)dfdda472018-06-04 12:02:29 -0700306 update_parser.set_defaults(func=update)
307
308 return parser.parse_args()
309
310
Haibo Huanga08fb602020-05-29 16:24:13 -0700311def main() -> None:
(raulenrique)dfdda472018-06-04 12:02:29 -0700312 """The main entry."""
313
314 args = parse_args()
315 args.func(args)
316
317
318if __name__ == '__main__':
319 main()