1#!/usr/bin/env python3 2# Copyright (C) 2022 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import os 17import sys 18import logging 19import re 20 21ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 23AMALGAMATION_MAP = { 24 'python/tools/record_android_trace.py': 'tools/record_android_trace', 25 'python/tools/tracebox.py': 'tools/tracebox', 26 'python/tools/traceconv.py': 'tools/traceconv', 27 'python/tools/trace_processor.py': 'tools/trace_processor', 28 'python/tools/cpu_profile.py': 'tools/cpu_profile', 29 'python/tools/heap_profile.py': 'tools/heap_profile', 30} 31 32 33def amalgamate_file(fname, stack=None, done=None, in_progress=None): 34 stack = [] if stack is None else stack 35 done = set() if done is None else done 36 in_progress = set() if in_progress is None else in_progress 37 if fname in in_progress: 38 cycle = ' > '.join(stack + [fname]) 39 logging.fatal('Cycle detected in %s', cycle) 40 sys.exit(1) 41 if fname in done: 42 return [] 43 logging.debug('Processing %s', fname) 44 done.add(fname) 45 in_progress.add(fname) 46 with open(fname, encoding='utf-8') as f: 47 lines = f.readlines() 48 outlines = [] 49 for line in lines: 50 if line.startswith('from perfetto') or line.startswith('import perfetto'): 51 if not re.match('from perfetto[.][.\w]+\s+import\s+[*]$', line): 52 logging.fatal('Error in %s on line \"%s\"', fname, line.rstrip()) 53 logging.fatal('Only "from perfetto.foo import *" is supported in ' 54 'sources that are used in //tools and get amalgamated') 55 sys.exit(1) 56 pkg = line.split()[1] 57 fpath = os.path.join('python', pkg.replace('.', os.sep) + '.py') 58 outlines.append('\n# ----- Amalgamator: begin of %s\n' % fpath) 59 outlines += amalgamate_file(fpath, stack + [fname], done, in_progress) 60 outlines.append('\n# ----- Amalgamator: end of %s\n' % fpath) 61 elif '__file__' in line and not 'amalgamator:nocheck' in line: 62 logging.fatal('__file__ is not allowed in sources that get amalgamated.' 63 'In %s on line \"%s\"', fname, line.rstrip()) 64 sys.exit(1) 65 66 else: 67 outlines.append(line) 68 in_progress.remove(fname) 69 logging.debug('%s: %d lines', fname, len(outlines)) 70 return outlines 71 72 73def amalgamate(src, dst, check_only=False): 74 lines = amalgamate_file(src) 75 banner = ''' 76# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 77# DO NOT EDIT. Auto-generated by %s 78# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 79''' 80 lines.insert(lines.index('\n'), banner % os.path.relpath(__file__, ROOT_DIR)) 81 new_content = ''.join(lines) 82 83 if check_only: 84 if not os.path.exists(dst): 85 return False 86 with open(dst, encoding='utf-8') as f: 87 return f.read() == new_content 88 89 logging.info('Amalgamating %s -> %s', src, dst) 90 with open(dst + '.tmp', 'w', encoding='utf-8') as f: 91 f.write(new_content) 92 os.chmod(dst + '.tmp', 0o755) 93 os.rename(dst + '.tmp', dst) 94 return True 95 96 97def main(): 98 check_only = '--check-only' in sys.argv 99 logging.basicConfig( 100 format='%(levelname)-8s: %(message)s', 101 level=logging.DEBUG if '-v' in sys.argv else logging.INFO) 102 os.chdir(ROOT_DIR) # Make the execution cwd-independent. 103 success = True 104 for src, dst in AMALGAMATION_MAP.items(): 105 success = success and amalgamate(src, dst, check_only) 106 return 0 if success else 1 107 108 109if __name__ == '__main__': 110 sys.exit(main()) 111