#!/usr/bin/env python3 # # Copyright 2017 The Dart project authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Usage: tools/dart/create_updated_flutter_deps.py [-d dart/DEPS] [-f flutter/DEPS] # # This script parses existing flutter DEPS file, identifies all 'dart_' prefixed # dependencies, looks up revision from dart DEPS file, updates those dependencies # and rewrites flutter DEPS file. import argparse import os import sys DART_SCRIPT_DIR = os.path.dirname(sys.argv[0]) OLD_DART_DEPS = os.path.realpath(os.path.join(DART_SCRIPT_DIR, '../../third_party/dart/DEPS')) DART_DEPS = os.path.realpath(os.path.join(DART_SCRIPT_DIR, '../../flutter/third_party/dart/DEPS')) FLUTTER_DEPS = os.path.realpath(os.path.join(DART_SCRIPT_DIR, '../../../../DEPS')) # Path to Dart SDK checkout within Flutter repo. DART_SDK_ROOT = 'engine/src/flutter/third_party/dart' class VarImpl(object): def __init__(self, local_scope): self._local_scope = local_scope def Lookup(self, var_name): """Implements the Var syntax.""" if var_name in self._local_scope.get("vars", {}): return self._local_scope["vars"][var_name] if var_name == 'host_os': return 'linux' # assume some default value if var_name == 'host_cpu': return 'x64' # assume some default value raise Exception("Var is not defined: %s" % var_name) def ParseDepsFile(deps_file): local_scope = {} var = VarImpl(local_scope) global_scope = { 'Var': var.Lookup, 'deps_os': {}, } # Read the content. with open(deps_file, 'r') as fp: deps_content = fp.read() # Eval the content. exec(deps_content, global_scope, local_scope) return (local_scope.get('vars', {}), local_scope.get('deps', {})) def ParseArgs(args): args = args[1:] parser = argparse.ArgumentParser( description='A script to generate updated dart dependencies for flutter DEPS.') parser.add_argument('--dart_deps', '-d', type=str, help='Dart DEPS file.', default=DART_DEPS) parser.add_argument('--flutter_deps', '-f', type=str, help='Flutter DEPS file.', default=FLUTTER_DEPS) return parser.parse_args(args) def PrettifySourcePathForDEPS(flutter_vars, dep_path, source): """Prepare source for writing into Flutter DEPS file. If source is not a string then it is expected to be a dictionary defining a CIPD dependency. In this case it is written as is - but sorted to guarantee stability of the output. Otherwise source is a path to a repo plus version (hash or tag): {repo_host}/{repo_path}@{version} We want to convert this into one of the following: Var(repo_host_var) + 'repo_path' + '@' + Var(version_var) Var(repo_host_var) + 'repo_path@version' 'source' Where repo_host_var is one of '*_git' variables and version_var is one of 'dart_{dep_name}_tag' or 'dart_{dep_name}_rev' variables. """ # If this a CIPD dependency then keep it as-is but sort its contents # to ensure stable ordering. if not isinstance(source, str): return dict(sorted(source.items())) # Decompose source into {repo_host}/{repo_path}@{version} repo_host_var = None version_var = None repo_path_with_version = source for var_name, var_value in flutter_vars.items(): if var_name.endswith("_git") and source.startswith(var_value): repo_host_var = var_name repo_path_with_version = source[len(var_value):] break if repo_path_with_version.find('@') == -1: raise ValueError(f'{dep_path} source is unversioned') repo_path, version = repo_path_with_version.split('@', 1) # Figure out the name of the dependency from its path to compute # corresponding version_var. # # Normally, the last component of the dep_path is the name of the dependency. # However some dependencies are placed in a subdirectory named "src" # within a directory named after the dependency. dep_name = os.path.basename(dep_path) if dep_name == 'src': dep_name = os.path.basename(os.path.dirname(dep_path)) for var_name in [f'dart_{dep_name}_tag', f'dart_{dep_name}_rev']: if var_name in flutter_vars: version_var = var_name break # Format result from available individual pieces. result = [] if repo_host_var is not None: result += [f"Var('{repo_host_var}')"] if version_var is not None: result += [f"'{repo_path}'", "'@'", f"Var('{version_var}')"] else: result += [f"'{repo_path_with_version}'"] return " + ".join(result) def ComputeDartDeps(flutter_vars, flutter_deps, dart_deps): """Compute sources for deps nested under DART_SDK_ROOT in Flutter DEPS. These dependencies originate from Dart SDK, so their version are computed by looking them up in Dart DEPS using appropriately relocated paths, e.g. '{DART_SDK_ROOT}/third_party/foo' is located at 'sdk/third_party/foo' in Dart DEPS. Source paths are expressed in terms of 'xyz_git' and 'dart_xyz_tag' or 'dart_xyz_rev' variables if possible. If corresponding path is not found in Dart DEPS the dependency is considered no longer needed and is removed from Flutter DEPS. Returns: dictionary of dependencies """ new_dart_deps = {} # Trailing / to avoid matching Dart SDK dependency itself. dart_sdk_root_dir = DART_SDK_ROOT + '/' # Find all dependencies which are nested inside Dart SDK and check # if Dart SDK still needs them. If Dart DEPS still mentions them # take updated version from Dart DEPS. for (dep_path, dep_source) in sorted(flutter_deps.items()): if dep_path.startswith(dart_sdk_root_dir): # Dart dependencies are given relative to root directory called `sdk/`. dart_dep_path = f'sdk/{dep_path[len(dart_sdk_root_dir):]}' if dart_dep_path in dart_deps: # Still used, add it to the result. new_dart_deps[dep_path] = PrettifySourcePathForDEPS(flutter_vars, dep_path, dart_deps[dart_dep_path]) return new_dart_deps def Main(argv): args = ParseArgs(argv) if args.dart_deps == DART_DEPS and not os.path.isfile(DART_DEPS): args.dart_deps = OLD_DART_DEPS (dart_vars, dart_deps) = ParseDepsFile(args.dart_deps) (flutter_vars, flutter_deps) = ParseDepsFile(args.flutter_deps) updated_vars = {} # Collect updated dependencies for (k,v) in sorted(flutter_vars.items()): if k not in ('dart_revision', 'dart_git') and k.startswith('dart_'): dart_key = k[len('dart_'):] if dart_key in dart_vars: updated_vars[k] = dart_vars[dart_key].lstrip('@') new_dart_deps = ComputeDartDeps(flutter_vars, flutter_deps, dart_deps) # Write updated DEPS file to a side updatedfilename = args.flutter_deps + ".new" updatedfile = open(updatedfilename, "w") file = open(args.flutter_deps) lines = file.readlines() i = 0 while i < len(lines): updatedfile.write(lines[i]) if lines[i].startswith(" 'dart_revision':"): i = i + 2 updatedfile.writelines([ '\n', ' # WARNING: DO NOT EDIT MANUALLY\n', ' # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py\n']) while i < len(lines) and len(lines[i].strip()) > 0: i = i + 1 for (k, v) in sorted(updated_vars.items()): updatedfile.write(" '%s': '%s',\n" % (k, v)) updatedfile.write('\n') elif lines[i].startswith(" # WARNING: Unused Dart dependencies"): updatedfile.write('\n') i = i + 1 while i < len(lines) and not lines[i].startswith(" # WARNING: end of dart dependencies"): i = i + 1 for dep_path, dep_source in new_dart_deps.items(): updatedfile.write(f" '{dep_path}':\n {dep_source},\n\n") updatedfile.write(lines[i]) i = i + 1 # Rename updated DEPS file into a new DEPS file os.remove(args.flutter_deps) os.rename(updatedfilename, args.flutter_deps) return 0 if __name__ == '__main__': sys.exit(Main(sys.argv))