Primiano Tucci | 11d94e1 | 2022-08-02 17:44:33 +0100 | [diff] [blame] | 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 | |
| 16 | import os |
| 17 | import sys |
| 18 | import logging |
| 19 | import re |
| 20 | |
| 21 | ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 22 | |
| 23 | AMALGAMATION_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 | |
| 33 | def 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 | |
| 73 | def 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 | |
| 97 | def 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 | |
| 109 | if __name__ == '__main__': |
| 110 | sys.exit(main()) |