diff options
49 files changed, 789 insertions, 793 deletions
diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp index a5ff4964b0a4..004834eed983 100644 --- a/ravenwood/tools/hoststubgen/Android.bp +++ b/ravenwood/tools/hoststubgen/Android.bp @@ -90,11 +90,9 @@ java_library { java_library_host { name: "hoststubgen-lib", defaults: ["ravenwood-internal-only-visibility-java"], - srcs: ["src/**/*.kt"], + srcs: ["lib/**/*.kt"], static_libs: [ "hoststubgen-helper-runtime", - ], - libs: [ "junit", "ow2-asm", "ow2-asm-analysis", @@ -108,15 +106,8 @@ java_library_host { java_binary_host { name: "hoststubgen", main_class: "com.android.hoststubgen.HostStubGenMain", - static_libs: [ - "hoststubgen-lib", - "junit", - "ow2-asm", - "ow2-asm-analysis", - "ow2-asm-commons", - "ow2-asm-tree", - "ow2-asm-util", - ], + srcs: ["src/**/*.kt"], + static_libs: ["hoststubgen-lib"], visibility: ["//visibility:public"], } diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt index f59e143c1e4e..f59e143c1e4e 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt new file mode 100644 index 000000000000..4fe21eac6972 --- /dev/null +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen + +import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.filters.AnnotationBasedFilter +import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter +import com.android.hoststubgen.filters.ConstantFilter +import com.android.hoststubgen.filters.DefaultHookInjectingFilter +import com.android.hoststubgen.filters.FilterRemapper +import com.android.hoststubgen.filters.ImplicitOutputFilter +import com.android.hoststubgen.filters.KeepNativeFilter +import com.android.hoststubgen.filters.OutputFilter +import com.android.hoststubgen.filters.SanitizationFilter +import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder +import com.android.hoststubgen.utils.ClassPredicate +import com.android.hoststubgen.visitors.BaseAdapter +import com.android.hoststubgen.visitors.PackageRedirectRemapper +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.util.CheckClassAdapter + +/** + * This class implements bytecode transformation of HostStubGen. + */ +class HostStubGenClassProcessor( + private val options: HostStubGenClassProcessorOptions, + private val allClasses: ClassNodes, + private val errors: HostStubGenErrors = HostStubGenErrors(), + private val stats: HostStubGenStats? = null, +) { + val filter = buildFilter() + val remapper = FilterRemapper(filter) + + private val packageRedirector = PackageRedirectRemapper(options.packageRedirects) + + /** + * Build the filter, which decides what classes/methods/fields should be put in stub or impl + * jars, and "how". (e.g. with substitution?) + */ + private fun buildFilter(): OutputFilter { + // We build a "chain" of multiple filters here. + // + // The filters are build in from "inside", meaning the first filter created here is + // the last filter used, so it has the least precedence. + // + // So, for example, the "remove" annotation, which is handled by AnnotationBasedFilter, + // can override a class-wide annotation, which is handled by + // ClassWidePolicyPropagatingFilter, and any annotations can be overridden by the + // text-file based filter, which is handled by parseTextFilterPolicyFile. + + // The first filter is for the default policy from the command line options. + var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") + + // Next, we build a filter that preserves all native methods by default + filter = KeepNativeFilter(allClasses, filter) + + // Next, we need a filter that resolves "class-wide" policies. + // This is used when a member (methods, fields, nested classes) don't get any policies + // from upper filters. e.g. when a method has no annotations, then this filter will apply + // the class-wide policy, if any. (if not, we'll fall back to the above filter.) + filter = ClassWidePolicyPropagatingFilter(allClasses, filter) + + // Inject default hooks from options. + filter = DefaultHookInjectingFilter( + allClasses, + options.defaultClassLoadHook.get, + options.defaultMethodCallHook.get, + filter + ) + + val annotationAllowedPredicate = options.annotationAllowedClassesFile.get.let { file -> + if (file == null) { + ClassPredicate.newConstantPredicate(true) // Allow all classes + } else { + ClassPredicate.loadFromFile(file, false) + } + } + + // Next, Java annotation based filter. + val annotFilter = AnnotationBasedFilter( + errors, + allClasses, + options.keepAnnotations, + options.keepClassAnnotations, + options.throwAnnotations, + options.removeAnnotations, + options.ignoreAnnotations, + options.substituteAnnotations, + options.redirectAnnotations, + options.redirectionClassAnnotations, + options.classLoadHookAnnotations, + options.partiallyAllowedAnnotations, + options.keepStaticInitializerAnnotations, + annotationAllowedPredicate, + filter + ) + filter = annotFilter + + // Next, "text based" filter, which allows to override policies without touching + // the target code. + if (options.policyOverrideFiles.isNotEmpty()) { + val builder = TextFileFilterPolicyBuilder(allClasses, filter) + options.policyOverrideFiles.forEach(builder::parse) + filter = builder.createOutputFilter() + annotFilter.annotationAllowedMembers = builder.annotationAllowedMembersFilter + } + + // Apply the implicit filter. + filter = ImplicitOutputFilter(errors, allClasses, filter) + + // Add a final sanitization step. + filter = SanitizationFilter(errors, allClasses, filter) + + return filter + } + + fun processClassBytecode(bytecode: ByteArray): ByteArray { + val cr = ClassReader(bytecode) + + // COMPUTE_FRAMES wouldn't be happy if code uses + val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES + val cw = ClassWriter(flags) + + // Connect to the class writer + var outVisitor: ClassVisitor = cw + if (options.enableClassChecker.get) { + outVisitor = CheckClassAdapter(outVisitor) + } + + // Remapping should happen at the end. + outVisitor = ClassRemapper(outVisitor, remapper) + + val visitorOptions = BaseAdapter.Options( + errors = errors, + stats = stats, + enablePreTrace = options.enablePreTrace.get, + enablePostTrace = options.enablePostTrace.get, + deleteClassFinals = options.deleteFinals.get, + deleteMethodFinals = options.deleteFinals.get, + ) + outVisitor = BaseAdapter.getVisitor( + cr.className, allClasses, outVisitor, filter, + packageRedirector, visitorOptions + ) + + cr.accept(outVisitor, ClassReader.EXPAND_FRAMES) + return cw.toByteArray() + } +} diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt new file mode 100644 index 000000000000..c7c45e65a8b6 --- /dev/null +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen + +import com.android.hoststubgen.filters.FilterPolicy +import com.android.hoststubgen.utils.ArgIterator +import com.android.hoststubgen.utils.BaseOptions +import com.android.hoststubgen.utils.SetOnce + +private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> { + val colon = fromColonTo.indexOf(':') + if ((colon < 1) || (colon + 1 >= fromColonTo.length)) { + throw ArgumentsException("--package-redirect must be a colon-separated string") + } + // TODO check for duplicates + return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1)) +} + +/** + * Options to configure [HostStubGenClassProcessor]. + */ +open class HostStubGenClassProcessorOptions( + var keepAnnotations: MutableSet<String> = mutableSetOf(), + var throwAnnotations: MutableSet<String> = mutableSetOf(), + var removeAnnotations: MutableSet<String> = mutableSetOf(), + var ignoreAnnotations: MutableSet<String> = mutableSetOf(), + var keepClassAnnotations: MutableSet<String> = mutableSetOf(), + var partiallyAllowedAnnotations: MutableSet<String> = mutableSetOf(), + var redirectAnnotations: MutableSet<String> = mutableSetOf(), + + var substituteAnnotations: MutableSet<String> = mutableSetOf(), + var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(), + var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(), + var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(), + + var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(), + + var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null), + + var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), + var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), + + var policyOverrideFiles: MutableList<String> = mutableListOf(), + + var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), + + var deleteFinals: SetOnce<Boolean> = SetOnce(false), + + var enableClassChecker: SetOnce<Boolean> = SetOnce(false), + var enablePreTrace: SetOnce<Boolean> = SetOnce(false), + var enablePostTrace: SetOnce<Boolean> = SetOnce(false), +) : BaseOptions() { + + private val allAnnotations = mutableSetOf<String>() + + private fun ensureUniqueAnnotation(name: String): String { + if (!allAnnotations.add(name)) { + throw DuplicateAnnotationException(name) + } + return name + } + + override fun parseOption(option: String, ai: ArgIterator): Boolean { + // Define some shorthands... + fun nextArg(): String = ai.nextArgRequired(option) + fun MutableSet<String>.addUniqueAnnotationArg(): String = + nextArg().also { this += ensureUniqueAnnotation(it) } + + when (option) { + "--policy-override-file" -> + policyOverrideFiles.add(nextArg().ensureFileExists()) + + "--default-remove" -> defaultPolicy.set(FilterPolicy.Remove) + "--default-throw" -> defaultPolicy.set(FilterPolicy.Throw) + "--default-keep" -> defaultPolicy.set(FilterPolicy.Keep) + + "--keep-annotation" -> + keepAnnotations.addUniqueAnnotationArg() + + "--keep-class-annotation" -> + keepClassAnnotations.addUniqueAnnotationArg() + + "--partially-allowed-annotation" -> + partiallyAllowedAnnotations.addUniqueAnnotationArg() + + "--throw-annotation" -> + throwAnnotations.addUniqueAnnotationArg() + + "--remove-annotation" -> + removeAnnotations.addUniqueAnnotationArg() + + "--ignore-annotation" -> + ignoreAnnotations.addUniqueAnnotationArg() + + "--substitute-annotation" -> + substituteAnnotations.addUniqueAnnotationArg() + + "--redirect-annotation" -> + redirectAnnotations.addUniqueAnnotationArg() + + "--redirection-class-annotation" -> + redirectionClassAnnotations.addUniqueAnnotationArg() + + "--class-load-hook-annotation" -> + classLoadHookAnnotations.addUniqueAnnotationArg() + + "--keep-static-initializer-annotation" -> + keepStaticInitializerAnnotations.addUniqueAnnotationArg() + + "--package-redirect" -> + packageRedirects += parsePackageRedirect(nextArg()) + + "--annotation-allowed-classes-file" -> + annotationAllowedClassesFile.set(nextArg()) + + "--default-class-load-hook" -> + defaultClassLoadHook.set(nextArg()) + + "--default-method-call-hook" -> + defaultMethodCallHook.set(nextArg()) + + "--delete-finals" -> deleteFinals.set(true) + + // Following options are for debugging. + "--enable-class-checker" -> enableClassChecker.set(true) + "--no-class-checker" -> enableClassChecker.set(false) + + "--enable-pre-trace" -> enablePreTrace.set(true) + "--no-pre-trace" -> enablePreTrace.set(false) + + "--enable-post-trace" -> enablePostTrace.set(true) + "--no-post-trace" -> enablePostTrace.set(false) + + else -> return false + } + + return true + } + + override fun dumpFields(): String { + return """ + keepAnnotations=$keepAnnotations, + throwAnnotations=$throwAnnotations, + removeAnnotations=$removeAnnotations, + ignoreAnnotations=$ignoreAnnotations, + keepClassAnnotations=$keepClassAnnotations, + partiallyAllowedAnnotations=$partiallyAllowedAnnotations, + substituteAnnotations=$substituteAnnotations, + nativeSubstituteAnnotations=$redirectionClassAnnotations, + classLoadHookAnnotations=$classLoadHookAnnotations, + keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations, + packageRedirects=$packageRedirects, + annotationAllowedClassesFile=$annotationAllowedClassesFile, + defaultClassLoadHook=$defaultClassLoadHook, + defaultMethodCallHook=$defaultMethodCallHook, + policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()}, + defaultPolicy=$defaultPolicy, + deleteFinals=$deleteFinals, + enableClassChecker=$enableClassChecker, + enablePreTrace=$enablePreTrace, + enablePostTrace=$enablePostTrace, + """.trimIndent() + } +} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenErrors.kt index a218c5599553..a218c5599553 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenErrors.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenLogger.kt index 4bcee409aaec..4bcee409aaec 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenLogger.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt index 9045db210495..9045db210495 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt index 10179eefcb95..b2af7827f8c5 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt @@ -15,6 +15,8 @@ */ package com.android.hoststubgen +import java.io.PrintWriter + /** * Name of this executable. Set it in the main method. */ @@ -96,3 +98,23 @@ class ParseException : Exception, UserErrorException { fun csvEscape(value: String): String { return "\"" + value.replace("\"", "\"\"") + "\"" } + +inline fun runMainWithBoilerplate(realMain: () -> Unit) { + var success = false + + try { + realMain() + + success = true + } catch (e: Throwable) { + log.e("$executableName: Error: ${e.message}") + if (e !is UserErrorException) { + e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error))) + } + } finally { + log.i("$executableName finished") + log.flush() + } + + System.exit(if (success) 0 else 1 ) +} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt index b41ce0f65017..b41ce0f65017 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt index e2647eb13ed3..e2647eb13ed3 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt index 5e4e70f0cbaa..5e4e70f0cbaa 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 6b360b79c327..b2252092f923 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -66,7 +66,7 @@ private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean { || className.endsWith("/FeatureFlags") || className.endsWith("/FeatureFlagsImpl") || className.endsWith("/CustomFeatureFlags") - || className.endsWith("/FakeFeatureFlagsImpl"); + || className.endsWith("/FakeFeatureFlagsImpl") } /** diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index 73c72a21ef7b..73c72a21ef7b 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/AnnotationBasedFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt index f8bb526d0a86..f8bb526d0a86 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ConstantFilter.kt index be3c59c80152..be3c59c80152 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ConstantFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt index aaf49c154a17..aaf49c154a17 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt index b8b0d8a31268..b8b0d8a31268 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt index 81c26ffdf1f4..81c26ffdf1f4 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt index b10165b835f2..b10165b835f2 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterRemapper.kt index bba4681d3838..bba4681d3838 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterRemapper.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 474da6dfa1b9..474da6dfa1b9 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt index fc885d6f463b..fc885d6f463b 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt index 00e7d77fa6e7..00e7d77fa6e7 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt index f99ce906240a..f99ce906240a 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/PackageFilter.kt index c67e6714d4c2..c67e6714d4c2 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/PackageFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SanitizationFilter.kt index 4375c6500b62..4375c6500b62 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SanitizationFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SubclassFilter.kt index fd7474b55fa6..fd7474b55fa6 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/SubclassFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index dd353e9caeff..dd353e9caeff 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt index a3f934cacc2c..a3f934cacc2c 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt index bc90d1248322..bc90d1248322 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassPredicate.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassPredicate.kt index 4c53bc8fba97..4c53bc8fba97 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassPredicate.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassPredicate.kt diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt new file mode 100644 index 000000000000..0b17879b862c --- /dev/null +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen.utils + +import com.android.hoststubgen.ArgumentsException +import com.android.hoststubgen.ensureFileExists +import com.android.hoststubgen.log +import com.android.hoststubgen.normalizeTextLine +import java.io.BufferedReader +import java.io.FileReader + +/** + * Base class for parsing arguments from commandline. + */ +abstract class BaseOptions { + /** + * Parse all arguments. + * + * This method should remain final. For customization in subclasses, override [parseOption]. + */ + fun parseArgs(args: List<String>) { + val ai = ArgIterator.withAtFiles(args) + while (true) { + val arg = ai.nextArgOptional() ?: break + + if (log.maybeHandleCommandLineArg(arg) { ai.nextArgRequired(arg) }) { + continue + } + try { + if (!parseOption(arg, ai)) { + throw ArgumentsException("Unknown option: $arg") + } + } catch (e: SetOnce.SetMoreThanOnceException) { + throw ArgumentsException("Duplicate or conflicting argument found: $arg") + } + } + + checkArgs() + } + + /** + * Print out all fields in this class. + * + * This method should remain final. For customization in subclasses, override [dumpFields]. + */ + final override fun toString(): String { + val fields = dumpFields().prependIndent(" ") + return "${this::class.simpleName} {\n$fields\n}" + } + + /** + * Check whether the parsed options are in a correct state. + * + * This method is called as the last step in [parseArgs]. + */ + open fun checkArgs() {} + + /** + * Parse a single option. Return true if the option is accepted, otherwise return false. + * + * Subclasses override/extend this method to support more options. + */ + abstract fun parseOption(option: String, ai: ArgIterator): Boolean + + abstract fun dumpFields(): String +} + +class ArgIterator( + private val args: List<String>, + private var currentIndex: Int = -1 +) { + val current: String + get() = args[currentIndex] + + /** + * Get the next argument, or [null] if there's no more arguments. + */ + fun nextArgOptional(): String? { + if ((currentIndex + 1) >= args.size) { + return null + } + return args[++currentIndex] + } + + /** + * Get the next argument, or throw if + */ + fun nextArgRequired(argName: String): String { + nextArgOptional().let { + if (it == null) { + throw ArgumentsException("Missing parameter for option $argName") + } + if (it.isEmpty()) { + throw ArgumentsException("Parameter can't be empty for option $argName") + } + return it + } + } + + companion object { + fun withAtFiles(args: List<String>): ArgIterator { + return ArgIterator(expandAtFiles(args)) + } + + /** + * Scan the arguments, and if any of them starts with an `@`, then load from the file + * and use its content as arguments. + * + * In order to pass an argument that starts with an '@', use '@@' instead. + * + * In this file, each line is treated as a single argument. + * + * The file can contain '#' as comments. + */ + private fun expandAtFiles(args: List<String>): List<String> { + val ret = mutableListOf<String>() + + args.forEach { arg -> + if (arg.startsWith("@@")) { + ret += arg.substring(1) + return@forEach + } else if (!arg.startsWith('@')) { + ret += arg + return@forEach + } + // Read from the file, and add each line to the result. + val filename = arg.substring(1).ensureFileExists() + + log.v("Expanding options file $filename") + + BufferedReader(FileReader(filename)).use { reader -> + while (true) { + var line = reader.readLine() ?: break // EOF + + line = normalizeTextLine(line) + if (line.isNotEmpty()) { + ret += line + } + } + } + } + return ret + } + } +} + +/** + * A single value that can only set once. + */ +open class SetOnce<T>(private var value: T) { + class SetMoreThanOnceException : Exception() + + private var set = false + + fun set(v: T): T { + if (set) { + throw SetMoreThanOnceException() + } + if (v == null) { + throw NullPointerException("This shouldn't happen") + } + set = true + value = v + return v + } + + val get: T + get() = this.value + + val isSet: Boolean + get() = this.set + + fun <R> ifSet(block: (T & Any) -> R): R? { + if (isSet) { + return block(value!!) + } + return null + } + + override fun toString(): String { + return "$value" + } +} + +class IntSetOnce(value: Int) : SetOnce<Int>(value) { + fun set(v: String): Int { + try { + return this.set(v.toInt()) + } catch (e: NumberFormatException) { + throw ArgumentsException("Invalid integer $v") + } + } +} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/Trie.kt index 1b3d79cddb8e..1b3d79cddb8e 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/Trie.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt index a08d1d605949..a08d1d605949 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt index 55d0c0e555f1..55d0c0e555f1 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/Helper.kt index dc4f26bdda34..dc4f26bdda34 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/Helper.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index b8a357668c2b..b8a357668c2b 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt index e90ecd7ef678..e90ecd7ef678 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt +++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 7e294ed652d3..333540573364 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -17,36 +17,15 @@ package com.android.hoststubgen import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.dumper.ApiDumper -import com.android.hoststubgen.filters.AnnotationBasedFilter -import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter -import com.android.hoststubgen.filters.ConstantFilter -import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.FilterPolicy -import com.android.hoststubgen.filters.FilterRemapper -import com.android.hoststubgen.filters.ImplicitOutputFilter -import com.android.hoststubgen.filters.KeepNativeFilter -import com.android.hoststubgen.filters.OutputFilter -import com.android.hoststubgen.filters.SanitizationFilter -import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder import com.android.hoststubgen.filters.printAsTextPolicy -import com.android.hoststubgen.utils.ClassPredicate -import com.android.hoststubgen.visitors.BaseAdapter -import com.android.hoststubgen.visitors.PackageRedirectRemapper import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.FileOutputStream -import java.io.InputStream -import java.io.OutputStream import java.io.PrintWriter import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.commons.ClassRemapper -import org.objectweb.asm.commons.Remapper -import org.objectweb.asm.util.CheckClassAdapter /** * Actual main class. @@ -76,21 +55,15 @@ class HostStubGen(val options: HostStubGenOptions) { } } - // Build the filters. - val filter = buildFilter(errors, allClasses, options) - - val filterRemapper = FilterRemapper(filter) + // Build the class processor + val processor = HostStubGenClassProcessor(options, allClasses, errors, stats) // Transform the jar. convert( options.inJar.get, options.outJar.get, - filter, + processor, options.enableClassChecker.get, - allClasses, - errors, - stats, - filterRemapper, options.numShards.get, options.shard.get, ) @@ -106,109 +79,20 @@ class HostStubGen(val options: HostStubGenOptions) { PrintWriter(it).use { pw -> // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed // framework-minus-apex.jar so that we can dump inherited methods from it. - ApiDumper(pw, allClasses, null, filter).dump() + ApiDumper(pw, allClasses, null, processor.filter).dump() } } } } /** - * Build the filter, which decides what classes/methods/fields should be put in stub or impl - * jars, and "how". (e.g. with substitution?) - */ - private fun buildFilter( - errors: HostStubGenErrors, - allClasses: ClassNodes, - options: HostStubGenOptions, - ): OutputFilter { - // We build a "chain" of multiple filters here. - // - // The filters are build in from "inside", meaning the first filter created here is - // the last filter used, so it has the least precedence. - // - // So, for example, the "remove" annotation, which is handled by AnnotationBasedFilter, - // can override a class-wide annotation, which is handled by - // ClassWidePolicyPropagatingFilter, and any annotations can be overridden by the - // text-file based filter, which is handled by parseTextFilterPolicyFile. - - // The first filter is for the default policy from the command line options. - var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") - - // Next, we build a filter that preserves all native methods by default - filter = KeepNativeFilter(allClasses, filter) - - // Next, we need a filter that resolves "class-wide" policies. - // This is used when a member (methods, fields, nested classes) don't get any polices - // from upper filters. e.g. when a method has no annotations, then this filter will apply - // the class-wide policy, if any. (if not, we'll fall back to the above filter.) - filter = ClassWidePolicyPropagatingFilter(allClasses, filter) - - // Inject default hooks from options. - filter = DefaultHookInjectingFilter( - allClasses, - options.defaultClassLoadHook.get, - options.defaultMethodCallHook.get, - filter - ) - - val annotationAllowedPredicate = options.annotationAllowedClassesFile.get.let { file -> - if (file == null) { - ClassPredicate.newConstantPredicate(true) // Allow all classes - } else { - ClassPredicate.loadFromFile(file, false) - } - } - - // Next, Java annotation based filter. - val annotFilter = AnnotationBasedFilter( - errors, - allClasses, - options.keepAnnotations, - options.keepClassAnnotations, - options.throwAnnotations, - options.removeAnnotations, - options.ignoreAnnotations, - options.substituteAnnotations, - options.redirectAnnotations, - options.redirectionClassAnnotations, - options.classLoadHookAnnotations, - options.partiallyAllowedAnnotations, - options.keepStaticInitializerAnnotations, - annotationAllowedPredicate, - filter - ) - filter = annotFilter - - // Next, "text based" filter, which allows to override polices without touching - // the target code. - if (options.policyOverrideFiles.isNotEmpty()) { - val builder = TextFileFilterPolicyBuilder(allClasses, filter) - options.policyOverrideFiles.forEach(builder::parse) - filter = builder.createOutputFilter() - annotFilter.annotationAllowedMembers = builder.annotationAllowedMembersFilter - } - - // Apply the implicit filter. - filter = ImplicitOutputFilter(errors, allClasses, filter) - - // Add a final sanitization step. - filter = SanitizationFilter(errors, allClasses, filter) - - return filter - } - - /** * Convert a JAR file into "stub" and "impl" JAR files. */ private fun convert( inJar: String, outJar: String?, - filter: OutputFilter, + processor: HostStubGenClassProcessor, enableChecker: Boolean, - classes: ClassNodes, - errors: HostStubGenErrors, - stats: HostStubGenStats, - remapper: Remapper?, numShards: Int, shard: Int ) { @@ -216,8 +100,6 @@ class HostStubGen(val options: HostStubGenOptions) { log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled") log.iTime("Transforming jar") { - val packageRedirector = PackageRedirectRemapper(options.packageRedirects) - var itemIndex = 0 var numItemsProcessed = 0 var numItems = -1 // == Unknown @@ -240,11 +122,7 @@ class HostStubGen(val options: HostStubGenOptions) { if (!inShard) { continue } - convertSingleEntry( - inZip, entry, outStream, filter, - packageRedirector, remapper, enableChecker, - classes, errors, stats - ) + convertSingleEntry(inZip, entry, outStream, processor) numItemsProcessed++ } log.i("Converted all entries.") @@ -270,13 +148,7 @@ class HostStubGen(val options: HostStubGenOptions) { inZip: ZipFile, entry: ZipEntry, outStream: ZipOutputStream?, - filter: OutputFilter, - packageRedirector: PackageRedirectRemapper, - remapper: Remapper?, - enableChecker: Boolean, - classes: ClassNodes, - errors: HostStubGenErrors, - stats: HostStubGenStats + processor: HostStubGenClassProcessor ) { log.d("Entry: %s", entry.name) log.withIndent { @@ -289,10 +161,7 @@ class HostStubGen(val options: HostStubGenOptions) { // If it's a class, convert it. if (name.endsWith(".class")) { - processSingleClass( - inZip, entry, outStream, filter, packageRedirector, - remapper, enableChecker, classes, errors, stats - ) + processSingleClass(inZip, entry, outStream, processor) return } @@ -332,29 +201,23 @@ class HostStubGen(val options: HostStubGenOptions) { } /** - * Convert a single class to "stub" and "impl". + * Convert a single class. */ private fun processSingleClass( inZip: ZipFile, entry: ZipEntry, outStream: ZipOutputStream?, - filter: OutputFilter, - packageRedirector: PackageRedirectRemapper, - remapper: Remapper?, - enableChecker: Boolean, - classes: ClassNodes, - errors: HostStubGenErrors, - stats: HostStubGenStats + processor: HostStubGenClassProcessor ) { val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "") - val classPolicy = filter.getPolicyForClass(classInternalName) + val classPolicy = processor.filter.getPolicyForClass(classInternalName) if (classPolicy.policy == FilterPolicy.Remove) { log.d("Removing class: %s %s", classInternalName, classPolicy) return } // If we're applying a remapper, we need to rename the file too. var newName = entry.name - remapper?.mapType(classInternalName)?.let { remappedName -> + processor.remapper.mapType(classInternalName)?.let { remappedName -> if (remappedName != classInternalName) { log.d("Renaming class file: %s -> %s", classInternalName, remappedName) newName = "$remappedName.class" @@ -367,64 +230,11 @@ class HostStubGen(val options: HostStubGenOptions) { BufferedInputStream(inZip.getInputStream(entry)).use { bis -> val newEntry = ZipEntry(newName) outStream.putNextEntry(newEntry) - convertClass( - classInternalName, bis, - outStream, filter, packageRedirector, remapper, - enableChecker, classes, errors, stats - ) + val classBytecode = bis.readAllBytes() + outStream.write(processor.processClassBytecode(classBytecode)) outStream.closeEntry() } } } } - - /** - * Convert a single class to either "stub" or "impl". - */ - private fun convertClass( - classInternalName: String, - input: InputStream, - out: OutputStream, - filter: OutputFilter, - packageRedirector: PackageRedirectRemapper, - remapper: Remapper?, - enableChecker: Boolean, - classes: ClassNodes, - errors: HostStubGenErrors, - stats: HostStubGenStats? - ) { - val cr = ClassReader(input) - - // COMPUTE_FRAMES wouldn't be happy if code uses - val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES - val cw = ClassWriter(flags) - - // Connect to the class writer - var outVisitor: ClassVisitor = cw - if (enableChecker) { - outVisitor = CheckClassAdapter(outVisitor) - } - - // Remapping should happen at the end. - remapper?.let { - outVisitor = ClassRemapper(outVisitor, remapper) - } - - val visitorOptions = BaseAdapter.Options( - errors = errors, - stats = stats, - enablePreTrace = options.enablePreTrace.get, - enablePostTrace = options.enablePostTrace.get, - deleteClassFinals = options.deleteFinals.get, - deleteMethodFinals = options.deleteFinals.get, - ) - outVisitor = BaseAdapter.getVisitor( - classInternalName, classes, outVisitor, filter, - packageRedirector, visitorOptions - ) - - cr.accept(outVisitor, ClassReader.EXPAND_FRAMES) - val data = cw.toByteArray() - out.write(data) - } } diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt index 85064661cd2b..4ba8c5c50059 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt @@ -17,8 +17,6 @@ package com.android.hoststubgen -import java.io.PrintWriter - /** * Entry point. */ @@ -26,10 +24,10 @@ fun main(args: Array<String>) { executableName = "HostStubGen" runMainWithBoilerplate { // Parse the command line arguments. - var clanupOnError = false + var cleanupOnError = false try { - val options = HostStubGenOptions.parseArgs(args) - clanupOnError = options.cleanUpOnError.get + val options = HostStubGenOptions().apply { parseArgs(args.asList()) } + cleanupOnError = options.cleanUpOnError.get log.v("$executableName started") log.v("Options: $options") @@ -37,30 +35,10 @@ fun main(args: Array<String>) { // Run. HostStubGen(options).run() } catch (e: Throwable) { - if (clanupOnError) { + if (cleanupOnError) { TODO("Remove output jars here") } throw e } } } - -inline fun runMainWithBoilerplate(realMain: () -> Unit) { - var success = false - - try { - realMain() - - success = true - } catch (e: Throwable) { - log.e("$executableName: Error: ${e.message}") - if (e !is UserErrorException) { - e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error))) - } - } finally { - log.i("$executableName finished") - log.flush() - } - - System.exit(if (success) 0 else 1 ) -} diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 1ab88d24ab28..8bb454fa12e7 100644 --- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -15,385 +15,102 @@ */ package com.android.hoststubgen -import com.android.hoststubgen.filters.FilterPolicy -import java.io.BufferedReader -import java.io.FileReader - -/** - * A single value that can only set once. - */ -open class SetOnce<T>(private var value: T) { - class SetMoreThanOnceException : Exception() - - private var set = false - - fun set(v: T): T { - if (set) { - throw SetMoreThanOnceException() - } - if (v == null) { - throw NullPointerException("This shouldn't happen") - } - set = true - value = v - return v - } - - val get: T - get() = this.value - - val isSet: Boolean - get() = this.set - - fun <R> ifSet(block: (T & Any) -> R): R? { - if (isSet) { - return block(value!!) - } - return null - } - - override fun toString(): String { - return "$value" - } -} - -class IntSetOnce(value: Int) : SetOnce<Int>(value) { - fun set(v: String): Int { - try { - return this.set(v.toInt()) - } catch (e: NumberFormatException) { - throw ArgumentsException("Invalid integer $v") - } - } -} +import com.android.hoststubgen.utils.ArgIterator +import com.android.hoststubgen.utils.IntSetOnce +import com.android.hoststubgen.utils.SetOnce /** * Options that can be set from command line arguments. */ class HostStubGenOptions( - /** Input jar file*/ - var inJar: SetOnce<String> = SetOnce(""), - - /** Output jar file */ - var outJar: SetOnce<String?> = SetOnce(null), - - var inputJarDumpFile: SetOnce<String?> = SetOnce(null), - - var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null), - - var keepAnnotations: MutableSet<String> = mutableSetOf(), - var throwAnnotations: MutableSet<String> = mutableSetOf(), - var removeAnnotations: MutableSet<String> = mutableSetOf(), - var ignoreAnnotations: MutableSet<String> = mutableSetOf(), - var keepClassAnnotations: MutableSet<String> = mutableSetOf(), - var partiallyAllowedAnnotations: MutableSet<String> = mutableSetOf(), - var redirectAnnotations: MutableSet<String> = mutableSetOf(), - - var substituteAnnotations: MutableSet<String> = mutableSetOf(), - var redirectionClassAnnotations: MutableSet<String> = mutableSetOf(), - var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(), - var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(), + /** Input jar file*/ + var inJar: SetOnce<String> = SetOnce(""), - var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(), + /** Output jar file */ + var outJar: SetOnce<String?> = SetOnce(null), - var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null), + var inputJarDumpFile: SetOnce<String?> = SetOnce(null), - var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), - var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), + var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null), - var policyOverrideFiles: MutableList<String> = mutableListOf(), + var cleanUpOnError: SetOnce<Boolean> = SetOnce(false), - var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), + var statsFile: SetOnce<String?> = SetOnce(null), - var cleanUpOnError: SetOnce<Boolean> = SetOnce(false), + var apiListFile: SetOnce<String?> = SetOnce(null), - var deleteFinals: SetOnce<Boolean> = SetOnce(false), + var numShards: IntSetOnce = IntSetOnce(1), + var shard: IntSetOnce = IntSetOnce(0), +) : HostStubGenClassProcessorOptions() { - var enableClassChecker: SetOnce<Boolean> = SetOnce(false), - var enablePreTrace: SetOnce<Boolean> = SetOnce(false), - var enablePostTrace: SetOnce<Boolean> = SetOnce(false), - - var statsFile: SetOnce<String?> = SetOnce(null), - - var apiListFile: SetOnce<String?> = SetOnce(null), - - var numShards: IntSetOnce = IntSetOnce(1), - var shard: IntSetOnce = IntSetOnce(0), -) { - companion object { - - private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> { - val colon = fromColonTo.indexOf(':') - if ((colon < 1) || (colon + 1 >= fromColonTo.length)) { - throw ArgumentsException("--package-redirect must be a colon-separated string") - } - // TODO check for duplicates - return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1)) + override fun checkArgs() { + if (!inJar.isSet) { + throw ArgumentsException("Required option missing: --in-jar") + } + if (!outJar.isSet) { + log.w("--out-jar is not set. $executableName will not generate jar files.") + } + if (numShards.isSet != shard.isSet) { + throw ArgumentsException("--num-shards and --shard-index must be used together") } - fun parseArgs(args: Array<String>): HostStubGenOptions { - val ret = HostStubGenOptions() - - val ai = ArgIterator.withAtFiles(args) - - var allAnnotations = mutableSetOf<String>() - - fun ensureUniqueAnnotation(name: String): String { - if (!allAnnotations.add(name)) { - throw DuplicateAnnotationException(ai.current) - } - return name + if (numShards.isSet) { + if (shard.get >= numShards.get) { + throw ArgumentsException("--shard-index must be smaller than --num-shards") } + } + } - while (true) { - val arg = ai.nextArgOptional() ?: break - - // Define some shorthands... - fun nextArg(): String = ai.nextArgRequired(arg) - fun MutableSet<String>.addUniqueAnnotationArg(): String = - nextArg().also { this += ensureUniqueAnnotation(it) } - - if (log.maybeHandleCommandLineArg(arg) { nextArg() }) { - continue - } - try { - when (arg) { - // TODO: Write help - "-h", "--help" -> TODO("Help is not implemented yet") - - "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists() - // We support both arguments because some AOSP dependencies - // still use the old argument - "--out-jar", "--out-impl-jar" -> ret.outJar.set(nextArg()) - - "--policy-override-file" -> - ret.policyOverrideFiles.add(nextArg().ensureFileExists()) - - "--clean-up-on-error" -> ret.cleanUpOnError.set(true) - "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false) - - "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove) - "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw) - "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep) - - "--keep-annotation" -> - ret.keepAnnotations.addUniqueAnnotationArg() - - "--keep-class-annotation" -> - ret.keepClassAnnotations.addUniqueAnnotationArg() - - "--partially-allowed-annotation" -> - ret.partiallyAllowedAnnotations.addUniqueAnnotationArg() - - "--throw-annotation" -> - ret.throwAnnotations.addUniqueAnnotationArg() - - "--remove-annotation" -> - ret.removeAnnotations.addUniqueAnnotationArg() - - "--ignore-annotation" -> - ret.ignoreAnnotations.addUniqueAnnotationArg() - - "--substitute-annotation" -> - ret.substituteAnnotations.addUniqueAnnotationArg() - - "--redirect-annotation" -> - ret.redirectAnnotations.addUniqueAnnotationArg() - - "--redirection-class-annotation" -> - ret.redirectionClassAnnotations.addUniqueAnnotationArg() - - "--class-load-hook-annotation" -> - ret.classLoadHookAnnotations.addUniqueAnnotationArg() - - "--keep-static-initializer-annotation" -> - ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg() - - "--package-redirect" -> - ret.packageRedirects += parsePackageRedirect(nextArg()) - - "--annotation-allowed-classes-file" -> - ret.annotationAllowedClassesFile.set(nextArg()) - - "--default-class-load-hook" -> - ret.defaultClassLoadHook.set(nextArg()) - - "--default-method-call-hook" -> - ret.defaultMethodCallHook.set(nextArg()) - - "--gen-keep-all-file" -> - ret.inputJarAsKeepAllFile.set(nextArg()) - - "--delete-finals" -> ret.deleteFinals.set(true) - - // Following options are for debugging. - "--enable-class-checker" -> ret.enableClassChecker.set(true) - "--no-class-checker" -> ret.enableClassChecker.set(false) + override fun parseOption(option: String, ai: ArgIterator): Boolean { + // Define some shorthands... + fun nextArg(): String = ai.nextArgRequired(option) - "--enable-pre-trace" -> ret.enablePreTrace.set(true) - "--no-pre-trace" -> ret.enablePreTrace.set(false) + when (option) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") - "--enable-post-trace" -> ret.enablePostTrace.set(true) - "--no-post-trace" -> ret.enablePostTrace.set(false) + "--in-jar" -> inJar.set(nextArg()).ensureFileExists() + // We support both arguments because some AOSP dependencies + // still use the old argument + "--out-jar", "--out-impl-jar" -> outJar.set(nextArg()) - "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg()) + "--clean-up-on-error" -> cleanUpOnError.set(true) + "--no-clean-up-on-error" -> cleanUpOnError.set(false) - "--stats-file" -> ret.statsFile.set(nextArg()) - "--supported-api-list-file" -> ret.apiListFile.set(nextArg()) + "--gen-input-dump-file" -> inputJarDumpFile.set(nextArg()) + "--gen-keep-all-file" -> inputJarAsKeepAllFile.set(nextArg()) - "--num-shards" -> ret.numShards.set(nextArg()).also { - if (it < 1) { - throw ArgumentsException("$arg must be positive integer") - } - } - "--shard-index" -> ret.shard.set(nextArg()).also { - if (it < 0) { - throw ArgumentsException("$arg must be positive integer or zero") - } - } + "--stats-file" -> statsFile.set(nextArg()) + "--supported-api-list-file" -> apiListFile.set(nextArg()) - else -> throw ArgumentsException("Unknown option: $arg") - } - } catch (e: SetOnce.SetMoreThanOnceException) { - throw ArgumentsException("Duplicate or conflicting argument found: $arg") + "--num-shards" -> numShards.set(nextArg()).also { + if (it < 1) { + throw ArgumentsException("$option must be positive integer") } } - - if (!ret.inJar.isSet) { - throw ArgumentsException("Required option missing: --in-jar") - } - if (!ret.outJar.isSet) { - log.w("--out-jar is not set. $executableName will not generate jar files.") - } - if (ret.numShards.isSet != ret.shard.isSet) { - throw ArgumentsException("--num-shards and --shard-index must be used together") - } - - if (ret.numShards.isSet) { - if (ret.shard.get >= ret.numShards.get) { - throw ArgumentsException("--shard-index must be smaller than --num-shards") + "--shard-index" -> shard.set(nextArg()).also { + if (it < 0) { + throw ArgumentsException("$option must be positive integer or zero") } } - return ret - } - } - - override fun toString(): String { - return """ - HostStubGenOptions{ - inJar='$inJar', - outJar='$outJar', - inputJarDumpFile=$inputJarDumpFile, - inputJarAsKeepAllFile=$inputJarAsKeepAllFile, - keepAnnotations=$keepAnnotations, - throwAnnotations=$throwAnnotations, - removeAnnotations=$removeAnnotations, - ignoreAnnotations=$ignoreAnnotations, - keepClassAnnotations=$keepClassAnnotations, - partiallyAllowedAnnotations=$partiallyAllowedAnnotations, - substituteAnnotations=$substituteAnnotations, - nativeSubstituteAnnotations=$redirectionClassAnnotations, - classLoadHookAnnotations=$classLoadHookAnnotations, - keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations, - packageRedirects=$packageRedirects, - annotationAllowedClassesFile=$annotationAllowedClassesFile, - defaultClassLoadHook=$defaultClassLoadHook, - defaultMethodCallHook=$defaultMethodCallHook, - policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()}, - defaultPolicy=$defaultPolicy, - deleteFinals=$deleteFinals, - cleanUpOnError=$cleanUpOnError, - enableClassChecker=$enableClassChecker, - enablePreTrace=$enablePreTrace, - enablePostTrace=$enablePostTrace, - statsFile=$statsFile, - apiListFile=$apiListFile, - numShards=$numShards, - shard=$shard, - } - """.trimIndent() - } -} - -class ArgIterator( - private val args: List<String>, - private var currentIndex: Int = -1 -) { - val current: String - get() = args.get(currentIndex) - - /** - * Get the next argument, or [null] if there's no more arguments. - */ - fun nextArgOptional(): String? { - if ((currentIndex + 1) >= args.size) { - return null + else -> return super.parseOption(option, ai) } - return args.get(++currentIndex) - } - /** - * Get the next argument, or throw if - */ - fun nextArgRequired(argName: String): String { - nextArgOptional().let { - if (it == null) { - throw ArgumentsException("Missing parameter for option $argName") - } - if (it.isEmpty()) { - throw ArgumentsException("Parameter can't be empty for option $argName") - } - return it - } + return true } - companion object { - fun withAtFiles(args: Array<String>): ArgIterator { - return ArgIterator(expandAtFiles(args)) - } - } -} - -/** - * Scan the arguments, and if any of them starts with an `@`, then load from the file - * and use its content as arguments. - * - * In order to pass an argument that starts with an '@', use '@@' instead. - * - * In this file, each line is treated as a single argument. - * - * The file can contain '#' as comments. - */ -private fun expandAtFiles(args: Array<String>): List<String> { - val ret = mutableListOf<String>() - - args.forEach { arg -> - if (arg.startsWith("@@")) { - ret += arg.substring(1) - return@forEach - } else if (!arg.startsWith('@')) { - ret += arg - return@forEach - } - // Read from the file, and add each line to the result. - val filename = arg.substring(1).ensureFileExists() - - log.v("Expanding options file $filename") - - BufferedReader(FileReader(filename)).use { reader -> - while (true) { - var line = reader.readLine() - if (line == null) { - break // EOF - } - - line = normalizeTextLine(line) - if (line.isNotEmpty()) { - ret += line - } - } - } + override fun dumpFields(): String { + return """ + inJar=$inJar, + outJar=$outJar, + inputJarDumpFile=$inputJarDumpFile, + inputJarAsKeepAllFile=$inputJarAsKeepAllFile, + cleanUpOnError=$cleanUpOnError, + statsFile=$statsFile, + apiListFile=$apiListFile, + numShards=$numShards, + shard=$shard, + """.trimIndent() + '\n' + super.dumpFields() } - return ret } diff --git a/ravenwood/tools/ravenhelper/Android.bp b/ravenwood/tools/ravenhelper/Android.bp index 3da6dd824c37..b27914750618 100644 --- a/ravenwood/tools/ravenhelper/Android.bp +++ b/ravenwood/tools/ravenhelper/Android.bp @@ -14,13 +14,7 @@ java_binary_host { static_libs: [ "guava", "hoststubgen-lib", - "junit", "metalava-gradle-plugin-deps", // Get lint/PSI related classes from here. - "ow2-asm", - "ow2-asm-analysis", - "ow2-asm-commons", - "ow2-asm-tree", - "ow2-asm-util", ], visibility: ["//visibility:public"], } diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt index 08bd95fd532b..f7fd0804c151 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt @@ -15,11 +15,11 @@ */ package com.android.platform.test.ravenwood.ravenhelper.policytoannot -import com.android.hoststubgen.ArgIterator import com.android.hoststubgen.ArgumentsException -import com.android.hoststubgen.SetOnce import com.android.hoststubgen.ensureFileExists -import com.android.hoststubgen.log +import com.android.hoststubgen.utils.ArgIterator +import com.android.hoststubgen.utils.BaseOptions +import com.android.hoststubgen.utils.SetOnce /** * Options for the "ravenhelper pta" subcommand. @@ -39,68 +39,48 @@ class PtaOptions( /** Dump the operations (for debugging) */ var dumpOperations: SetOnce<Boolean> = SetOnce(false), -) { - companion object { - fun parseArgs(args: List<String>): PtaOptions { - val ret = PtaOptions() - val ai = ArgIterator.withAtFiles(args.toTypedArray()) +) : BaseOptions() { - while (true) { - val arg = ai.nextArgOptional() ?: break + override fun parseOption(option: String, ai: ArgIterator): Boolean { + fun nextArg(): String = ai.nextArgRequired(option) - fun nextArg(): String = ai.nextArgRequired(arg) + when (option) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") - if (log.maybeHandleCommandLineArg(arg) { nextArg() }) { - continue - } - try { - when (arg) { - // TODO: Write help - "-h", "--help" -> TODO("Help is not implemented yet") + "-p", "--policy-override-file" -> + policyOverrideFiles.add(nextArg().ensureFileExists()) - "-p", "--policy-override-file" -> - ret.policyOverrideFiles.add(nextArg().ensureFileExists()) + "-a", "--annotation-allowed-classes-file" -> + annotationAllowedClassesFile.set(nextArg().ensureFileExists()) - "-a", "--annotation-allowed-classes-file" -> - ret.annotationAllowedClassesFile.set(nextArg().ensureFileExists()) + "-s", "--src" -> sourceFilesOrDirectories.add(nextArg().ensureFileExists()) + "--dump" -> dumpOperations.set(true) + "-o", "--output-script" -> outputScriptFile.set(nextArg()) - "-s", "--src" -> - ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists()) - - "--dump" -> - ret.dumpOperations.set(true) - - "-o", "--output-script" -> - ret.outputScriptFile.set(nextArg()) - - else -> throw ArgumentsException("Unknown option: $arg") - } - } catch (e: SetOnce.SetMoreThanOnceException) { - throw ArgumentsException("Duplicate or conflicting argument found: $arg") - } - } + else -> return false + } - if (ret.policyOverrideFiles.size == 0) { - throw ArgumentsException("Must specify at least one policy file") - } + return true + } - if (ret.sourceFilesOrDirectories.size == 0) { - throw ArgumentsException("Must specify at least one source path") - } + override fun checkArgs() { + if (policyOverrideFiles.size == 0) { + throw ArgumentsException("Must specify at least one policy file") + } - return ret + if (sourceFilesOrDirectories.size == 0) { + throw ArgumentsException("Must specify at least one source path") } } - override fun toString(): String { + override fun dumpFields(): String { return """ - PtaOptions{ - policyOverrideFiles=$policyOverrideFiles - annotationAllowedClassesFile=$annotationAllowedClassesFile - sourceFilesOrDirectories=$sourceFilesOrDirectories - outputScriptFile=$outputScriptFile - dumpOperations=$dumpOperations - } - """.trimIndent() + policyOverrideFiles=$policyOverrideFiles + annotationAllowedClassesFile=$annotationAllowedClassesFile + sourceFilesOrDirectories=$sourceFilesOrDirectories + outputScriptFile=$outputScriptFile + dumpOperations=$dumpOperations + """.trimIndent() } -}
\ No newline at end of file +} diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt index a7f481a02533..fd6f732a06ce 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt @@ -39,7 +39,7 @@ import java.util.regex.Pattern */ class PtaProcessor : SubcommandHandler { override fun handle(args: List<String>) { - val options = PtaOptions.parseArgs(args) + val options = PtaOptions().apply { parseArgs(args) } log.v("Options: $options") diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt index ee200bb39df2..8b95843f08a6 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt @@ -15,11 +15,11 @@ */ package com.android.platform.test.ravenwood.ravenhelper.sourcemap -import com.android.hoststubgen.ArgIterator import com.android.hoststubgen.ArgumentsException -import com.android.hoststubgen.SetOnce import com.android.hoststubgen.ensureFileExists -import com.android.hoststubgen.log +import com.android.hoststubgen.utils.ArgIterator +import com.android.hoststubgen.utils.BaseOptions +import com.android.hoststubgen.utils.SetOnce /** * Options for the "ravenhelper map" subcommand. @@ -36,60 +36,36 @@ class MapOptions( /** Text to insert. */ var text: SetOnce<String?> = SetOnce(null), -) { - companion object { - fun parseArgs(args: List<String>): MapOptions { - val ret = MapOptions() - val ai = ArgIterator.withAtFiles(args.toTypedArray()) - - while (true) { - val arg = ai.nextArgOptional() ?: break - - fun nextArg(): String = ai.nextArgRequired(arg) - - if (log.maybeHandleCommandLineArg(arg) { nextArg() }) { - continue - } - try { - when (arg) { - // TODO: Write help - "-h", "--help" -> TODO("Help is not implemented yet") - - "-s", "--src" -> - ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists()) - - "-i", "--input" -> - ret.targetMethodFiles.add(nextArg().ensureFileExists()) - - "-o", "--output-script" -> - ret.outputScriptFile.set(nextArg()) - - "-t", "--text" -> - ret.text.set(nextArg()) - - else -> throw ArgumentsException("Unknown option: $arg") - } - } catch (e: SetOnce.SetMoreThanOnceException) { - throw ArgumentsException("Duplicate or conflicting argument found: $arg") - } - } +) : BaseOptions() { + + override fun parseOption(option: String, ai: ArgIterator): Boolean { + fun nextArg(): String = ai.nextArgRequired(option) + + when (option) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") + "-s", "--src" -> sourceFilesOrDirectories.add(nextArg().ensureFileExists()) + "-i", "--input" -> targetMethodFiles.add(nextArg().ensureFileExists()) + "-o", "--output-script" -> outputScriptFile.set(nextArg()) + "-t", "--text" -> text.set(nextArg()) + else -> return false + } - if (ret.sourceFilesOrDirectories.size == 0) { - throw ArgumentsException("Must specify at least one source path") - } + return true + } - return ret + override fun checkArgs() { + if (sourceFilesOrDirectories.size == 0) { + throw ArgumentsException("Must specify at least one source path") } } - override fun toString(): String { + override fun dumpFields(): String { return """ - PtaOptions{ - sourceFilesOrDirectories=$sourceFilesOrDirectories - targetMethods=$targetMethodFiles - outputScriptFile=$outputScriptFile - text=$text - } - """.trimIndent() + sourceFilesOrDirectories=$sourceFilesOrDirectories + targetMethods=$targetMethodFiles + outputScriptFile=$outputScriptFile + text=$text + """.trimIndent() } -}
\ No newline at end of file +} diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt index 8085253895f9..f1c139891b2d 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt @@ -35,7 +35,7 @@ import java.io.FileReader */ class MarkMethodHandler : SubcommandHandler { override fun handle(args: List<String>) { - val options = MapOptions.parseArgs(args) + val options = MapOptions().apply { parseArgs(args) } log.i("Options: $options") diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp index a52a04b44f2d..957e20647d44 100644 --- a/ravenwood/tools/ravenizer/Android.bp +++ b/ravenwood/tools/ravenizer/Android.bp @@ -13,12 +13,6 @@ java_binary_host { srcs: ["src/**/*.kt"], static_libs: [ "hoststubgen-lib", - "ow2-asm", - "ow2-asm-analysis", - "ow2-asm-commons", - "ow2-asm-tree", - "ow2-asm-util", - "junit", "ravenwood-junit-for-ravenizer", ], visibility: ["//visibility:public"], diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt index aee453020fb4..7f4829ec6127 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt @@ -21,6 +21,21 @@ import com.android.hoststubgen.LogLevel import com.android.hoststubgen.executableName import com.android.hoststubgen.log import com.android.hoststubgen.runMainWithBoilerplate +import java.nio.file.Paths +import kotlin.io.path.exists + +/** + * If this file exits, we also read options from it. This is "unsafe" because it could break + * incremental builds, if it sets any flag that affects the output file. + * (however, for now, there's no such options.) + * + * For example, to enable verbose logging, do `echo '-v' > ~/.raveniezr-unsafe` + * + * (but even the content of this file changes, soong won't rerun the command, so you need to + * remove the output first and then do a build again.) + */ +private val RAVENIZER_DOTFILE = System.getenv("HOME") + "/.ravenizer-unsafe" + /** * Entry point. @@ -30,7 +45,15 @@ fun main(args: Array<String>) { log.setConsoleLogLevel(LogLevel.Info) runMainWithBoilerplate { - val options = RavenizerOptions.parseArgs(args) + var newArgs = args.asList() + if (Paths.get(RAVENIZER_DOTFILE).exists()) { + log.i("Reading options from $RAVENIZER_DOTFILE") + newArgs = args.toMutableList().apply { + add(0, "@$RAVENIZER_DOTFILE") + } + } + + val options = RavenizerOptions().apply { parseArgs(newArgs) } log.i("$executableName started") log.v("Options: $options") diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt index a0e5599c0a7c..2c0365404ab6 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt @@ -15,25 +15,11 @@ */ package com.android.platform.test.ravenwood.ravenizer -import com.android.hoststubgen.ArgIterator import com.android.hoststubgen.ArgumentsException -import com.android.hoststubgen.SetOnce import com.android.hoststubgen.ensureFileExists -import com.android.hoststubgen.log -import java.nio.file.Paths -import kotlin.io.path.exists - -/** - * If this file exits, we also read options from it. This is "unsafe" because it could break - * incremental builds, if it sets any flag that affects the output file. - * (however, for now, there's no such options.) - * - * For example, to enable verbose logging, do `echo '-v' > ~/.raveniezr-unsafe` - * - * (but even the content of this file changes, soong won't rerun the command, so you need to - * remove the output first and then do a build again.) - */ -private val RAVENIZER_DOTFILE = System.getenv("HOME") + "/.raveniezr-unsafe" +import com.android.hoststubgen.utils.ArgIterator +import com.android.hoststubgen.utils.BaseOptions +import com.android.hoststubgen.utils.SetOnce class RavenizerOptions( /** Input jar file*/ @@ -50,72 +36,49 @@ class RavenizerOptions( /** Whether to remove mockito and dexmaker classes. */ var stripMockito: SetOnce<Boolean> = SetOnce(false), -) { - companion object { - - fun parseArgs(origArgs: Array<String>): RavenizerOptions { - val args = origArgs.toMutableList() - if (Paths.get(RAVENIZER_DOTFILE).exists()) { - log.i("Reading options from $RAVENIZER_DOTFILE") - args.add(0, "@$RAVENIZER_DOTFILE") - } - - val ret = RavenizerOptions() - val ai = ArgIterator.withAtFiles(args.toTypedArray()) - - while (true) { - val arg = ai.nextArgOptional() - if (arg == null) { - break - } - - fun nextArg(): String = ai.nextArgRequired(arg) - - if (log.maybeHandleCommandLineArg(arg) { nextArg() }) { - continue - } - try { - when (arg) { - // TODO: Write help - "-h", "--help" -> TODO("Help is not implemented yet") - - "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists() - "--out-jar" -> ret.outJar.set(nextArg()) - - "--enable-validation" -> ret.enableValidation.set(true) - "--disable-validation" -> ret.enableValidation.set(false) - - "--fatal-validation" -> ret.fatalValidation.set(true) - "--no-fatal-validation" -> ret.fatalValidation.set(false) - - "--strip-mockito" -> ret.stripMockito.set(true) - "--no-strip-mockito" -> ret.stripMockito.set(false) - - else -> throw ArgumentsException("Unknown option: $arg") - } - } catch (e: SetOnce.SetMoreThanOnceException) { - throw ArgumentsException("Duplicate or conflicting argument found: $arg") - } - } - - if (!ret.inJar.isSet) { - throw ArgumentsException("Required option missing: --in-jar") - } - if (!ret.outJar.isSet) { - throw ArgumentsException("Required option missing: --out-jar") - } - return ret +) : BaseOptions() { + + override fun parseOption(option: String, ai: ArgIterator): Boolean { + fun nextArg(): String = ai.nextArgRequired(option) + + when (option) { + // TODO: Write help + "-h", "--help" -> TODO("Help is not implemented yet") + + "--in-jar" -> inJar.set(nextArg()).ensureFileExists() + "--out-jar" -> outJar.set(nextArg()) + + "--enable-validation" -> enableValidation.set(true) + "--disable-validation" -> enableValidation.set(false) + + "--fatal-validation" -> fatalValidation.set(true) + "--no-fatal-validation" -> fatalValidation.set(false) + + "--strip-mockito" -> stripMockito.set(true) + "--no-strip-mockito" -> stripMockito.set(false) + + else -> return false + } + + return true + } + + override fun checkArgs() { + if (!inJar.isSet) { + throw ArgumentsException("Required option missing: --in-jar") + } + if (!outJar.isSet) { + throw ArgumentsException("Required option missing: --out-jar") } } - override fun toString(): String { + override fun dumpFields(): String { return """ - RavenizerOptions{ - inJar=$inJar, - outJar=$outJar, - enableValidation=$enableValidation, - fatalValidation=$fatalValidation, - } - """.trimIndent() + inJar=$inJar, + outJar=$outJar, + enableValidation=$enableValidation, + fatalValidation=$fatalValidation, + stripMockito=$stripMockito, + """.trimIndent() } } |