xref: /aosp_15_r20/frameworks/base/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.hoststubgen
17 
18 import com.android.hoststubgen.filters.FilterPolicy
19 import java.io.BufferedReader
20 import java.io.FileReader
21 
22 /**
23  * A single value that can only set once.
24  */
25 open class SetOnce<T>(private var value: T) {
26     class SetMoreThanOnceException : Exception()
27 
28     private var set = false
29 
30     fun set(v: T): T {
31         if (set) {
32             throw SetMoreThanOnceException()
33         }
34         if (v == null) {
35             throw NullPointerException("This shouldn't happen")
36         }
37         set = true
38         value = v
39         return v
40     }
41 
42     val get: T
43         get() = this.value
44 
45     val isSet: Boolean
46         get() = this.set
47 
48     fun <R> ifSet(block: (T & Any) -> R): R? {
49         if (isSet) {
50             return block(value!!)
51         }
52         return null
53     }
54 
55     override fun toString(): String {
56         return "$value"
57     }
58 }
59 
60 class IntSetOnce(value: Int) : SetOnce<Int>(value) {
setnull61     fun set(v: String): Int {
62         try {
63             return this.set(v.toInt())
64         } catch (e: NumberFormatException) {
65             throw ArgumentsException("Invalid integer $v")
66         }
67     }
68 }
69 
70 /**
71  * Options that can be set from command line arguments.
72  */
73 class HostStubGenOptions(
74         /** Input jar file*/
75         var inJar: SetOnce<String> = SetOnce(""),
76 
77         /** Output jar file */
78         var outJar: SetOnce<String?> = SetOnce(null),
79 
80         var inputJarDumpFile: SetOnce<String?> = SetOnce(null),
81 
82         var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null),
83 
84         var keepAnnotations: MutableSet<String> = mutableSetOf(),
85         var throwAnnotations: MutableSet<String> = mutableSetOf(),
86         var removeAnnotations: MutableSet<String> = mutableSetOf(),
87         var ignoreAnnotations: MutableSet<String> = mutableSetOf(),
88         var keepClassAnnotations: MutableSet<String> = mutableSetOf(),
89         var redirectAnnotations: MutableSet<String> = mutableSetOf(),
90 
91         var substituteAnnotations: MutableSet<String> = mutableSetOf(),
92         var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(),
93         var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(),
94         var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(),
95 
96         var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
97 
98         var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null),
99 
100         var defaultClassLoadHook: SetOnce<String?> = SetOnce(null),
101         var defaultMethodCallHook: SetOnce<String?> = SetOnce(null),
102 
103         var policyOverrideFiles: MutableList<String> = mutableListOf(),
104 
105         var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
106 
107         var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
108 
109         var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
110         var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
111         var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
112 
113         var statsFile: SetOnce<String?> = SetOnce(null),
114 
115         var apiListFile: SetOnce<String?> = SetOnce(null),
116 
117         var numShards: IntSetOnce = IntSetOnce(1),
118         var shard: IntSetOnce = IntSetOnce(0),
119 ) {
120     companion object {
121 
parsePackageRedirectnull122         private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
123             val colon = fromColonTo.indexOf(':')
124             if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
125                 throw ArgumentsException("--package-redirect must be a colon-separated string")
126             }
127             // TODO check for duplicates
128             return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1))
129         }
130 
parseArgsnull131         fun parseArgs(args: Array<String>): HostStubGenOptions {
132             val ret = HostStubGenOptions()
133 
134             val ai = ArgIterator.withAtFiles(args)
135 
136             var allAnnotations = mutableSetOf<String>()
137 
138             fun ensureUniqueAnnotation(name: String): String {
139                 if (!allAnnotations.add(name)) {
140                     throw DuplicateAnnotationException(ai.current)
141                 }
142                 return name
143             }
144 
145             while (true) {
146                 val arg = ai.nextArgOptional() ?: break
147 
148                 // Define some shorthands...
149                 fun nextArg(): String = ai.nextArgRequired(arg)
150                 fun MutableSet<String>.addUniqueAnnotationArg(): String =
151                         nextArg().also { this += ensureUniqueAnnotation(it) }
152 
153                 if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
154                     continue
155                 }
156                 try {
157                     when (arg) {
158                         // TODO: Write help
159                         "-h", "--help" -> TODO("Help is not implemented yet")
160 
161                         "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
162                         // We support both arguments because some AOSP dependencies
163                         // still use the old argument
164                         "--out-jar", "--out-impl-jar" -> ret.outJar.set(nextArg())
165 
166                         "--policy-override-file" ->
167                             ret.policyOverrideFiles.add(nextArg().ensureFileExists())
168 
169                         "--clean-up-on-error" -> ret.cleanUpOnError.set(true)
170                         "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
171 
172                         "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove)
173                         "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw)
174                         "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep)
175 
176                         "--keep-annotation" ->
177                             ret.keepAnnotations.addUniqueAnnotationArg()
178 
179                         "--keep-class-annotation" ->
180                             ret.keepClassAnnotations.addUniqueAnnotationArg()
181 
182                         "--throw-annotation" ->
183                             ret.throwAnnotations.addUniqueAnnotationArg()
184 
185                         "--remove-annotation" ->
186                             ret.removeAnnotations.addUniqueAnnotationArg()
187 
188                         "--ignore-annotation" ->
189                             ret.ignoreAnnotations.addUniqueAnnotationArg()
190 
191                         "--substitute-annotation" ->
192                             ret.substituteAnnotations.addUniqueAnnotationArg()
193 
194                         "--redirect-annotation" ->
195                             ret.redirectAnnotations.addUniqueAnnotationArg()
196 
197                         "--redirection-class-annotation" ->
198                             ret.redirectionClassAnnotations.addUniqueAnnotationArg()
199 
200                         "--class-load-hook-annotation" ->
201                             ret.classLoadHookAnnotations.addUniqueAnnotationArg()
202 
203                         "--keep-static-initializer-annotation" ->
204                             ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg()
205 
206                         "--package-redirect" ->
207                             ret.packageRedirects += parsePackageRedirect(nextArg())
208 
209                         "--annotation-allowed-classes-file" ->
210                             ret.annotationAllowedClassesFile.set(nextArg())
211 
212                         "--default-class-load-hook" ->
213                             ret.defaultClassLoadHook.set(nextArg())
214 
215                         "--default-method-call-hook" ->
216                             ret.defaultMethodCallHook.set(nextArg())
217 
218                         "--gen-keep-all-file" ->
219                             ret.inputJarAsKeepAllFile.set(nextArg())
220 
221                         // Following options are for debugging.
222                         "--enable-class-checker" -> ret.enableClassChecker.set(true)
223                         "--no-class-checker" -> ret.enableClassChecker.set(false)
224 
225                         "--enable-pre-trace" -> ret.enablePreTrace.set(true)
226                         "--no-pre-trace" -> ret.enablePreTrace.set(false)
227 
228                         "--enable-post-trace" -> ret.enablePostTrace.set(true)
229                         "--no-post-trace" -> ret.enablePostTrace.set(false)
230 
231                         "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
232 
233                         "--stats-file" -> ret.statsFile.set(nextArg())
234                         "--supported-api-list-file" -> ret.apiListFile.set(nextArg())
235 
236                         "--num-shards" -> ret.numShards.set(nextArg()).also {
237                             if (it < 1) {
238                                 throw ArgumentsException("$arg must be positive integer")
239                             }
240                         }
241                         "--shard-index" -> ret.shard.set(nextArg()).also {
242                             if (it < 0) {
243                                 throw ArgumentsException("$arg must be positive integer or zero")
244                             }
245                         }
246 
247                         else -> throw ArgumentsException("Unknown option: $arg")
248                     }
249                 } catch (e: SetOnce.SetMoreThanOnceException) {
250                     throw ArgumentsException("Duplicate or conflicting argument found: $arg")
251                 }
252             }
253 
254             if (!ret.inJar.isSet) {
255                 throw ArgumentsException("Required option missing: --in-jar")
256             }
257             if (!ret.outJar.isSet) {
258                 log.w("--out-jar is not set. $executableName will not generate jar files.")
259             }
260             if (ret.numShards.isSet != ret.shard.isSet) {
261                 throw ArgumentsException("--num-shards and --shard-index must be used together")
262             }
263 
264             if (ret.numShards.isSet) {
265                 if (ret.shard.get >= ret.numShards.get) {
266                     throw ArgumentsException("--shard-index must be smaller than --num-shards")
267                 }
268             }
269 
270             return ret
271         }
272     }
273 
toStringnull274     override fun toString(): String {
275         return """
276             HostStubGenOptions{
277               inJar='$inJar',
278               outJar='$outJar',
279               inputJarDumpFile=$inputJarDumpFile,
280               inputJarAsKeepAllFile=$inputJarAsKeepAllFile,
281               keepAnnotations=$keepAnnotations,
282               throwAnnotations=$throwAnnotations,
283               removeAnnotations=$removeAnnotations,
284               ignoreAnnotations=$ignoreAnnotations,
285               keepClassAnnotations=$keepClassAnnotations,
286               substituteAnnotations=$substituteAnnotations,
287               nativeSubstituteAnnotations=$redirectionClassAnnotations,
288               classLoadHookAnnotations=$classLoadHookAnnotations,
289               keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations,
290               packageRedirects=$packageRedirects,
291               annotationAllowedClassesFile=$annotationAllowedClassesFile,
292               defaultClassLoadHook=$defaultClassLoadHook,
293               defaultMethodCallHook=$defaultMethodCallHook,
294               policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
295               defaultPolicy=$defaultPolicy,
296               cleanUpOnError=$cleanUpOnError,
297               enableClassChecker=$enableClassChecker,
298               enablePreTrace=$enablePreTrace,
299               enablePostTrace=$enablePostTrace,
300               statsFile=$statsFile,
301               apiListFile=$apiListFile,
302               numShards=$numShards,
303               shard=$shard,
304             }
305             """.trimIndent()
306     }
307 }
308 
309 class ArgIterator(
310     private val args: List<String>,
311     private var currentIndex: Int = -1
312 ) {
313     val current: String
314         get() = args.get(currentIndex)
315 
316     /**
317      * Get the next argument, or [null] if there's no more arguments.
318      */
nextArgOptionalnull319     fun nextArgOptional(): String? {
320         if ((currentIndex + 1) >= args.size) {
321             return null
322         }
323         return args.get(++currentIndex)
324     }
325 
326     /**
327      * Get the next argument, or throw if
328      */
nextArgRequirednull329     fun nextArgRequired(argName: String): String {
330         nextArgOptional().let {
331             if (it == null) {
332                 throw ArgumentsException("Missing parameter for option $argName")
333             }
334             if (it.isEmpty()) {
335                 throw ArgumentsException("Parameter can't be empty for option $argName")
336             }
337             return it
338         }
339     }
340 
341     companion object {
withAtFilesnull342         fun withAtFiles(args: Array<String>): ArgIterator {
343             return ArgIterator(expandAtFiles(args))
344         }
345     }
346 }
347 
348 /**
349  * Scan the arguments, and if any of them starts with an `@`, then load from the file
350  * and use its content as arguments.
351  *
352  * In this file, each line is treated as a single argument.
353  *
354  * The file can contain '#' as comments.
355  */
expandAtFilesnull356 private fun expandAtFiles(args: Array<String>): List<String> {
357     val ret = mutableListOf<String>()
358 
359     args.forEach { arg ->
360         if (!arg.startsWith('@')) {
361             ret += arg
362             return@forEach
363         }
364         // Read from the file, and add each line to the result.
365         val filename = arg.substring(1).ensureFileExists()
366 
367         log.v("Expanding options file $filename")
368 
369         BufferedReader(FileReader(filename)).use { reader ->
370             while (true) {
371                 var line = reader.readLine()
372                 if (line == null) {
373                     break // EOF
374                 }
375 
376                 line = normalizeTextLine(line)
377                 if (line.isNotEmpty()) {
378                     ret += line
379                 }
380             }
381         }
382     }
383     return ret
384 }
385