1# Copyright (C) 2018 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Helper functions for updaters.""" 15 16from collections.abc import Sequence 17import os 18import re 19import subprocess 20import sys 21from pathlib import Path 22from typing import List, Tuple, Type 23 24from base_updater import Updater 25import fileutils 26# pylint: disable=import-error 27import metadata_pb2 # type: ignore 28 29 30def create_updater(metadata: metadata_pb2.MetaData, proj_path: Path, 31 updaters: List[Type[Updater]]) -> Updater: 32 """Creates corresponding updater object for a project. 33 34 Args: 35 metadata: Parsed proto for METADATA file. 36 proj_path: Absolute path for the project. 37 38 Returns: 39 An updater object. 40 41 Raises: 42 ValueError: Occurred when there's no updater for all urls. 43 """ 44 for identifier in metadata.third_party.identifier: 45 if identifier.type.lower() != 'homepage': 46 for updater_cls in updaters: 47 updater = updater_cls(proj_path, identifier, metadata.third_party.version) 48 if updater.is_supported_url(): 49 return updater 50 51 raise ValueError('No supported URL.') 52 53 54def replace_package(source_dir, target_dir, temp_file=None) -> None: 55 """Invokes a shell script to prepare and update a project. 56 57 Args: 58 source_dir: Path to the new downloaded and extracted package. 59 target_dir: The path to the project in Android source tree. 60 """ 61 62 print(f'Updating {target_dir} using {source_dir}.') 63 script_path = os.path.join(os.path.dirname(sys.argv[0]), 64 'update_package.sh') 65 subprocess.check_call(['bash', script_path, source_dir, target_dir, 66 "" if temp_file is None else temp_file]) 67 68 69def run_post_update(source_dir: Path, target_dir: Path) -> None: 70 """ 71 source_dir: Path to the new downloaded and extracted package. 72 target_dir: The path to the project in Android source tree. 73 """ 74 post_update_path = os.path.join(source_dir, 'post_update.sh') 75 if os.path.isfile(post_update_path): 76 print("Running post update script") 77 cmd: Sequence[str | Path] = ['bash', post_update_path, source_dir, target_dir] 78 print(f'Running {post_update_path}') 79 subprocess.check_call(cmd) 80 81 82VERSION_SPLITTER_PATTERN: str = r'[\.\-_]' 83VERSION_PATTERN: str = (r'^(?P<prefix>[^\d]*)' + r'(?P<version>\d+(' + 84 VERSION_SPLITTER_PATTERN + r'\d+)*)' + 85 r'(?P<suffix>.*)$') 86TAG_PATTERN: str = r".*refs/tags/(?P<tag>[^\^]*).*" 87TAG_RE: re.Pattern = re.compile(TAG_PATTERN) 88VERSION_RE: re.Pattern = re.compile(VERSION_PATTERN) 89VERSION_SPLITTER_RE: re.Pattern = re.compile(VERSION_SPLITTER_PATTERN) 90 91ParsedVersion = Tuple[List[int], str, str] 92 93 94def _parse_version(version: str) -> ParsedVersion: 95 match = VERSION_RE.match(version) 96 if match is None: 97 raise ValueError('Invalid version.') 98 try: 99 prefix, version, suffix = match.group('prefix', 'version', 'suffix') 100 versions = [int(v) for v in VERSION_SPLITTER_RE.split(version)] 101 return versions, str(prefix), str(suffix) 102 except IndexError: 103 # pylint: disable=raise-missing-from 104 raise ValueError('Invalid version.') 105 106 107def _match_and_get_version(old_ver: ParsedVersion, 108 version: str) -> Tuple[bool, bool, List[int]]: 109 try: 110 new_ver = _parse_version(version) 111 except ValueError: 112 return False, False, [] 113 114 right_format = new_ver[1:] == old_ver[1:] 115 right_length = len(new_ver[0]) == len(old_ver[0]) 116 117 return right_format, right_length, new_ver[0] 118 119 120def get_latest_stable_release_tag(current_version: str, version_list: List[str]) -> str: 121 """Gets the latest version name from a list of versions. 122 123 The new version must have the same prefix and suffix with old version. 124 If no matched version is newer, current version name will be returned. 125 """ 126 parsed_current_ver = _parse_version(current_version) 127 128 latest = max( 129 version_list, 130 key=lambda ver: _match_and_get_version(parsed_current_ver, ver), 131 default=None) 132 if not latest: 133 raise ValueError('No matching version.') 134 return latest 135 136 137def parse_remote_tag(line: str) -> str: 138 if (m := TAG_RE.match(line)) is not None: 139 return m.group("tag") 140 raise ValueError(f"Could not parse tag from {line}") 141 142 143def build(proj_path: Path) -> None: 144 tree = fileutils.find_tree_containing(proj_path) 145 cmd = [ 146 str(tree / 'build/soong/soong_ui.bash'), 147 "--build-mode", 148 "--modules-in-a-dir-no-deps", 149 f"--dir={str(proj_path)}", 150 ] 151 print('Building...') 152 subprocess.run(cmd, check=True, text=True) 153