xref: /aosp_15_r20/external/perfetto/tools/gen_amalgamated_python_tools (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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    'python/tools/install_test_reporter_app.py': 'tools/install_test_reporter_app',
31}
32
33
34def amalgamate_file(fname, stack=None, done=None, in_progress=None):
35  stack = [] if stack is None else stack
36  done = set() if done is None else done
37  in_progress = set() if in_progress is None else in_progress
38  if fname in in_progress:
39    cycle = ' > '.join(stack + [fname])
40    logging.fatal('Cycle detected in %s', cycle)
41    sys.exit(1)
42  if fname in done:
43    return []
44  logging.debug('Processing %s', fname)
45  done.add(fname)
46  in_progress.add(fname)
47  with open(fname, encoding='utf-8') as f:
48    lines = f.readlines()
49  outlines = []
50  for line in lines:
51    if line.startswith('from perfetto') or line.startswith('import perfetto'):
52      if not re.match('from perfetto[.][.\w]+\s+import\s+[*]$', line):
53        logging.fatal('Error in %s on line \"%s\"', fname, line.rstrip())
54        logging.fatal('Only "from perfetto.foo import *" is supported in '
55                      'sources that are used in //tools and get amalgamated')
56        sys.exit(1)
57      pkg = line.split()[1]
58      fpath = os.path.join('python', pkg.replace('.', os.sep) + '.py')
59      outlines.append('\n# ----- Amalgamator: begin of %s\n' % fpath)
60      outlines += amalgamate_file(fpath, stack + [fname], done, in_progress)
61      outlines.append('\n# ----- Amalgamator: end of %s\n' % fpath)
62    elif '__file__' in line and not 'amalgamator:nocheck' in line:
63      logging.fatal('__file__ is not allowed in sources that get amalgamated.'
64                    'In %s on line \"%s\"', fname, line.rstrip())
65      sys.exit(1)
66
67    else:
68      outlines.append(line)
69  in_progress.remove(fname)
70  logging.debug('%s: %d lines', fname, len(outlines))
71  return outlines
72
73
74def amalgamate(src, dst, check_only=False):
75  lines = amalgamate_file(src)
76  banner = '''
77# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
78# DO NOT EDIT. Auto-generated by %s
79# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
80'''
81  lines.insert(lines.index('\n'), banner % os.path.relpath(__file__, ROOT_DIR))
82  new_content = ''.join(lines)
83
84  if check_only:
85    if not os.path.exists(dst):
86      return False
87    with open(dst, encoding='utf-8') as f:
88      return f.read() == new_content
89
90  logging.info('Amalgamating %s -> %s', src, dst)
91  with open(dst + '.tmp', 'w', encoding='utf-8') as f:
92    f.write(new_content)
93  os.chmod(dst + '.tmp', 0o755)
94  os.rename(dst + '.tmp', dst)
95  return True
96
97
98def main():
99  check_only = '--check-only' in sys.argv
100  logging.basicConfig(
101    format='%(levelname)-8s: %(message)s',
102    level=logging.DEBUG if '-v' in sys.argv else logging.INFO)
103  os.chdir(ROOT_DIR)  # Make the execution cwd-independent.
104  success = True
105  for src, dst in AMALGAMATION_MAP.items():
106    success = success and amalgamate(src, dst, check_only)
107  return 0 if success else 1
108
109
110if __name__ == '__main__':
111  sys.exit(main())
112