Files
StaticLink.Avalonia/build/linux-toolchain/sync-skia-deps.py
T

90 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Fetch pinned native cross-build dependencies without cloning Git history."""
import argparse
import ast
import json
import hashlib
from pathlib import Path
import re
import subprocess
import sys
# These dependencies correspond to features explicitly disabled by this recipe.
# Unknown/new dependencies stay enabled; do not infer a skip from their names.
DISABLED = {'dng_sdk', 'icu', 'dawn', 'angle2', 'swiftshader', 'emsdk'}
def evaluate(node, variables):
if isinstance(node, ast.Constant):
return node.value
if isinstance(node, ast.Dict):
return {evaluate(k, variables): evaluate(v, variables) for k, v in zip(node.keys, node.values)}
if isinstance(node, (ast.List, ast.Tuple)):
return [evaluate(n, variables) for n in node.elts]
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
return evaluate(node.left, variables) + evaluate(node.right, variables)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'Var' and len(node.args) == 1:
return variables[evaluate(node.args[0], variables)]
raise ValueError('Unsupported DEPS expression: ' + ast.dump(node))
def dependencies(source):
values = {}
for node in ast.parse((source / 'DEPS').read_text()).body:
if isinstance(node, ast.Assign):
for name in node.targets:
if isinstance(name, ast.Name) and name.id in ('vars', 'deps'):
values[name.id] = evaluate(node.value, values.get('vars', {}))
if not values.get('deps'):
raise ValueError('No dependency map in DEPS')
return values['deps']
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--source', type=Path, required=True)
p.add_argument('--cache', type=Path, help='Immutable dependency checkouts shared across version profiles')
p.add_argument('--no-fetch-gn', action='store_true', help='Use an existing verified GN for a local test')
a = p.parse_args()
source = a.source.resolve()
records = {}
for relative, value in dependencies(source).items():
if not isinstance(value, str):
continue # GN/Ninja CIPD packages are handled by upstream fetch-gn.
if Path(relative).name in DISABLED:
continue
if '..' in Path(relative).parts:
raise ValueError('Dependency path escapes source: ' + relative)
link = source / relative
path = link.absolute()
if not path.is_relative_to(source):
raise ValueError('Dependency path escapes source: ' + relative)
url, revision = value.rsplit('@', 1)
if not re.fullmatch(r'[0-9a-f]{40}', revision):
raise ValueError('Dependency is not commit-pinned: ' + value)
if a.cache:
key = hashlib.sha256((url + '@' + revision).encode()).hexdigest()
cached = a.cache.resolve() / key
if link.is_symlink():
link.unlink()
if not link.exists():
link.parent.mkdir(parents=True, exist_ok=True)
link.symlink_to(cached, target_is_directory=True)
path = cached
if not (path / '.git').exists():
subprocess.run(['git', 'init', '-q', str(path)], check=True)
subprocess.run(['git', '-C', str(path), 'remote', 'add', 'origin', url], check=True)
current = subprocess.run(['git', '-C', str(path), 'rev-parse', 'HEAD'], capture_output=True, text=True)
if current.stdout.strip() != revision:
subprocess.run(['git', '-C', str(path), 'remote', 'set-url', 'origin', url], check=True)
subprocess.run(['git', '-C', str(path), 'fetch', '--depth=1', 'origin', revision], check=True)
subprocess.run(['git', '-C', str(path), 'checkout', '--detach', revision], check=True)
records[relative] = revision
if not a.no_fetch_gn:
subprocess.run([sys.executable, str(source / 'bin/fetch-gn')], cwd=source, check=True)
(source / 'native-cross-deps.json').write_text(json.dumps(records, indent=2) + '\n')
if __name__ == '__main__':
main()