#!/usr/bin/env python3 # Copyright 2023 The PDFium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Tool for converting GN runtime_deps to CAS archive paths.""" import argparse from collections import deque import filecmp import json import logging from pathlib import Path import os EXCLUDE_DIRS = { '.git', '__pycache__', } def parse_runtime_deps(runtime_deps): """Parses GN's `runtime_deps` format.""" with runtime_deps: return [line.rstrip() for line in runtime_deps] def resolve_paths(root, initial_paths): """Converts paths to CAS archive paths format.""" absolute_root = os.path.abspath(root) resolved_paths = [] unvisited_paths = deque(map(Path, initial_paths)) while unvisited_paths: path = unvisited_paths.popleft() if not path.exists(): logging.warning('"%(path)s" does not exist', {'path': path}) continue if path.is_dir(): # Expand specific children if any are excluded. child_paths = expand_dir(path) if child_paths: unvisited_paths.extendleft(child_paths) continue resolved_paths.append(os.path.relpath(path, start=absolute_root)) resolved_paths.sort() return [[absolute_root, path] for path in resolved_paths] def expand_dir(path): """Explicitly expands directory if any children are excluded.""" expand = False expanded_paths = [] for child_path in path.iterdir(): if child_path.name in EXCLUDE_DIRS and path.is_dir(): expand = True continue expanded_paths.append(child_path) return expanded_paths if expand else [] def replace_output(resolved, output_path): """Atomically replaces the output with the resolved JSON if changed.""" new_output_path = output_path + '.new' try: with open(new_output_path, 'w', encoding='ascii') as new_output: json.dump(resolved, new_output) if (os.path.exists(output_path) and filecmp.cmp(new_output_path, output_path, shallow=False)): return os.replace(new_output_path, output_path) new_output_path = None finally: if new_output_path: os.remove(new_output_path) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--root') parser.add_argument( 'runtime_deps', help='runtime_deps written by GN', type=argparse.FileType('r', encoding='utf_8'), metavar='input.runtime_deps') parser.add_argument( 'output_json', help='CAS archive paths in JSON format', metavar='output.json') args = parser.parse_args() runtime_deps = parse_runtime_deps(args.runtime_deps) resolved_paths = resolve_paths(args.root, runtime_deps) replace_output(resolved_paths, args.output_json) if __name__ == '__main__': main()