fetch_util.py (6077B)
1 # Copyright 2025 The Chromium Authors 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import hashlib 6 import json 7 import pathlib 8 import os 9 import subprocess 10 import zipfile 11 import re 12 13 _SRC_PATH = pathlib.Path(__file__).resolve().parents[2] 14 _FETCH_ALL_PATH = _SRC_PATH / 'third_party/android_deps/fetch_all.py' 15 _HASH_LENGTH = 15 16 _SKIP_FILES = ('OWNERS', 'cipd.yaml') 17 18 _DEFAULT_GENERATED_DISCLAIMER = '''\ 19 // **IMPORTANT**: build.gradle is generated and any changes would be overridden 20 // by the autoroller. Please update build.gradle.template 21 // instead. 22 ''' 23 24 25 def generate_version_map_str(bom_path, with_hash=False): 26 """Generate groovy code to fill the versionCache map. 27 28 Args: 29 bom_path: Path to bill_of_materials.json to parse. 30 with_hash: Whether to also return a hash of all the packages in the BoM. 31 """ 32 bom = [] 33 version_map_lines = [] 34 bom_hash = hashlib.sha256() 35 with open(bom_path) as f: 36 bom = json.load(f) 37 bom.sort(key=lambda x: (x['group'], x['name'])) 38 for dep in bom: 39 group = dep['group'] 40 name = dep['name'] 41 version = dep['version'] 42 bom_hash.update(f'${group}:${name}:${version}'.encode()) 43 map_line = f"versionCache['{group}:{name}'] = '{version}'" 44 version_map_lines.append(map_line) 45 version_map_str = '\n'.join(sorted(version_map_lines)) 46 version_hash = bom_hash.hexdigest()[:_HASH_LENGTH] 47 if with_hash: 48 return version_map_str, version_hash 49 return version_map_str 50 51 52 def fill_template(template_path, output_path, **kwargs): 53 """Fills in a template. 54 55 Args: 56 template_path: Path to <file>.template. 57 output_path: Path to <file>. 58 **kwargs: each kwarg should be a string to replace in the template. 59 """ 60 content = pathlib.Path(template_path).read_text() 61 for key, value in kwargs.items(): 62 replace_string = '{{' + key + '}}' 63 if not replace_string in content: 64 raise Exception(f'Replace text {replace_string} ' 65 f'not found in {template_path}') 66 try: 67 content = content.replace(replace_string, value) 68 except Exception as e: 69 raise e from Exception( 70 f'Failed to replace {repr(replace_string)} with {repr(value)}') 71 72 content = content.replace(r'{{generated_disclaimer}}', 73 _DEFAULT_GENERATED_DISCLAIMER) 74 75 unreplaced_variable_re = re.compile(r'\{\{(.+)\}\}') 76 if matches := unreplaced_variable_re.findall(content): 77 unreplaced_variables = ', '.join(repr(match) for match in matches) 78 raise Exception('Found unreplaced variables ' 79 f'[{unreplaced_variables}] in {template_path}') 80 81 pathlib.Path(output_path).write_text(content) 82 83 84 def write_cipd_yaml(package_root, 85 package_name, 86 version, 87 output_path, 88 experimental=False): 89 """Writes cipd.yaml file at the passed-in path.""" 90 91 root_libs_dir = package_root / 'libs' 92 lib_dirs = os.listdir(root_libs_dir) 93 if not lib_dirs: 94 raise Exception('No generated libraries in {}'.format(root_libs_dir)) 95 96 data_files = [ 97 'BUILD.gn', 98 'VERSION.txt', 99 'bill_of_materials.json', 100 'additional_readme_paths.json', 101 'build.gradle', 102 'to_commit.zip', 103 ] 104 for lib_dir in lib_dirs: 105 abs_lib_dir: pathlib.Path = root_libs_dir / lib_dir 106 if not abs_lib_dir.is_dir(): 107 continue 108 109 for lib_file in abs_lib_dir.iterdir(): 110 if lib_file.name in _SKIP_FILES: 111 continue 112 data_files.append((abs_lib_dir / lib_file).relative_to(package_root)) 113 114 if experimental: 115 package_name = (f'experimental/google.com/{os.getlogin()}/{package_name}') 116 contents = [ 117 '# Copyright 2025 The Chromium Authors', 118 '# Use of this source code is governed by a BSD-style license that can be', 119 '# found in the LICENSE file.', 120 f'# version: {version}', 121 f'package: {package_name}', 122 f'description: CIPD package for {package_name}', 123 'data:', 124 ] 125 contents.extend(f'- file: {str(f)}' for f in data_files) 126 127 with open(output_path, 'w') as out: 128 out.write('\n'.join(contents)) 129 130 131 def create_to_commit_zip(output_path, package_root, dirnames, 132 absolute_file_map): 133 """Generates a to_commit.zip from useful text files inside |package_root|. 134 135 Args: 136 output_path: where to output the zipfile. 137 package_root: path to gradle/cipd package. 138 dirnames: list of subdirs under |package_root| to walk. 139 absolute_file_map: List of files to be stored under the absolute prefix 140 CHROMIUM_SRC/. 141 """ 142 to_commit_paths = [] 143 for directory in dirnames: 144 for root, _, files in os.walk(package_root / directory): 145 for filename in files: 146 # Avoid committing actual artifacts. 147 if filename.endswith(('.aar', '.jar')): 148 continue 149 # TODO(mheikal): stop outputting these from gradle since they are not 150 # useful. 151 if filename in _SKIP_FILES: 152 continue 153 file_path = pathlib.Path(root) / filename 154 file_path_in_zip = file_path.relative_to(package_root) 155 to_commit_paths.append((file_path, file_path_in_zip)) 156 157 for filename, path_in_repo in absolute_file_map.items(): 158 file_path = package_root / filename 159 path_in_zip = f'CHROMIUM_SRC/{path_in_repo}' 160 to_commit_paths.append((file_path, path_in_zip)) 161 162 with zipfile.ZipFile(output_path, 'w') as zip_file: 163 for filename, arcname in to_commit_paths: 164 zip_file.write(filename, arcname=arcname) 165 166 167 def run_fetch_all(android_deps_dir, 168 extra_args, 169 verbose_count=0, 170 output_subdir=None): 171 fetch_all_cmd = [ 172 _FETCH_ALL_PATH, '--android-deps-dir', android_deps_dir, 173 '--ignore-vulnerabilities' 174 ] + ['-v'] * verbose_count 175 if output_subdir: 176 fetch_all_cmd += ['--output-subdir', output_subdir] 177 178 # Filter out -- from the args to pass to fetch_all.py. 179 fetch_all_cmd += [a for a in extra_args if a != '--'] 180 181 subprocess.run(fetch_all_cmd, check=True)