Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 41ade60e authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Ravenizer tool skelton" into main

parents ad7a4794 efbec067
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -166,6 +166,14 @@ java_library {
    jarjar_rules: ":ravenwood-services-jarjar-rules",
}

java_device_for_host {
    name: "ravenwood-junit-impl-for-ravenizer",
    libs: [
        "ravenwood-junit-impl",
    ],
    visibility: [":__subpackages__"],
}

// Separated out from ravenwood-junit-impl since it needs to compile
// against `module_current`
java_library {
+0 −31
Original line number Diff line number Diff line
#!/bin/bash
# Copyright (C) 2024 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.

# "Fake" ravenizer, which just copies the file.
# We need it to add ravenizer support to Soong on AOSP,
# when the actual ravenizer is not in AOSP yet.

invalid_arg() {
    echo "Ravenizer(fake): invalid args" 1>&2
    exit 1
}

(( $# >= 4 )) || invalid_arg
[[ "$1" == "--in-jar" ]] || invalid_arg
[[ "$3" == "--out-jar" ]] || invalid_arg

echo "Ravenizer(fake): copiyng $2 to $4"

cp "$2" "$4"
+13 −2
Original line number Diff line number Diff line
@@ -7,8 +7,19 @@ package {
    default_applicable_licenses: ["frameworks_base_license"],
}

sh_binary_host {
java_binary_host {
    name: "ravenizer",
    src: "ravenizer",
    main_class: "com.android.platform.test.ravenwood.ravenizer.RavenizerMain",
    srcs: ["src/**/*.kt"],
    static_libs: [
        "hoststubgen-lib",
        "ow2-asm",
        "ow2-asm-analysis",
        "ow2-asm-commons",
        "ow2-asm-tree",
        "ow2-asm-util",
        "junit",
        "ravenwood-junit-impl-for-ravenizer",
    ],
    visibility: ["//visibility:public"],
}
+212 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer

import com.android.hoststubgen.GeneralUserErrorException
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.zipEntryNameToClassName
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

/**
 * Various stats on Ravenizer.
 */
data class RavenizerStats(
    /** Total end-to-end time. */
    var totalTime: Double = .0,

    /** Time took to build [ClasNodes] */
    var loadStructureTime: Double = .0,

    /** Total real time spent for converting the jar file */
    var totalProcessTime: Double = .0,

    /** Total real time spent for converting class files (except for I/O time). */
    var totalConversionTime: Double = .0,

    /** Total real time spent for copying class files without modification. */
    var totalCopyTime: Double = .0,

    /** # of entries in the input jar file */
    var totalEntiries: Int = 0,

    /** # of *.class files in the input jar file */
    var totalClasses: Int = 0,

    /** # of *.class files that have been processed. */
    var processedClasses: Int = 0,
) {
    override fun toString(): String {
        return """
            RavenizerStats{
              totalTime=$totalTime,
              loadStructureTime=$loadStructureTime,
              totalProcessTime=$totalProcessTime,
              totalConversionTime=$totalConversionTime,
              totalCopyTime=$totalCopyTime,
              totalEntiries=$totalEntiries,
              totalClasses=$totalClasses,
              processedClasses=$processedClasses,
            }
            """.trimIndent()
    }
}

/**
 * Main class.
 */
class Ravenizer(val options: RavenizerOptions) {
    fun run() {
        val stats = RavenizerStats()
        stats.totalTime = log.nTime {
            process(options.inJar.get, options.outJar.get, stats)
        }
        log.i(stats.toString())
    }

    private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
        var allClasses = ClassNodes.loadClassStructures(inJar) {
            time -> stats.loadStructureTime = time
        }

        stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
            ZipFile(inJar).use { inZip ->
                val inEntries = inZip.entries()

                stats.totalEntiries = inZip.size()

                ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
                    while (inEntries.hasMoreElements()) {
                        val entry = inEntries.nextElement()

                        if (entry.name.endsWith(".dex")) {
                            // Seems like it's an ART jar file. We can't process it.
                            // It's a fatal error.
                            throw GeneralUserErrorException(
                                "$inJar is not a desktop jar file. It contains a *.dex file."
                            )
                        }

                        val className = zipEntryNameToClassName(entry.name)

                        if (className != null) {
                            stats.totalClasses += 1
                        }

                        if (className != null && shouldProcessClass(allClasses, className)) {
                            stats.processedClasses += 1
                            processSingleClass(inZip, entry, outZip, allClasses, stats)
                        } else {
                            // Too slow, let's use merge_zips to bring back the original classes.
                            copyZipEntry(inZip, entry, outZip, stats)
                        }
                    }
                }
            }
        }
    }

    /**
     * Copy a single ZIP entry to the output.
     */
    private fun copyZipEntry(
        inZip: ZipFile,
        entry: ZipEntry,
        out: ZipOutputStream,
        stats: RavenizerStats,
    ) {
        stats.totalCopyTime += log.nTime {
            inZip.getInputStream(entry).use { ins ->
                // Copy unknown entries as is to the impl out. (but not to the stub out.)
                val outEntry = ZipEntry(entry.name)
                outEntry.method = 0
                outEntry.size = entry.size
                outEntry.crc = entry.crc
                out.putNextEntry(outEntry)

                ins.transferTo(out)

                out.closeEntry()
            }
        }
    }

    private fun processSingleClass(
        inZip: ZipFile,
        entry: ZipEntry,
        outZip: ZipOutputStream,
        allClasses: ClassNodes,
        stats: RavenizerStats,
    ) {
        val newEntry = ZipEntry(entry.name)
        outZip.putNextEntry(newEntry)

        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
            processSingleClass(entry, bis, outZip, allClasses, stats)
        }
        outZip.closeEntry()
    }

    /**
     * Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
     */
    private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
        return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
    }

    private fun processSingleClass(
        entry: ZipEntry,
        input: InputStream,
        output: OutputStream,
        allClasses: ClassNodes,
        stats: RavenizerStats,
    ) {
        val cr = ClassReader(input)

        lateinit var data: ByteArray
        stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
            val flags = ClassWriter.COMPUTE_MAXS
            val cw = ClassWriter(flags)
            var outVisitor: ClassVisitor = cw

            val enableChecker = false
            if (enableChecker) {
                outVisitor = CheckClassAdapter(outVisitor)
            }

            // This must be kept in sync with shouldProcessClass.
            outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)

            cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)

            data = cw.toByteArray()
        }
        output.write(data)
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */
@file:JvmName("RavenizerMain")

package com.android.platform.test.ravenwood.ravenizer

import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.hoststubgen.runMainWithBoilerplate

/**
 * Entry point.
 */
fun main(args: Array<String>) {
    executableName = "Ravenizer"
    log.setConsoleLogLevel(LogLevel.Info)

    runMainWithBoilerplate {
        val options = RavenizerOptions.parseArgs(args)

        log.i("$executableName started")
        log.v("Options: $options")

        // Run.
        Ravenizer(options).run()
    }
}
Loading