chromium/third_party/android_deps/fetch_common.py

# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""3pp fetch.py shared logic for libs."""

import argparse
import dataclasses
import json
import os
import re
import typing
import urllib.request


@dataclasses.dataclass
class Spec:
    repo_url: str
    group_name: str
    module_name: str
    file_ext: str
    patch_version: str = ''
    version_override: typing.Optional[str] = None
    version_filter: typing.Optional[str] = None


def do_latest(spec):
    if spec.version_override is not None:
        print(spec.version_override + f'.{spec.patch_version}')
        return
    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
        spec.repo_url, spec.group_name, spec.module_name)
    metadata = urllib.request.urlopen(maven_metadata_url).read().decode(
        'utf-8')
    # Do not parse xml with the python included parser since it is susceptible
    # to maliciously crafted xmls. Only use regular expression parsing to be
    # safe. RE should be enough to handle what we need to extract.
    versions = re.findall('<version>([^<]+)</version>', metadata)
    if m := re.search('<latest>([^<]+)</latest>', metadata):
        versions.append(m.group(1))

    # If no latest info was found just hope the versions are sorted and the
    # last one is the latest (as is commonly the case).
    if spec.version_filter is not None:
        r = re.compile(spec.version_filter)
        versions = [v for v in versions if r.search(v)]
    latest = versions[-1]
    print(latest + f'.{spec.patch_version}')


def get_download_url(version, spec):
    # Remove the patch version when getting the download url
    version_no_patch, patch = version.rsplit('.', 1)
    if patch.startswith('cr'):
        version = version_no_patch
    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(spec.repo_url,
                                                    spec.group_name,
                                                    spec.module_name, version,
                                                    spec.file_ext)
    file_name = file_url.rsplit('/', 1)[-1]

    partial_manifest = {
        'url': [file_url],
        'name': [file_name],
        'ext': '.' + spec.file_ext,
    }
    print(json.dumps(partial_manifest))


def main(spec):
    ap = argparse.ArgumentParser()
    sub = ap.add_subparsers(required=True)

    latest = sub.add_parser('latest')
    latest.set_defaults(func=lambda _opts: do_latest(spec))

    download = sub.add_parser('get_url')
    download.set_defaults(
        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION'], spec))

    opts = ap.parse_args()
    opts.func(opts)