1 /* <lambda>null2 * Copyright (C) 2019 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 */ 16 17 package com.android.protolog.tool 18 19 import java.util.regex.Pattern 20 21 class CommandOptions(args: Array<String>) { 22 companion object { 23 const val TRANSFORM_CALLS_CMD = "transform-protolog-calls" 24 const val GENERATE_CONFIG_CMD = "generate-viewer-config" 25 const val READ_LOG_CMD = "read-log" 26 private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD) 27 28 // TODO: This is always the same. I don't think it's required 29 private const val PROTOLOG_CLASS_PARAM = "--protolog-class" 30 private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class" 31 private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar" 32 private const val VIEWER_CONFIG_PARAM = "--viewer-config" 33 private const val VIEWER_CONFIG_TYPE_PARAM = "--viewer-config-type" 34 private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar" 35 private const val VIEWER_CONFIG_FILE_PATH_PARAM = "--viewer-config-file-path" 36 // TODO(b/324128613): Remove these legacy options once we fully flip the Perfetto protolog flag 37 private const val LEGACY_VIEWER_CONFIG_FILE_PATH_PARAM = "--legacy-viewer-config-file-path" 38 private const val LEGACY_OUTPUT_FILE_PATH = "--legacy-output-file-path" 39 private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGGROUP_CLASS_PARAM, 40 PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_PARAM, VIEWER_CONFIG_TYPE_PARAM, 41 OUTPUT_SOURCE_JAR_PARAM, VIEWER_CONFIG_FILE_PATH_PARAM, 42 LEGACY_VIEWER_CONFIG_FILE_PATH_PARAM, LEGACY_OUTPUT_FILE_PATH) 43 44 val USAGE = """ 45 Usage: ${Constants.NAME} <command> [<args>] 46 Available commands: 47 48 $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> 49 $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> 50 $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>] 51 - processes java files replacing stub calls with logging code. 52 53 $GENERATE_CONFIG_CMD $PROTOLOG_CLASS_PARAM <class name> 54 $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> 55 $VIEWER_CONFIG_PARAM <viewer.json|viewer.pb> [<input.java>] 56 - creates viewer config file from given java files. 57 58 $READ_LOG_CMD $VIEWER_CONFIG_PARAM <viewer.json|viewer.pb> <wm_log.pb> 59 - translates a binary log to a readable format. 60 """.trimIndent() 61 62 private fun validateClassName(name: String): String { 63 if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9$]+)$", name)) { 64 throw InvalidCommandException("Invalid class name $name") 65 } 66 return name 67 } 68 69 private fun getParam(paramName: String, params: Map<String, String>): String { 70 if (!params.containsKey(paramName)) { 71 throw InvalidCommandException("Param $paramName required") 72 } 73 return params.getValue(paramName) 74 } 75 76 private fun getOptionalParam(paramName: String, params: Map<String, String>): String? { 77 if (!params.containsKey(paramName)) { 78 return null 79 } 80 return params.getValue(paramName) 81 } 82 83 private fun validateNotSpecified(paramName: String, params: Map<String, String>): String { 84 if (params.containsKey(paramName)) { 85 throw InvalidCommandException("Unsupported param $paramName") 86 } 87 return "" 88 } 89 90 private fun validateJarName(name: String): String { 91 if (!name.endsWith(".jar")) { 92 throw InvalidCommandException("Jar file required, got $name instead") 93 } 94 return name 95 } 96 97 private fun validateSrcJarName(name: String): String { 98 if (!name.endsWith(".srcjar")) { 99 throw InvalidCommandException("Source jar file required, got $name instead") 100 } 101 return name 102 } 103 104 private fun validateViewerConfigFilePath(name: String): String { 105 if (!name.endsWith(".pb")) { 106 throw InvalidCommandException("Proto file (ending with .pb) required, " + 107 "got $name instead") 108 } 109 return name 110 } 111 112 private fun validateLegacyViewerConfigFilePath(name: String): String { 113 if (!name.endsWith(".json.gz")) { 114 throw InvalidCommandException("GZiped Json file (ending with .json.gz) required, " + 115 "got $name instead") 116 } 117 return name 118 } 119 120 private fun validateOutputFilePath(name: String): String { 121 if (!name.endsWith(".winscope")) { 122 throw InvalidCommandException("Winscope file (ending with .winscope) required, " + 123 "got $name instead") 124 } 125 return name 126 } 127 128 private fun validateConfigFileName(name: String): String { 129 if (!name.endsWith(".json") && !name.endsWith(".pb")) { 130 throw InvalidCommandException("Json file (ending with .json) or proto file " + 131 "(ending with .pb) required, got $name instead") 132 } 133 return name 134 } 135 136 private fun validateConfigType(name: String): String { 137 val validType = listOf("json", "proto") 138 if (!validType.contains(name)) { 139 throw InvalidCommandException("Unexpected config file type. " + 140 "Expected on of [${validType.joinToString()}], but got $name") 141 } 142 return name 143 } 144 145 private fun validateJavaInputList(list: List<String>): List<String> { 146 if (list.isEmpty()) { 147 throw InvalidCommandException("No java source input files") 148 } 149 list.forEach { name -> 150 if (!name.endsWith(".java") && !name.endsWith(".kt")) { 151 throw InvalidCommandException("Not a java or kotlin source file $name") 152 } 153 } 154 return list 155 } 156 157 private fun validateLogInputList(list: List<String>): String { 158 if (list.isEmpty()) { 159 throw InvalidCommandException("No log input file") 160 } 161 if (list.size > 1) { 162 throw InvalidCommandException("Only one log input file allowed") 163 } 164 return list[0] 165 } 166 } 167 168 val protoLogClassNameArg: String 169 val protoLogGroupsClassNameArg: String 170 val protoLogGroupsJarArg: String 171 val viewerConfigFileNameArg: String 172 val viewerConfigTypeArg: String 173 val outputSourceJarArg: String 174 val logProtofileArg: String 175 val viewerConfigFilePathArg: String 176 val legacyViewerConfigFilePathArg: String? 177 val legacyOutputFilePath: String? 178 val javaSourceArgs: List<String> 179 val command: String 180 181 init { 182 if (args.isEmpty()) { 183 throw InvalidCommandException("No command specified.") 184 } 185 command = args[0] 186 if (command !in commands) { 187 throw InvalidCommandException("Unknown command.") 188 } 189 190 val params: MutableMap<String, String> = mutableMapOf() 191 val inputFiles: MutableList<String> = mutableListOf() 192 193 var idx = 1 194 while (idx < args.size) { 195 if (args[idx].startsWith("--")) { 196 if (idx + 1 >= args.size) { 197 throw InvalidCommandException("No value for ${args[idx]}") 198 } 199 if (args[idx] !in parameters) { 200 throw InvalidCommandException("Unknown parameter ${args[idx]}") 201 } 202 if (args[idx + 1].startsWith("--")) { 203 throw InvalidCommandException("No value for ${args[idx]}") 204 } 205 if (params.containsKey(args[idx])) { 206 throw InvalidCommandException("Duplicated parameter ${args[idx]}") 207 } 208 params[args[idx]] = args[idx + 1] 209 idx += 2 210 } else { 211 inputFiles.add(args[idx]) 212 idx += 1 213 } 214 } 215 216 when (command) { 217 TRANSFORM_CALLS_CMD -> { 218 protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params)) 219 protoLogGroupsClassNameArg = 220 validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM, params)) 221 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params)) 222 viewerConfigFileNameArg = validateNotSpecified(VIEWER_CONFIG_PARAM, params) 223 viewerConfigTypeArg = validateNotSpecified(VIEWER_CONFIG_TYPE_PARAM, params) 224 outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params)) 225 viewerConfigFilePathArg = validateViewerConfigFilePath( 226 getParam(VIEWER_CONFIG_FILE_PATH_PARAM, params)) 227 legacyViewerConfigFilePathArg = 228 getOptionalParam(LEGACY_VIEWER_CONFIG_FILE_PATH_PARAM, params)?.let { 229 validateLegacyViewerConfigFilePath(it) 230 } 231 legacyOutputFilePath = 232 getOptionalParam(LEGACY_OUTPUT_FILE_PATH, params)?.let { 233 validateOutputFilePath(it) 234 } 235 javaSourceArgs = validateJavaInputList(inputFiles) 236 logProtofileArg = "" 237 } 238 GENERATE_CONFIG_CMD -> { 239 protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params)) 240 protoLogGroupsClassNameArg = 241 validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM, params)) 242 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params)) 243 viewerConfigFileNameArg = 244 validateConfigFileName(getParam(VIEWER_CONFIG_PARAM, params)) 245 viewerConfigTypeArg = validateConfigType(getParam(VIEWER_CONFIG_TYPE_PARAM, params)) 246 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params) 247 viewerConfigFilePathArg = 248 validateNotSpecified(VIEWER_CONFIG_FILE_PATH_PARAM, params) 249 legacyViewerConfigFilePathArg = 250 validateNotSpecified(LEGACY_VIEWER_CONFIG_FILE_PATH_PARAM, params) 251 legacyOutputFilePath = validateNotSpecified(LEGACY_OUTPUT_FILE_PATH, params) 252 javaSourceArgs = validateJavaInputList(inputFiles) 253 logProtofileArg = "" 254 } 255 READ_LOG_CMD -> { 256 protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params) 257 protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params) 258 protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params) 259 viewerConfigFileNameArg = 260 validateConfigFileName(getParam(VIEWER_CONFIG_PARAM, params)) 261 viewerConfigTypeArg = validateNotSpecified(VIEWER_CONFIG_TYPE_PARAM, params) 262 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params) 263 viewerConfigFilePathArg = 264 validateNotSpecified(VIEWER_CONFIG_FILE_PATH_PARAM, params) 265 legacyViewerConfigFilePathArg = 266 validateNotSpecified(LEGACY_VIEWER_CONFIG_FILE_PATH_PARAM, params) 267 legacyOutputFilePath = validateNotSpecified(LEGACY_OUTPUT_FILE_PATH, params) 268 javaSourceArgs = listOf() 269 logProtofileArg = validateLogInputList(inputFiles) 270 } 271 else -> { 272 throw InvalidCommandException("Unknown command.") 273 } 274 } 275 } 276 } 277