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

Commit efbec067 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Ravenizer tool skelton

- It still doesn't do any conversion, but the outer structure is done.
- Also make the "load class structure" step a bit faster by loading
    classes in parallel.

Flag: EXEMPT host test change only
Bug: 360390999
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
Change-Id: I0f28ccd7388c310f0f733900fea9ad709e16f1cb
parent c906b847
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