xref: /aosp_15_r20/external/perfetto/tools/gen_stdlib_docs_json.py (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 argparse
17import os
18import sys
19import json
20from collections import defaultdict
21from typing import Dict
22
23ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24sys.path.append(os.path.join(ROOT_DIR))
25
26from python.generators.sql_processing.docs_parse import parse_file
27
28
29def _summary_desc(s: str) -> str:
30  return s.split('. ')[0].replace('\n', ' ')
31
32
33def main():
34  parser = argparse.ArgumentParser()
35  parser.add_argument('--json-out', required=True)
36  parser.add_argument('--input-list-file')
37  parser.add_argument('--minify')
38  parser.add_argument('sql_files', nargs='*')
39  args = parser.parse_args()
40
41  if args.input_list_file and args.sql_files:
42    print("Only one of --input-list-file and list of SQL files expected")
43    return 1
44
45  sql_files = []
46  if args.input_list_file:
47    with open(args.input_list_file, 'r') as input_list_file:
48      for line in input_list_file.read().splitlines():
49        sql_files.append(line)
50  else:
51    sql_files = args.sql_files
52
53  # Unfortunately we cannot pass this in as an arg as soong does not provide
54  # us a way to get the path to the Perfetto source directory. This fails on
55  # empty path but it's a price worth paying to have to use gross hacks in
56  # Soong.
57  root_dir = os.path.commonpath(sql_files)
58
59  # Extract the SQL output from each file.
60  sql_outputs: Dict[str, str] = {}
61  for file_name in sql_files:
62    with open(file_name, 'r') as f:
63      relpath = os.path.relpath(file_name, root_dir)
64
65      # We've had bugs (e.g. b/264711057) when Soong's common path logic breaks
66      # and ends up with a bunch of ../ prefixing the path: disallow any ../
67      # as this should never be a valid in our C++ output.
68      assert '../' not in relpath
69
70      sql_outputs[relpath] = f.read()
71
72  packages = defaultdict(list)
73  # Add documentation from each file
74  for path, sql in sql_outputs.items():
75    package_name = path.split("/")[0]
76    module_name = path.split(".sql")[0].replace("/", ".")
77
78    docs = parse_file(path, sql)
79
80    # Some modules (i.e `deprecated`) should not generate docs.
81    if not docs:
82      continue
83
84    if len(docs.errors) > 0:
85      for e in docs.errors:
86        print(e)
87      return 1
88
89    module_dict = {
90        'module_name':
91            module_name,
92        'data_objects': [{
93            'name':
94                table.name,
95            'desc':
96                table.desc,
97            'summary_desc':
98                _summary_desc(table.desc),
99            'type':
100                table.type,
101            'cols': [{
102                'name': col_name,
103                'type': col.long_type,
104                'desc': col.description
105            } for (col_name, col) in table.cols.items()]
106        } for table in docs.table_views],
107        'functions': [{
108            'name': function.name,
109            'desc': function.desc,
110            'summary_desc': _summary_desc(function.desc),
111            'args': [{
112                'name': arg_name,
113                'type': arg.long_type,
114                'desc': arg.description,
115            } for (arg_name, arg) in function.args.items()],
116            'return_type': function.return_type,
117            'return_desc': function.return_desc,
118        } for function in docs.functions],
119        'table_functions': [{
120            'name':
121                function.name,
122            'desc':
123                function.desc,
124            'summary_desc':
125                _summary_desc(function.desc),
126            'args': [{
127                'name': arg_name,
128                'type': arg.long_type,
129                'desc': arg.description,
130            } for (arg_name, arg) in function.args.items()],
131            'cols': [{
132                'name': col_name,
133                'type': col.long_type,
134                'desc': col.description
135            } for (col_name, col) in function.cols.items()]
136        } for function in docs.table_functions],
137        'macros': [{
138            'name':
139                macro.name,
140            'desc':
141                macro.desc,
142            'summary_desc':
143                _summary_desc(macro.desc),
144            'return_desc':
145                macro.return_desc,
146            'return_type':
147                macro.return_type,
148            'args': [{
149                'name': arg_name,
150                'type': arg.long_type,
151                'desc': arg.description,
152            } for (arg_name, arg) in macro.args.items()],
153        } for macro in docs.macros],
154    }
155    packages[package_name].append(module_dict)
156
157  packages_list = [{
158      "name": name,
159      "modules": modules
160  } for name, modules in packages.items()]
161
162  with open(args.json_out, 'w+') as f:
163    json.dump(packages_list, f, indent=None if args.minify else 4)
164
165  return 0
166
167
168if __name__ == '__main__':
169  sys.exit(main())
170