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