blob: 4b21069d7400d3442d30d461fe63361ab2bd0dcb [file] [log] [blame]
Primiano Tucci11d94e12022-08-02 17:44:33 +01001#!/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())