# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper functions for updaters."""
import os
import re
import subprocess
import sys
from collections.abc
import Sequence
from pathlib
import Path
from typing
import List, Tuple, Type
# pylint: disable=import-error
import metadata_pb2
# type: ignore
import fileutils
from base_updater
import Updater
def create_updater(metadata: metadata_pb2.MetaData, proj_path: Path,
updaters: List[Type[Updater]]) -> Updater:
"""Creates corresponding updater object for a project.
Args:
metadata: Parsed proto
for METADATA file.
proj_path: Absolute path
for the project.
Returns:
An updater object.
Raises:
ValueError: Occurred when there
's no updater for all urls.
"""
for identifier
in metadata.third_party.identifier:
if identifier.type.lower() !=
'homepage':
for updater_cls
in updaters:
updater = updater_cls(proj_path, identifier, metadata.third_party.version)
if updater.is_supported_url():
return updater
raise ValueError(
'No supported URL.')
def replace_package(source_dir, target_dir, temp_file=
None) ->
None:
"""Invokes a shell script to prepare and update a project.
Args:
source_dir: Path to the new downloaded
and extracted package.
target_dir: The path to the project
in Android source tree.
"""
print(f
'Updating {target_dir} using {source_dir}.')
script_path = os.path.join(os.path.dirname(sys.argv[
0]),
'update_package.sh')
subprocess.check_call([
'bash', script_path, source_dir, target_dir,
"" if temp_file
is None else temp_file])
def run_post_update(proj_path: Path, old_project_path: Path |
None =
None) ->
None:
""" Runs the post_update.sh script if exists.
Args:
proj_path: The path to the project
in Android source tree. Note that this
project
is now updated.
old_project_path: Temp dir to where it stored the old version after
upgrading the project.
"""
post_update_path = os.path.join(proj_path,
'post_update.sh')
if os.path.isfile(post_update_path):
print(f
"Running post update script {post_update_path}")
cmd: Sequence[str | Path]
if old_project_path:
cmd = [
'bash', post_update_path, proj_path, old_project_path]
else:
cmd = [
'bash', post_update_path, proj_path]
subprocess.check_call(cmd)
VERSION_SPLITTER_PATTERN: str = r
'[\.\-_]'
VERSION_PATTERN: str = (r
'^(?P<prefix>[^\d]*)' + r
'(?P<version>\d+(' +
VERSION_SPLITTER_PATTERN + r
'\d+)*)' +
r
'(?P<suffix>.*)$')
TAG_PATTERN: str = r
".*refs/tags/(?P<tag>[^\^]*).*"
TAG_RE: re.Pattern = re.compile(TAG_PATTERN)
VERSION_RE: re.Pattern = re.compile(VERSION_PATTERN)
VERSION_SPLITTER_RE: re.Pattern = re.compile(VERSION_SPLITTER_PATTERN)
ParsedVersion = Tuple[List[int], str, str]
def _parse_version(version: str) -> ParsedVersion:
match = VERSION_RE.match(version)
if match
is None:
raise ValueError(
'Invalid version.')
try:
prefix, version, suffix = match.group(
'prefix',
'version',
'suffix')
versions = [int(v)
for v
in VERSION_SPLITTER_RE.split(version)]
return versions, str(prefix), str(suffix)
except IndexError:
# pylint: disable=raise-missing-from
raise ValueError(
'Invalid version.')
def _match_and_get_version(old_ver: ParsedVersion,
version: str) -> Tuple[bool, bool, List[int]]:
try:
new_ver = _parse_version(version)
except ValueError:
return False,
False, []
right_format = new_ver[
1:] == old_ver[
1:]
right_length = len(new_ver[
0]) == len(old_ver[
0])
return right_format, right_length, new_ver[
0]
def get_latest_stable_release_tag(current_version: str, version_list: List[str]) -> str
:
"""Gets the latest version name from a list of versions.
The new version must have the same prefix and suffix with old version.
If no matched version is newer, current version name will be returned.
"""
parsed_current_ver = _parse_version(current_version)
latest = max(
version_list,
key=lambda ver: _match_and_get_version(parsed_current_ver, ver),
default=None)
if not latest:
raise ValueError('No matching version.')
return latest
def parse_remote_tag(line: str) -> str:
if (m := TAG_RE.match(line)) is not None:
return m.group("tag")
raise ValueError(f"Could not parse tag from {line}")
def build(proj_path: Path) -> None:
tree = fileutils.find_tree_containing(proj_path)
cmd = [
str(tree / 'build/soong/soong_ui.bash'),
"--build-mode",
"--modules-in-a-dir",
f"--dir={str(proj_path)}",
]
print('Building...')
subprocess.run(cmd, check=True, text=True)