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