1// Copyright (C) 2020 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Generation of reference from protos 16 17'use strict'; 18 19const protobufjs = require('protobufjs'); 20const fs = require('fs'); 21const path = require('path'); 22const argv = require('yargs').argv 23 24const PROJECT_ROOT = 25 path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); 26 27const visited = {}; 28 29 30// Removes \n due to 80col wrapping and preserves only end-of-sentence line 31// breaks. 32function singleLineComment(comment) { 33 comment = comment || ''; 34 comment = comment.trim(); 35 comment = comment.replace(/\.\n/g, '<br>'); 36 comment = comment.replace(/\n/g, ' '); 37 return comment; 38} 39 40function getFullName(pType) { 41 let cur = pType; 42 let name = pType.name; 43 while (cur && cur.parent != cur && cur.parent instanceof protobufjs.Type) { 44 name = `${cur.parent.name}.${name}`; 45 cur = cur.parent; 46 } 47 return name; 48} 49 50function genType(pType, depth) { 51 depth = depth || 0; 52 console.assert(pType instanceof protobufjs.ReflectionObject); 53 const fullName = getFullName(pType); 54 if (fullName in visited) 55 return ''; 56 visited[fullName] = true; 57 58 const heading = '#' + 59 '#'.repeat(Math.min(depth, 2)); 60 const anchor = depth > 0 ? `{#${fullName}} ` : ''; 61 let md = `${heading} ${anchor}${fullName}`; 62 md += '\n'; 63 const fileName = path.basename(pType.filename); 64 const relPath = path.relative(PROJECT_ROOT, pType.filename); 65 md += `${(pType.comment || '').replace(/(\n)?^\s*next.*\bid:.*$/img, '')}`; 66 md += `\n\nDefined in [${fileName}](/${relPath})\n\n`; 67 68 const subTypes = []; 69 70 if (pType instanceof protobufjs.Enum) { 71 md += '#### Enum values:\n'; 72 md += 'Name | Value | Description\n'; 73 md += '---- | ----- | -----------\n'; 74 for (const enumName of Object.keys(pType.values)) { 75 const enumVal = pType.values[enumName]; 76 const comment = singleLineComment(pType.comments[enumName]); 77 md += `${enumName} | ${enumVal} | ${comment}\n` 78 } 79 } else { 80 md += '#### Fields:\n'; 81 md += 'Field | Type | Description\n'; 82 md += '----- | ---- | -----------\n'; 83 84 for (const fieldName in pType.fields) { 85 const field = pType.fields[fieldName]; 86 let type = field.type; 87 if (field.repeated) { 88 type = `${type}[]`; 89 } 90 if (field.resolvedType) { 91 // The TraceConfig proto is linked from the TracePacket reference. 92 // Instead of recursing and generating the TraceConfig types all over 93 // again, just link to the dedicated TraceConfig reference page. 94 if (getFullName(field.resolvedType) === 'TraceConfig') { 95 type = `[${type}](/docs/reference/trace-config-proto.autogen)`; 96 } else { 97 subTypes.push(field.resolvedType); 98 type = `[${type}](#${getFullName(field.resolvedType)})`; 99 } 100 } 101 md += `${fieldName} | ${type} | ${singleLineComment(field.comment)}\n` 102 } 103 } 104 md += '\n\n\n\n'; 105 106 for (const subType of subTypes) 107 md += genType(subType, depth + 1); 108 109 return md; 110} 111 112 113function main() { 114 const inProtoFile = argv['i']; 115 const protoName = argv['p']; 116 const outFile = argv['o']; 117 if (!inProtoFile || !protoName) { 118 console.error('Usage: -i input.proto -p protos.RootType [-o out.md]'); 119 process.exit(1); 120 } 121 122 const parser = new protobufjs.Root(); 123 parser.resolvePath = (_, target) => { 124 if (target == inProtoFile) { 125 // The root proto file passed from the cmdline will be relative to the 126 // root_build_dir (out/xxx) (e.g.: ../../protos/config) 127 return inProtoFile; 128 } 129 // All the other imports, instead, will be relative to the project root 130 // (e.g. protos/config/...) 131 return path.join(PROJECT_ROOT, target); 132 }; 133 134 const cfg = parser.loadSync( 135 inProtoFile, {alternateCommentMode: true, keepCase: true}); 136 cfg.resolveAll(); 137 const traceConfig = cfg.lookup(protoName); 138 const generatedMd = genType(traceConfig); 139 if (outFile) { 140 fs.writeFileSync(outFile, generatedMd); 141 } else { 142 console.log(generatedMd); 143 } 144 process.exit(0); 145} 146 147main(); 148