1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project 3*6dbdd20aSAndroid Build Coastguard Worker# 4*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*6dbdd20aSAndroid Build Coastguard Worker# 8*6dbdd20aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*6dbdd20aSAndroid Build Coastguard Worker# 10*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License. 15*6dbdd20aSAndroid Build Coastguard Worker""" 16*6dbdd20aSAndroid Build Coastguard WorkerEnforce import rules for https://ui.perfetto.dev. 17*6dbdd20aSAndroid Build Coastguard Worker""" 18*6dbdd20aSAndroid Build Coastguard Worker 19*6dbdd20aSAndroid Build Coastguard Workerimport sys 20*6dbdd20aSAndroid Build Coastguard Workerimport os 21*6dbdd20aSAndroid Build Coastguard Workerimport re 22*6dbdd20aSAndroid Build Coastguard Workerimport collections 23*6dbdd20aSAndroid Build Coastguard Workerimport argparse 24*6dbdd20aSAndroid Build Coastguard Workerimport fnmatch 25*6dbdd20aSAndroid Build Coastguard Worker 26*6dbdd20aSAndroid Build Coastguard WorkerROOT_DIR = os.path.dirname( 27*6dbdd20aSAndroid Build Coastguard Worker os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 28*6dbdd20aSAndroid Build Coastguard WorkerUI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src') 29*6dbdd20aSAndroid Build Coastguard Worker 30*6dbdd20aSAndroid Build Coastguard WorkerNODE_MODULES = '%node_modules%' # placeholder to depend on any node module. 31*6dbdd20aSAndroid Build Coastguard Worker 32*6dbdd20aSAndroid Build Coastguard Worker# The format of this array is: (src) -> (dst). 33*6dbdd20aSAndroid Build Coastguard Worker# If src or dst are arrays, the semantic is the cartesian product, e.g.: 34*6dbdd20aSAndroid Build Coastguard Worker# [a,b] -> [c,d] is equivalent to allowing a>c, a>d, b>c, b>d. 35*6dbdd20aSAndroid Build Coastguard WorkerDEPS_ALLOWLIST = [ 36*6dbdd20aSAndroid Build Coastguard Worker # Everything can depend on base/, protos and NPM packages. 37*6dbdd20aSAndroid Build Coastguard Worker ('*', ['/base/*', '/protos/index', '/gen/perfetto_version', NODE_MODULES]), 38*6dbdd20aSAndroid Build Coastguard Worker 39*6dbdd20aSAndroid Build Coastguard Worker # Integration tests can depend on everything. 40*6dbdd20aSAndroid Build Coastguard Worker ('/test/*', '*'), 41*6dbdd20aSAndroid Build Coastguard Worker 42*6dbdd20aSAndroid Build Coastguard Worker # Dependencies allowed for internal UI code. 43*6dbdd20aSAndroid Build Coastguard Worker ( 44*6dbdd20aSAndroid Build Coastguard Worker [ 45*6dbdd20aSAndroid Build Coastguard Worker '/frontend/*', 46*6dbdd20aSAndroid Build Coastguard Worker '/core/*', 47*6dbdd20aSAndroid Build Coastguard Worker '/common/*', 48*6dbdd20aSAndroid Build Coastguard Worker ], 49*6dbdd20aSAndroid Build Coastguard Worker [ 50*6dbdd20aSAndroid Build Coastguard Worker '/frontend/*', 51*6dbdd20aSAndroid Build Coastguard Worker '/core/*', 52*6dbdd20aSAndroid Build Coastguard Worker '/common/*', 53*6dbdd20aSAndroid Build Coastguard Worker '/components/*', 54*6dbdd20aSAndroid Build Coastguard Worker '/public/*', 55*6dbdd20aSAndroid Build Coastguard Worker '/trace_processor/*', 56*6dbdd20aSAndroid Build Coastguard Worker '/widgets/*', 57*6dbdd20aSAndroid Build Coastguard Worker '/protos/*', 58*6dbdd20aSAndroid Build Coastguard Worker '/gen/perfetto_version', 59*6dbdd20aSAndroid Build Coastguard Worker ], 60*6dbdd20aSAndroid Build Coastguard Worker ), 61*6dbdd20aSAndroid Build Coastguard Worker 62*6dbdd20aSAndroid Build Coastguard Worker # /public (interfaces + lib) can depend only on a restricted surface. 63*6dbdd20aSAndroid Build Coastguard Worker ('/public/*', ['/base/*', '/trace_processor/*', '/widgets/*']), 64*6dbdd20aSAndroid Build Coastguard Worker 65*6dbdd20aSAndroid Build Coastguard Worker # /plugins (and core_plugins) can depend only on a restricted surface. 66*6dbdd20aSAndroid Build Coastguard Worker ( 67*6dbdd20aSAndroid Build Coastguard Worker '/*plugins/*', 68*6dbdd20aSAndroid Build Coastguard Worker [ 69*6dbdd20aSAndroid Build Coastguard Worker '/base/*', '/public/*', '/trace_processor/*', '/widgets/*', 70*6dbdd20aSAndroid Build Coastguard Worker '/components/*' 71*6dbdd20aSAndroid Build Coastguard Worker ], 72*6dbdd20aSAndroid Build Coastguard Worker ), 73*6dbdd20aSAndroid Build Coastguard Worker 74*6dbdd20aSAndroid Build Coastguard Worker # /components can depend on the 'base' packages & public 75*6dbdd20aSAndroid Build Coastguard Worker ( 76*6dbdd20aSAndroid Build Coastguard Worker '/components/*', 77*6dbdd20aSAndroid Build Coastguard Worker [ 78*6dbdd20aSAndroid Build Coastguard Worker '/base/*', 79*6dbdd20aSAndroid Build Coastguard Worker '/trace_processor/*', 80*6dbdd20aSAndroid Build Coastguard Worker '/widgets/*', 81*6dbdd20aSAndroid Build Coastguard Worker '/public/*', 82*6dbdd20aSAndroid Build Coastguard Worker '/components/*', 83*6dbdd20aSAndroid Build Coastguard Worker ], 84*6dbdd20aSAndroid Build Coastguard Worker ), 85*6dbdd20aSAndroid Build Coastguard Worker 86*6dbdd20aSAndroid Build Coastguard Worker # Extra dependencies allowed for core_plugins only. 87*6dbdd20aSAndroid Build Coastguard Worker # TODO(priniano): remove this entry to figure out what it takes to move the 88*6dbdd20aSAndroid Build Coastguard Worker # remaining /core_plugins to /plugins and get rid of core_plugins. 89*6dbdd20aSAndroid Build Coastguard Worker ( 90*6dbdd20aSAndroid Build Coastguard Worker ['/core_plugins/*'], 91*6dbdd20aSAndroid Build Coastguard Worker ['/core/*', '/frontend/*', '/common/actions'], 92*6dbdd20aSAndroid Build Coastguard Worker ), 93*6dbdd20aSAndroid Build Coastguard Worker 94*6dbdd20aSAndroid Build Coastguard Worker # Miscl legitimate deps. 95*6dbdd20aSAndroid Build Coastguard Worker ('/frontend/index', ['/gen/*']), 96*6dbdd20aSAndroid Build Coastguard Worker ('/traceconv/index', '/gen/traceconv'), 97*6dbdd20aSAndroid Build Coastguard Worker ('/engine/wasm_bridge', '/gen/trace_processor'), 98*6dbdd20aSAndroid Build Coastguard Worker ('/trace_processor/sql_utils/*', '/trace_processor/*'), 99*6dbdd20aSAndroid Build Coastguard Worker ('/protos/index', '/gen/protos'), 100*6dbdd20aSAndroid Build Coastguard Worker 101*6dbdd20aSAndroid Build Coastguard Worker # ------ Technical debt that needs cleaning up below this point ------ 102*6dbdd20aSAndroid Build Coastguard Worker # TODO(stevegolton): Remove these once we extract core types out of 103*6dbdd20aSAndroid Build Coastguard Worker # components. 104*6dbdd20aSAndroid Build Coastguard Worker ( 105*6dbdd20aSAndroid Build Coastguard Worker '/components/*', 106*6dbdd20aSAndroid Build Coastguard Worker [ 107*6dbdd20aSAndroid Build Coastguard Worker '/core/trace_impl', 108*6dbdd20aSAndroid Build Coastguard Worker '/core/app_impl', 109*6dbdd20aSAndroid Build Coastguard Worker '/core/router', 110*6dbdd20aSAndroid Build Coastguard Worker '/core/flow_types', 111*6dbdd20aSAndroid Build Coastguard Worker '/core/fake_trace_impl', 112*6dbdd20aSAndroid Build Coastguard Worker '/core/raf_scheduler', 113*6dbdd20aSAndroid Build Coastguard Worker '/core/feature_flags', 114*6dbdd20aSAndroid Build Coastguard Worker '/frontend/css_constants', 115*6dbdd20aSAndroid Build Coastguard Worker ], 116*6dbdd20aSAndroid Build Coastguard Worker ), 117*6dbdd20aSAndroid Build Coastguard Worker 118*6dbdd20aSAndroid Build Coastguard Worker # TODO(primiano): Record page-related technical debt. 119*6dbdd20aSAndroid Build Coastguard Worker ('/plugins/dev.perfetto.RecordTrace/*', 120*6dbdd20aSAndroid Build Coastguard Worker ['/frontend/globals', '/gen/protos']), 121*6dbdd20aSAndroid Build Coastguard Worker ('/chrome_extension/chrome_tracing_controller', 122*6dbdd20aSAndroid Build Coastguard Worker '/plugins/dev.perfetto.RecordTrace/*'), 123*6dbdd20aSAndroid Build Coastguard Worker 124*6dbdd20aSAndroid Build Coastguard Worker # Bigtrace deps. 125*6dbdd20aSAndroid Build Coastguard Worker ('/bigtrace/*', ['/base/*', '/widgets/*', '/trace_processor/*']), 126*6dbdd20aSAndroid Build Coastguard Worker 127*6dbdd20aSAndroid Build Coastguard Worker # TODO(primiano): misc tech debt. 128*6dbdd20aSAndroid Build Coastguard Worker ('/public/lib/extensions', '/frontend/*'), 129*6dbdd20aSAndroid Build Coastguard Worker ('/bigtrace/index', ['/core/live_reload', '/core/raf_scheduler']), 130*6dbdd20aSAndroid Build Coastguard Worker ('/plugins/dev.perfetto.HeapProfile/*', '/frontend/trace_converter'), 131*6dbdd20aSAndroid Build Coastguard Worker] 132*6dbdd20aSAndroid Build Coastguard Worker 133*6dbdd20aSAndroid Build Coastguard Worker 134*6dbdd20aSAndroid Build Coastguard Workerdef all_source_files(): 135*6dbdd20aSAndroid Build Coastguard Worker for root, dirs, files in os.walk(UI_SRC_DIR, followlinks=False): 136*6dbdd20aSAndroid Build Coastguard Worker for name in files: 137*6dbdd20aSAndroid Build Coastguard Worker if name.endswith('.ts') and not name.endswith('.d.ts'): 138*6dbdd20aSAndroid Build Coastguard Worker yield os.path.join(root, name) 139*6dbdd20aSAndroid Build Coastguard Worker 140*6dbdd20aSAndroid Build Coastguard Worker 141*6dbdd20aSAndroid Build Coastguard Workerdef is_dir(path, cache={}): 142*6dbdd20aSAndroid Build Coastguard Worker try: 143*6dbdd20aSAndroid Build Coastguard Worker return cache[path] 144*6dbdd20aSAndroid Build Coastguard Worker except KeyError: 145*6dbdd20aSAndroid Build Coastguard Worker result = cache[path] = os.path.isdir(path) 146*6dbdd20aSAndroid Build Coastguard Worker return result 147*6dbdd20aSAndroid Build Coastguard Worker 148*6dbdd20aSAndroid Build Coastguard Worker 149*6dbdd20aSAndroid Build Coastguard Workerdef remove_prefix(s, prefix): 150*6dbdd20aSAndroid Build Coastguard Worker return s[len(prefix):] if s.startswith(prefix) else s 151*6dbdd20aSAndroid Build Coastguard Worker 152*6dbdd20aSAndroid Build Coastguard Worker 153*6dbdd20aSAndroid Build Coastguard Workerdef remove_suffix(s, suffix): 154*6dbdd20aSAndroid Build Coastguard Worker return s[:-len(suffix)] if s.endswith(suffix) else s 155*6dbdd20aSAndroid Build Coastguard Worker 156*6dbdd20aSAndroid Build Coastguard Worker 157*6dbdd20aSAndroid Build Coastguard Workerdef normalize_path(path): 158*6dbdd20aSAndroid Build Coastguard Worker return remove_suffix(remove_prefix(path, UI_SRC_DIR), '.ts') 159*6dbdd20aSAndroid Build Coastguard Worker 160*6dbdd20aSAndroid Build Coastguard Worker 161*6dbdd20aSAndroid Build Coastguard Workerdef find_plugin_declared_deps(path): 162*6dbdd20aSAndroid Build Coastguard Worker """Returns the set of deps declared by the plugin (if any) 163*6dbdd20aSAndroid Build Coastguard Worker 164*6dbdd20aSAndroid Build Coastguard Worker It scans the plugin/index.ts file, and resolves the declared dependencies, 165*6dbdd20aSAndroid Build Coastguard Worker working out the path of the plugin we depend on (by looking at the imports). 166*6dbdd20aSAndroid Build Coastguard Worker Returns a tuple of the form (src_plugin_path, set{dst_plugin_path}) 167*6dbdd20aSAndroid Build Coastguard Worker Where: 168*6dbdd20aSAndroid Build Coastguard Worker src_plugin_path: is the normalized path of the input (e.g. /plugins/foo) 169*6dbdd20aSAndroid Build Coastguard Worker dst_path: is the normalized path of the declared dependency. 170*6dbdd20aSAndroid Build Coastguard Worker """ 171*6dbdd20aSAndroid Build Coastguard Worker src = normalize_path(path) 172*6dbdd20aSAndroid Build Coastguard Worker src_plugin = get_plugin_path(src) 173*6dbdd20aSAndroid Build Coastguard Worker if src_plugin is None or src != src_plugin + '/index': 174*6dbdd20aSAndroid Build Coastguard Worker # If the file is not a plugin, or is not the plugin index.ts, bail out. 175*6dbdd20aSAndroid Build Coastguard Worker return 176*6dbdd20aSAndroid Build Coastguard Worker # First extract all the default-imports in the file. Usually there is one for 177*6dbdd20aSAndroid Build Coastguard Worker # each imported plugin, of the form: 178*6dbdd20aSAndroid Build Coastguard Worker # import ThreadPlugin from '../plugins/dev.perfetto.Thread' 179*6dbdd20aSAndroid Build Coastguard Worker import_map = {} # 'ThreadPlugin' -> '/plugins/dev.perfetto.Thread' 180*6dbdd20aSAndroid Build Coastguard Worker for (src, target, default_import) in find_imports(path): 181*6dbdd20aSAndroid Build Coastguard Worker target_plugin = get_plugin_path(target) 182*6dbdd20aSAndroid Build Coastguard Worker if default_import is not None or target_plugin is not None: 183*6dbdd20aSAndroid Build Coastguard Worker import_map[default_import] = target_plugin 184*6dbdd20aSAndroid Build Coastguard Worker 185*6dbdd20aSAndroid Build Coastguard Worker # Now extract the declared dependencies for the plugin. This looks for the 186*6dbdd20aSAndroid Build Coastguard Worker # statement 'static readonly dependencies = [ThreadPlugin]'. It can be broken 187*6dbdd20aSAndroid Build Coastguard Worker # down over multiple lines, so we approach this in two steps. First we find 188*6dbdd20aSAndroid Build Coastguard Worker # everything within the square brackets; then we remove spaces and \n and 189*6dbdd20aSAndroid Build Coastguard Worker # tokenize on commas 190*6dbdd20aSAndroid Build Coastguard Worker with open(path) as f: 191*6dbdd20aSAndroid Build Coastguard Worker s = f.read() 192*6dbdd20aSAndroid Build Coastguard Worker DEP_REGEX = r'^\s*static readonly dependencies\s*=\s*\[([^\]]*)\]' 193*6dbdd20aSAndroid Build Coastguard Worker all_deps = re.findall(DEP_REGEX, s, flags=re.MULTILINE) 194*6dbdd20aSAndroid Build Coastguard Worker if len(all_deps) == 0: 195*6dbdd20aSAndroid Build Coastguard Worker return 196*6dbdd20aSAndroid Build Coastguard Worker if len(all_deps) > 1: 197*6dbdd20aSAndroid Build Coastguard Worker raise Exception('Ambiguous plugin deps in %s: %s' % (path, all_deps)) 198*6dbdd20aSAndroid Build Coastguard Worker declared_deps = re.sub('\s*', '', all_deps[0]).split(',') 199*6dbdd20aSAndroid Build Coastguard Worker for imported_as in declared_deps: 200*6dbdd20aSAndroid Build Coastguard Worker resolved_dep = import_map.get(imported_as) 201*6dbdd20aSAndroid Build Coastguard Worker if resolved_dep is None: 202*6dbdd20aSAndroid Build Coastguard Worker raise Exception('Could not resolve import %s in %s' % (imported_as, src)) 203*6dbdd20aSAndroid Build Coastguard Worker yield (src_plugin, resolved_dep) 204*6dbdd20aSAndroid Build Coastguard Worker 205*6dbdd20aSAndroid Build Coastguard Worker 206*6dbdd20aSAndroid Build Coastguard Workerdef find_imports(path): 207*6dbdd20aSAndroid Build Coastguard Worker src = normalize_path(path) 208*6dbdd20aSAndroid Build Coastguard Worker directory, _ = os.path.split(src) 209*6dbdd20aSAndroid Build Coastguard Worker with open(path) as f: 210*6dbdd20aSAndroid Build Coastguard Worker s = f.read() 211*6dbdd20aSAndroid Build Coastguard Worker for m in re.finditer( 212*6dbdd20aSAndroid Build Coastguard Worker "^import\s+([^;]+)\s+from\s+'([^']+)';$", s, flags=re.MULTILINE): 213*6dbdd20aSAndroid Build Coastguard Worker # Flatten multi-line imports into one line, removing spaces. The resulting 214*6dbdd20aSAndroid Build Coastguard Worker # import line can look like: 215*6dbdd20aSAndroid Build Coastguard Worker # '{foo,bar,baz}' in most cases 216*6dbdd20aSAndroid Build Coastguard Worker # 'DefaultImportName' when doing import DefaultImportName from '...' 217*6dbdd20aSAndroid Build Coastguard Worker # 'DefaultImportName,{foo,bar,bar}' when doing a mixture of the above. 218*6dbdd20aSAndroid Build Coastguard Worker imports = re.sub('\s', '', m[1]) 219*6dbdd20aSAndroid Build Coastguard Worker default_import = (re.findall('^\w+', imports) + [None])[0] 220*6dbdd20aSAndroid Build Coastguard Worker 221*6dbdd20aSAndroid Build Coastguard Worker # Normalize the imported file 222*6dbdd20aSAndroid Build Coastguard Worker target = m[2] 223*6dbdd20aSAndroid Build Coastguard Worker if target.startswith('.'): 224*6dbdd20aSAndroid Build Coastguard Worker target = os.path.normpath(os.path.join(directory, target)) 225*6dbdd20aSAndroid Build Coastguard Worker if is_dir(UI_SRC_DIR + target): 226*6dbdd20aSAndroid Build Coastguard Worker target = os.path.join(target, 'index') 227*6dbdd20aSAndroid Build Coastguard Worker 228*6dbdd20aSAndroid Build Coastguard Worker yield (src, target, default_import) 229*6dbdd20aSAndroid Build Coastguard Worker 230*6dbdd20aSAndroid Build Coastguard Worker 231*6dbdd20aSAndroid Build Coastguard Workerdef path_to_id(path): 232*6dbdd20aSAndroid Build Coastguard Worker path = path.replace('/', '_') 233*6dbdd20aSAndroid Build Coastguard Worker path = path.replace('-', '_') 234*6dbdd20aSAndroid Build Coastguard Worker path = path.replace('@', '_at_') 235*6dbdd20aSAndroid Build Coastguard Worker path = path.replace('.', '_') 236*6dbdd20aSAndroid Build Coastguard Worker return path 237*6dbdd20aSAndroid Build Coastguard Worker 238*6dbdd20aSAndroid Build Coastguard Worker 239*6dbdd20aSAndroid Build Coastguard Workerdef is_external_dep(path): 240*6dbdd20aSAndroid Build Coastguard Worker return not path.startswith('/') 241*6dbdd20aSAndroid Build Coastguard Worker 242*6dbdd20aSAndroid Build Coastguard Worker 243*6dbdd20aSAndroid Build Coastguard Workerdef write_dot(graph, f): 244*6dbdd20aSAndroid Build Coastguard Worker print('digraph g {', file=f) 245*6dbdd20aSAndroid Build Coastguard Worker for node, edges in graph.items(): 246*6dbdd20aSAndroid Build Coastguard Worker node_id = path_to_id(node) 247*6dbdd20aSAndroid Build Coastguard Worker shape = 'rectangle' if is_external_dep(node) else 'ellipse' 248*6dbdd20aSAndroid Build Coastguard Worker print(f'{node_id} [shape={shape}, label="{node}"];', file=f) 249*6dbdd20aSAndroid Build Coastguard Worker 250*6dbdd20aSAndroid Build Coastguard Worker for edge in edges: 251*6dbdd20aSAndroid Build Coastguard Worker edge_id = path_to_id(edge) 252*6dbdd20aSAndroid Build Coastguard Worker print(f'{node_id} -> {edge_id};', file=f) 253*6dbdd20aSAndroid Build Coastguard Worker print('}', file=f) 254*6dbdd20aSAndroid Build Coastguard Worker 255*6dbdd20aSAndroid Build Coastguard Worker 256*6dbdd20aSAndroid Build Coastguard Workerdef get_plugin_path(path): 257*6dbdd20aSAndroid Build Coastguard Worker m = re.match('^(/(?:core_)?plugins/([^/]+))/.*', path) 258*6dbdd20aSAndroid Build Coastguard Worker return m.group(1) if m is not None else None 259*6dbdd20aSAndroid Build Coastguard Worker 260*6dbdd20aSAndroid Build Coastguard Worker 261*6dbdd20aSAndroid Build Coastguard Workerdef flatten_rules(rules): 262*6dbdd20aSAndroid Build Coastguard Worker flat_deps = [] 263*6dbdd20aSAndroid Build Coastguard Worker for rule_src, rule_dst in rules: 264*6dbdd20aSAndroid Build Coastguard Worker src_list = rule_src if isinstance(rule_src, list) else [rule_src] 265*6dbdd20aSAndroid Build Coastguard Worker dst_list = rule_dst if isinstance(rule_dst, list) else [rule_dst] 266*6dbdd20aSAndroid Build Coastguard Worker for src in src_list: 267*6dbdd20aSAndroid Build Coastguard Worker for dst in dst_list: 268*6dbdd20aSAndroid Build Coastguard Worker flat_deps.append((src, dst)) 269*6dbdd20aSAndroid Build Coastguard Worker return flat_deps 270*6dbdd20aSAndroid Build Coastguard Worker 271*6dbdd20aSAndroid Build Coastguard Worker 272*6dbdd20aSAndroid Build Coastguard Workerdef get_node_modules(graph): 273*6dbdd20aSAndroid Build Coastguard Worker """Infers the dependencies onto NPM packages (node_modules) 274*6dbdd20aSAndroid Build Coastguard Worker 275*6dbdd20aSAndroid Build Coastguard Worker An import is guessed to be a node module if doesn't contain any . or .. in the 276*6dbdd20aSAndroid Build Coastguard Worker path, and optionally starts with @. 277*6dbdd20aSAndroid Build Coastguard Worker """ 278*6dbdd20aSAndroid Build Coastguard Worker node_modules = set() 279*6dbdd20aSAndroid Build Coastguard Worker for _, imports in graph.items(): 280*6dbdd20aSAndroid Build Coastguard Worker for dst in imports: 281*6dbdd20aSAndroid Build Coastguard Worker if re.match(r'^[@a-z][a-z0-9-_/]+$', dst): 282*6dbdd20aSAndroid Build Coastguard Worker node_modules.add(dst) 283*6dbdd20aSAndroid Build Coastguard Worker return node_modules 284*6dbdd20aSAndroid Build Coastguard Worker 285*6dbdd20aSAndroid Build Coastguard Worker 286*6dbdd20aSAndroid Build Coastguard Workerdef check_one_import(src, dst, allowlist, plugin_declared_deps, node_modules): 287*6dbdd20aSAndroid Build Coastguard Worker # Translate node_module deps into the wildcard '%node_modules%' so it can be 288*6dbdd20aSAndroid Build Coastguard Worker # treated as a single entity. 289*6dbdd20aSAndroid Build Coastguard Worker if dst in node_modules: 290*6dbdd20aSAndroid Build Coastguard Worker dst = NODE_MODULES 291*6dbdd20aSAndroid Build Coastguard Worker 292*6dbdd20aSAndroid Build Coastguard Worker # Always allow imports from the same directory or its own subdirectories. 293*6dbdd20aSAndroid Build Coastguard Worker src_dir = '/'.join(src.split('/')[:-1]) 294*6dbdd20aSAndroid Build Coastguard Worker dst_dir = '/'.join(dst.split('/')[:-1]) 295*6dbdd20aSAndroid Build Coastguard Worker if dst_dir.startswith(src_dir): 296*6dbdd20aSAndroid Build Coastguard Worker return True 297*6dbdd20aSAndroid Build Coastguard Worker 298*6dbdd20aSAndroid Build Coastguard Worker # Match against the (flattened) allowlist. 299*6dbdd20aSAndroid Build Coastguard Worker for rule_src, rule_dst in allowlist: 300*6dbdd20aSAndroid Build Coastguard Worker if fnmatch.fnmatch(src, rule_src) and fnmatch.fnmatch(dst, rule_dst): 301*6dbdd20aSAndroid Build Coastguard Worker return True 302*6dbdd20aSAndroid Build Coastguard Worker 303*6dbdd20aSAndroid Build Coastguard Worker # Check inter-plugin deps. 304*6dbdd20aSAndroid Build Coastguard Worker src_plugin = get_plugin_path(src) 305*6dbdd20aSAndroid Build Coastguard Worker dst_plugin = get_plugin_path(dst) 306*6dbdd20aSAndroid Build Coastguard Worker extra_err = '' 307*6dbdd20aSAndroid Build Coastguard Worker if src_plugin is not None and dst_plugin is not None: 308*6dbdd20aSAndroid Build Coastguard Worker if src_plugin == dst_plugin: 309*6dbdd20aSAndroid Build Coastguard Worker # Allow a plugin to depends on arbitrary subdirectories of itself. 310*6dbdd20aSAndroid Build Coastguard Worker return True 311*6dbdd20aSAndroid Build Coastguard Worker # Check if there is a dependency declared by plugins, via 312*6dbdd20aSAndroid Build Coastguard Worker # static readonly dependencies = [DstPlugin] 313*6dbdd20aSAndroid Build Coastguard Worker declared_deps = plugin_declared_deps.get(src_plugin, set()) 314*6dbdd20aSAndroid Build Coastguard Worker extra_err = '(plugin deps: %s)' % ','.join(declared_deps) 315*6dbdd20aSAndroid Build Coastguard Worker if dst_plugin in declared_deps: 316*6dbdd20aSAndroid Build Coastguard Worker return True 317*6dbdd20aSAndroid Build Coastguard Worker print('Import not allowed %s -> %s %s' % (src, dst, extra_err)) 318*6dbdd20aSAndroid Build Coastguard Worker return False 319*6dbdd20aSAndroid Build Coastguard Worker 320*6dbdd20aSAndroid Build Coastguard Worker 321*6dbdd20aSAndroid Build Coastguard Workerdef do_check(_options, graph): 322*6dbdd20aSAndroid Build Coastguard Worker result = 0 323*6dbdd20aSAndroid Build Coastguard Worker rules = flatten_rules(DEPS_ALLOWLIST) 324*6dbdd20aSAndroid Build Coastguard Worker node_modules = get_node_modules(graph) 325*6dbdd20aSAndroid Build Coastguard Worker 326*6dbdd20aSAndroid Build Coastguard Worker # Build a map of depencies declared between plugin. The maps looks like: 327*6dbdd20aSAndroid Build Coastguard Worker # 'Foo' -> {'Bar', 'Baz'} # Foo declares a dependency on Bar and Baz 328*6dbdd20aSAndroid Build Coastguard Worker plugin_declared_deps = collections.defaultdict(set) 329*6dbdd20aSAndroid Build Coastguard Worker for path in all_source_files(): 330*6dbdd20aSAndroid Build Coastguard Worker for src_plugin, dst_plugin in find_plugin_declared_deps(path): 331*6dbdd20aSAndroid Build Coastguard Worker plugin_declared_deps[src_plugin].add(dst_plugin) 332*6dbdd20aSAndroid Build Coastguard Worker 333*6dbdd20aSAndroid Build Coastguard Worker for src, imports in graph.items(): 334*6dbdd20aSAndroid Build Coastguard Worker for dst in imports: 335*6dbdd20aSAndroid Build Coastguard Worker if not check_one_import(src, dst, rules, plugin_declared_deps, 336*6dbdd20aSAndroid Build Coastguard Worker node_modules): 337*6dbdd20aSAndroid Build Coastguard Worker result = 1 338*6dbdd20aSAndroid Build Coastguard Worker return result 339*6dbdd20aSAndroid Build Coastguard Worker 340*6dbdd20aSAndroid Build Coastguard Worker 341*6dbdd20aSAndroid Build Coastguard Workerdef do_desc(options, graph): 342*6dbdd20aSAndroid Build Coastguard Worker print('Rules:') 343*6dbdd20aSAndroid Build Coastguard Worker for rule in flatten_rules(DEPS_ALLOWLIST): 344*6dbdd20aSAndroid Build Coastguard Worker print(' - %s' % rule) 345*6dbdd20aSAndroid Build Coastguard Worker 346*6dbdd20aSAndroid Build Coastguard Worker 347*6dbdd20aSAndroid Build Coastguard Workerdef do_print(options, graph): 348*6dbdd20aSAndroid Build Coastguard Worker for node, edges in graph.items(): 349*6dbdd20aSAndroid Build Coastguard Worker for edge in edges: 350*6dbdd20aSAndroid Build Coastguard Worker print("{}\t{}".format(node, edge)) 351*6dbdd20aSAndroid Build Coastguard Worker 352*6dbdd20aSAndroid Build Coastguard Worker 353*6dbdd20aSAndroid Build Coastguard Workerdef do_dot(options, graph): 354*6dbdd20aSAndroid Build Coastguard Worker 355*6dbdd20aSAndroid Build Coastguard Worker def simplify(path): 356*6dbdd20aSAndroid Build Coastguard Worker if is_external_dep(path): 357*6dbdd20aSAndroid Build Coastguard Worker return path 358*6dbdd20aSAndroid Build Coastguard Worker return os.path.dirname(path) 359*6dbdd20aSAndroid Build Coastguard Worker 360*6dbdd20aSAndroid Build Coastguard Worker new_graph = collections.defaultdict(set) 361*6dbdd20aSAndroid Build Coastguard Worker for node, edges in graph.items(): 362*6dbdd20aSAndroid Build Coastguard Worker for edge in edges: 363*6dbdd20aSAndroid Build Coastguard Worker new_graph[simplify(edge)] 364*6dbdd20aSAndroid Build Coastguard Worker new_graph[simplify(node)].add(simplify(edge)) 365*6dbdd20aSAndroid Build Coastguard Worker graph = new_graph 366*6dbdd20aSAndroid Build Coastguard Worker 367*6dbdd20aSAndroid Build Coastguard Worker if options.ignore_external: 368*6dbdd20aSAndroid Build Coastguard Worker new_graph = collections.defaultdict(set) 369*6dbdd20aSAndroid Build Coastguard Worker for node, edges in graph.items(): 370*6dbdd20aSAndroid Build Coastguard Worker if is_external_dep(node): 371*6dbdd20aSAndroid Build Coastguard Worker continue 372*6dbdd20aSAndroid Build Coastguard Worker for edge in edges: 373*6dbdd20aSAndroid Build Coastguard Worker if is_external_dep(edge): 374*6dbdd20aSAndroid Build Coastguard Worker continue 375*6dbdd20aSAndroid Build Coastguard Worker new_graph[edge] 376*6dbdd20aSAndroid Build Coastguard Worker new_graph[node].add(edge) 377*6dbdd20aSAndroid Build Coastguard Worker graph = new_graph 378*6dbdd20aSAndroid Build Coastguard Worker 379*6dbdd20aSAndroid Build Coastguard Worker write_dot(graph, sys.stdout) 380*6dbdd20aSAndroid Build Coastguard Worker return 0 381*6dbdd20aSAndroid Build Coastguard Worker 382*6dbdd20aSAndroid Build Coastguard Worker 383*6dbdd20aSAndroid Build Coastguard Workerdef main(): 384*6dbdd20aSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=__doc__) 385*6dbdd20aSAndroid Build Coastguard Worker parser.set_defaults(func=do_check) 386*6dbdd20aSAndroid Build Coastguard Worker subparsers = parser.add_subparsers() 387*6dbdd20aSAndroid Build Coastguard Worker 388*6dbdd20aSAndroid Build Coastguard Worker check_command = subparsers.add_parser( 389*6dbdd20aSAndroid Build Coastguard Worker 'check', help='Check the rules (default)') 390*6dbdd20aSAndroid Build Coastguard Worker check_command.set_defaults(func=do_check) 391*6dbdd20aSAndroid Build Coastguard Worker 392*6dbdd20aSAndroid Build Coastguard Worker desc_command = subparsers.add_parser('desc', help='Print the rules') 393*6dbdd20aSAndroid Build Coastguard Worker desc_command.set_defaults(func=do_desc) 394*6dbdd20aSAndroid Build Coastguard Worker 395*6dbdd20aSAndroid Build Coastguard Worker print_command = subparsers.add_parser('print', help='Print all imports') 396*6dbdd20aSAndroid Build Coastguard Worker print_command.set_defaults(func=do_print) 397*6dbdd20aSAndroid Build Coastguard Worker 398*6dbdd20aSAndroid Build Coastguard Worker dot_command = subparsers.add_parser( 399*6dbdd20aSAndroid Build Coastguard Worker 'dot', 400*6dbdd20aSAndroid Build Coastguard Worker help='Output dependency graph in dot format suitble for use in ' + 401*6dbdd20aSAndroid Build Coastguard Worker 'graphviz (e.g. ./tools/check_imports dot | dot -Tpng -ograph.png)') 402*6dbdd20aSAndroid Build Coastguard Worker dot_command.set_defaults(func=do_dot) 403*6dbdd20aSAndroid Build Coastguard Worker dot_command.add_argument( 404*6dbdd20aSAndroid Build Coastguard Worker '--ignore-external', 405*6dbdd20aSAndroid Build Coastguard Worker action='store_true', 406*6dbdd20aSAndroid Build Coastguard Worker help='Don\'t show external dependencies', 407*6dbdd20aSAndroid Build Coastguard Worker ) 408*6dbdd20aSAndroid Build Coastguard Worker 409*6dbdd20aSAndroid Build Coastguard Worker # This is a general import graph of the form /plugins/foo/index -> /base/hash 410*6dbdd20aSAndroid Build Coastguard Worker graph = collections.defaultdict(set) 411*6dbdd20aSAndroid Build Coastguard Worker 412*6dbdd20aSAndroid Build Coastguard Worker # Build the dep graph 413*6dbdd20aSAndroid Build Coastguard Worker for path in all_source_files(): 414*6dbdd20aSAndroid Build Coastguard Worker for src, target, _ in find_imports(path): 415*6dbdd20aSAndroid Build Coastguard Worker graph[src].add(target) 416*6dbdd20aSAndroid Build Coastguard Worker graph[target] 417*6dbdd20aSAndroid Build Coastguard Worker 418*6dbdd20aSAndroid Build Coastguard Worker options = parser.parse_args() 419*6dbdd20aSAndroid Build Coastguard Worker return options.func(options, graph) 420*6dbdd20aSAndroid Build Coastguard Worker 421*6dbdd20aSAndroid Build Coastguard Worker 422*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__': 423*6dbdd20aSAndroid Build Coastguard Worker sys.exit(main()) 424