blob: b4098615d1356404265856c78510d59106d1f9b6 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A script to generate build.gradle from template and run fetch_all.py
More specifically, to generate build.gradle:
- It downloads the BUILD_INFO file for the latest androidx snapshot from
https://androidx.dev/snapshots/
- It replaces {{androidx_repository_url}} with the URL for the latest snapshot
- For each dependency, it looks up the version in the BUILD_INFO file and
substitutes the version into {{androidx_dependency_version}}.
"""
import contextlib
import json
import os
import re
import shutil
import subprocess
import tempfile
import urllib
from urllib import request
_ANDROIDX_PATH = os.path.normpath(os.path.join(__file__, '..'))
_FETCH_ALL_PATH = os.path.normpath(
os.path.join(_ANDROIDX_PATH, '..', 'android_deps', 'fetch_all.py'))
# URL to BUILD_INFO in latest androidx snapshot.
_ANDROIDX_LATEST_SNAPSHOT_BUILD_INFO_URL = 'https://androidx.dev/snapshots/latest/artifacts/BUILD_INFO'
def _parse_dir_list(dir_list):
"""Computes 'library_group:library_name'->library_version mapping.
Args:
dir_list: List of androidx library directories.
"""
dependency_version_map = dict()
for dir_entry in dir_list:
stripped_dir = dir_entry.strip()
if not stripped_dir.startswith('repository/androidx/'):
continue
dir_components = stripped_dir.split('/')
# Expected format:
# "repository/androidx/library_group/library_name/library_version/pom_or_jar"
if len(dir_components) < 6:
continue
dependency_module = 'androidx.{}:{}'.format(dir_components[2],
dir_components[3])
if dependency_module not in dependency_version_map:
dependency_version_map[dependency_module] = dir_components[4]
return dependency_version_map
def _compute_replacement(dependency_version_map, androidx_repository_url,
line):
"""Computes output line for build.gradle from build.gradle.template line.
Replaces {{android_repository_url}} and {{androidx_dependency_version}}.
Args:
dependency_version_map: An "dependency_group:dependency_name"->dependency_version mapping.
androidx_repository_url: URL of the maven repository.
line: Input line from the build.gradle.template.
"""
line = line.replace('{{androidx_repository_url}}', androidx_repository_url)
match = re.search(r'"(\S+):{{androidx_dependency_version}}"', line)
if not match:
return line
version = dependency_version_map.get(match.group(1))
if not version:
return line
return line.replace('{{androidx_dependency_version}}', version)
@contextlib.contextmanager
def _build_dir():
dirname = tempfile.mkdtemp()
try:
yield dirname
finally:
shutil.rmtree(dirname)
def _download_and_parse_build_info():
"""Downloads and parses BUILD_INFO file."""
with _build_dir() as build_dir:
androidx_build_info_response = request.urlopen(
_ANDROIDX_LATEST_SNAPSHOT_BUILD_INFO_URL)
androidx_build_info_path = os.path.join(build_dir, 'BUILD_INFO')
with open(androidx_build_info_path, 'w') as f:
f.write(androidx_build_info_response.read().decode('utf-8'))
# Compute repository URL from resolved BUILD_INFO url in case 'latest' redirect changes.
androidx_snapshot_repository_url = (
androidx_build_info_response.geturl().rsplit('/', 1)[0] +
'/repository')
with open(androidx_build_info_path, 'r') as f:
build_info_dict = json.loads(f.read())
dir_list = build_info_dict['target']['dir_list']
dependency_version_map = _parse_dir_list(dir_list)
return (dependency_version_map, androidx_snapshot_repository_url)
def _process_build_gradle(dependency_version_map, androidx_repository_url):
"""Generates build.gradle from template.
Args:
dependency_version_map: An "dependency_group:dependency_name"->dependency_version mapping.
androidx_repository_url: URL of the maven repository.
"""
build_gradle_template_path = os.path.join(_ANDROIDX_PATH,
'build.gradle.template')
build_gradle_out_path = os.path.join(_ANDROIDX_PATH, 'build.gradle')
# |build_gradle_out_path| is not deleted after script has finished running. The file is in
# .gitignore and thus will be excluded from uploaded CLs.
with open(build_gradle_template_path, 'r') as template_f, \
open(build_gradle_out_path, 'w') as out:
for template_line in template_f:
replacement = _compute_replacement(dependency_version_map,
androidx_repository_url,
template_line)
out.write(replacement)
def _extract_files_from_yaml(yaml_path):
"""Extracts '- file' file listings from yaml file."""
out = None
with open(yaml_path, 'r') as f:
for line in f.readlines():
line = line.rstrip('\n')
if line == 'data:':
out = []
continue
if out is not None:
if not line.startswith('- file:'):
raise Exception(
'{} has unsupported attributes. Only \'- file\' is supported'
.format(yaml_path))
out.append(line.rsplit(' ', 1)[1])
if not out:
raise Exception('{} does not have \'data\' section.'.format(yaml_path))
return out
def _write_cipd_yaml(libs_dir, cipd_yaml_path):
"""Writes cipd.yaml file at the passed-in path."""
lib_dirs = os.listdir(libs_dir)
if not lib_dirs:
raise Exception('No generated libraries in {}'.format(libs_dir))
data_files = ['BUILD.gn', 'additional_readme_paths.json']
for lib_dir in lib_dirs:
abs_lib_dir = os.path.join(libs_dir, lib_dir)
androidx_rel_lib_dir = os.path.relpath(abs_lib_dir, _ANDROIDX_PATH)
if not os.path.isdir(abs_lib_dir):
continue
lib_files = os.listdir(abs_lib_dir)
if not 'cipd.yaml' in lib_files:
continue
if not 'README.chromium' in lib_files:
raise Exception('README.chromium not in {}'.format(abs_lib_dir))
if not 'LICENSE' in lib_files:
raise Exception('LICENSE not in {}'.format(abs_lib_dir))
data_files.append(os.path.join(androidx_rel_lib_dir,
'README.chromium'))
data_files.append(os.path.join(androidx_rel_lib_dir, 'LICENSE'))
_rel_extracted_files = _extract_files_from_yaml(
os.path.join(abs_lib_dir, 'cipd.yaml'))
data_files.extend(
os.path.join(androidx_rel_lib_dir, f)
for f in _rel_extracted_files)
contents = [
'# Copyright 2020 The Chromium Authors. All rights reserved.',
'# Use of this source code is governed by a BSD-style license that can be',
'# found in the LICENSE file.',
'package: chromium/third_party/androidx', 'description: androidx',
'data:'
]
contents.extend('- file: ' + f for f in data_files)
with open(cipd_yaml_path, 'w') as out:
out.write('\n'.join(contents))
def main():
libs_dir = os.path.join(_ANDROIDX_PATH, 'libs')
# Let recipe delete contents of lib directory because it has API to retry
# directory deletion if the first deletion attempt does not work.
if os.path.exists(libs_dir) and os.listdir(libs_dir):
raise Exception('Recipe did not empty \'libs\' directory.')
dependency_version_map, androidx_snapshot_repository_url = (
_download_and_parse_build_info())
_process_build_gradle(dependency_version_map,
androidx_snapshot_repository_url)
fetch_all_cmd = [
_FETCH_ALL_PATH, '--android-deps-dir', _ANDROIDX_PATH,
'--ignore-vulnerabilities'
]
subprocess.run(fetch_all_cmd, check=True)
_write_cipd_yaml(libs_dir, os.path.join(_ANDROIDX_PATH, 'cipd.yaml'))
if __name__ == '__main__':
main()