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