| #!/usr/bin/env python3 |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Print a version and prepare the artifacts for "avd" CIPD package. |
| |
| This script has the following two functions: |
| * latest: Prints a version for 3pp framework to decide if creating a new CIPD |
| package or not. The version value is calculated by computing the sha256 |
| hash of all the dependent artifacts. In this way, it is guaranteed that a |
| new version will be generated if any of the dependencies get changed. |
| * checkout: Prepares the dependent artifacts for 3pp framework to package and |
| upload to CIPD by copying all the dependencies to a temporary checkout path |
| created by 3pp framework. |
| |
| This script is normally called by 3pp framework from chromium_3pp recipe module. |
| |
| """ |
| |
| import argparse |
| import hashlib |
| import os |
| import re |
| import shutil |
| import subprocess |
| |
| _SRC_PATH = os.path.abspath( |
| os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, |
| os.path.pardir, os.path.pardir)) |
| # The src-relative files and dirs we would like to include in the CIPD. |
| _BASE_DEPS = [ |
| # vpython, binaries and avd configs used by //tools/android/avd/avd.py |
| '.vpython3', |
| 'third_party/android_sdk/public/cmdline-tools/', |
| 'third_party/android_sdk/public/platform-tools/', |
| 'tools/android/avd/proto/', |
| |
| # Should be the same as python_library("devil_chromium_py") in |
| # //build/android/BUILD.gn |
| 'build/android/devil_chromium.json', |
| 'third_party/catapult/devil/devil/devil_dependencies.json', |
| 'third_party/catapult/third_party/gsutil/', |
| |
| # Read by gn_helpers.BuildWithChromium() |
| # Needed to recognize the adb binary in third_party/android_sdk |
| 'build/config/gclient_args.gni', |
| ] |
| |
| |
| def _file_hash(sha, rel_path, base_path): |
| path = os.path.join(base_path, rel_path) |
| with open(path, 'rb') as f: |
| sha.update(str(len(rel_path)).encode('utf-8')) |
| sha.update(rel_path.encode('utf-8')) |
| while True: |
| f_stream = f.read(4096) |
| if not f_stream: |
| break |
| sha.update(str(len(f_stream)).encode('utf-8')) |
| sha.update(f_stream) |
| |
| |
| # This function computes sha256 hash of provided directories and/or files that |
| # are relative to the given base_path. |
| # Copied from the compute_hash function in https://chromium.googlesource.com/ |
| # infra/luci/recipes-py/+/HEAD/recipe_modules/file/resources/fileutil.py |
| def _compute_hash_paths(base_path, *rel_paths): |
| sha = hashlib.sha256() |
| for rel_path in rel_paths: |
| path = os.path.join(base_path, rel_path) |
| if os.path.isfile(path): |
| _file_hash(sha, rel_path, base_path) |
| elif os.path.isdir(path): |
| for root, dirs, files in os.walk(path, topdown=True): |
| dirs.sort() # ensure we walk dirs in sorted order |
| files.sort() |
| for f_name in files: |
| f_path = os.path.join(root, f_name) |
| # Check if it's a file to prevent following symlinks. |
| if os.path.isfile(f_path): |
| rel_file_path = os.path.relpath(f_path, base_path) |
| _file_hash(sha, rel_file_path, base_path) |
| |
| return sha.hexdigest() |
| |
| |
| # Return a list of src-relative paths for the dependent files and dirs for avd |
| def _get_deps(chromium_src_path): |
| deps_cmds = [ |
| os.path.join(chromium_src_path, 'build', 'print_python_deps.py'), |
| '--root', |
| chromium_src_path, |
| os.path.join(chromium_src_path, 'tools', 'android', 'avd', 'avd.py'), |
| ] |
| deps_output = subprocess.check_output(deps_cmds, universal_newlines=True) |
| # Filter out comments in deps_output |
| deps_lines = deps_output.strip().split('\n') |
| deps_entries = [dep for dep in deps_lines if not dep.startswith('#')] |
| return sorted(_BASE_DEPS + deps_entries) |
| |
| |
| def do_latest(): |
| deps = _get_deps(_SRC_PATH) |
| print(_compute_hash_paths(_SRC_PATH, *deps)) |
| |
| |
| def do_checkout(checkout_path): |
| deps = _get_deps(_SRC_PATH) |
| # Copy all contents under deps to `<checkout_path>/src` |
| for dep in deps: |
| dep_pieces = dep.split('/') |
| chromium_dep_path = os.path.join(_SRC_PATH, *dep_pieces) |
| checkout_dep_path = os.path.join(checkout_path, 'src', *dep_pieces) |
| |
| checkout_dep_par_path = os.path.abspath( |
| os.path.join(checkout_dep_path, os.path.pardir)) |
| if not os.path.exists(checkout_dep_par_path): |
| os.makedirs(checkout_dep_par_path) |
| |
| # Since _BASE_DEPS and deps may have overlaps, continue if a path exists |
| if os.path.exists(checkout_dep_path): |
| continue |
| |
| if os.path.isdir(chromium_dep_path): |
| shutil.copytree(chromium_dep_path, checkout_dep_path, symlinks=True) |
| else: |
| shutil.copy(chromium_dep_path, checkout_dep_path) |
| |
| |
| def main(): |
| ap = argparse.ArgumentParser( |
| description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) |
| sub = ap.add_subparsers() |
| |
| latest = sub.add_parser("latest") |
| latest.set_defaults(func=lambda _opts: do_latest()) |
| |
| checkout = sub.add_parser("checkout") |
| checkout.add_argument("checkout_path") |
| checkout.set_defaults(func=lambda opts: do_checkout(opts.checkout_path)) |
| |
| opts = ap.parse_args() |
| opts.func(opts) |
| |
| |
| if __name__ == '__main__': |
| main() |