/* * Copyright 2021 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 androidx.baselineprofiles import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required import java.io.File /** * A Utility that can split a monolithic baseline profiles to its constituent modules. * * This command helps split the large baseline profile produced via the `BaselineProfileRule`, * to its constituent library modules by scanning the `checkoutPath` for source files, and matching * the rules in the baseline profiles, to the packages discovered. */ class SplitBaselineProfiles : CliktCommand() { private val profilePath by option( "-p", "--profilePath", help = "The baseline profile path" ).required() private val checkoutPath by option( "-c", "--checkoutPath", help = "The checkout path" ).required() private val outputName by option( "-o", "--outputName", help = "The name of the output file" ).default("output-prof.txt") override fun run() { val profileFile = File(profilePath).requiredExists() val checkoutFile = File(checkoutPath).requiredExists() val outputFile = File(profileFile.parentFile, outputName) val index = buildIndex(checkoutFile) val contents = profileFile.readLines() val groupedProfiles = contents.groupBy { line -> val matchResult = CLASS_PATTERN.find(line) val groupBy = if (matchResult != null) { // The simplified class name // Looks something like androidx/Foo/Bar val name = matchResult.groupValues[2] index[name] ?: name.split("/").dropLast(1).joinToString(separator = "/") } else { null } groupBy ?: "Unknown" } println("Writing to ${outputFile.absolutePath}") writeOutputProfile(groupedProfiles, outputFile) println("All Done.") } private fun buildIndex(checkoutFile: File): Map { val sourceFiles = mutableListOf() findSourceFiles(listOf(checkoutFile), sourceFiles) println("Found ${sourceFiles.size} files.") return buildIndexFromFiles(sourceFiles) } private fun buildIndexFromFiles(sourceFiles: List): Map { val grouped = sourceFiles.groupBy { file -> var currentFile = file var group: String? = null while (group == null) { // Group by path to src/main because that is where the baseline-prof.txt // Needs to end up in. if (currentFile.isDirectory && currentFile.name == "main") { group = currentFile.absolutePath } else { currentFile = currentFile.parentFile } } group } // Invert val inverted = mutableMapOf() grouped.forEach { (pathToMain, entries) -> entries.forEach { file -> val matchResult = PATH_PATTERN.find(file.absolutePath) if (matchResult != null) { val packagePath = matchResult.groupValues[3] inverted[packagePath] = pathToMain } } } return inverted } private fun findSourceFiles(paths: List, sourceFiles: MutableList) { if (paths.isEmpty()) { return } val nextPaths = mutableListOf() for (path in paths) { val files = path.listFiles() files?.forEach { file -> if (file.isFile) { val name = file.name // Only look at source files with a "src/main/" prefix // They are gradle modules we might be interested in. if ((name.endsWith("java") || name.endsWith("kt")) && file.absolutePath.contains("src/main") ) { sourceFiles += file } } else if (file.isDirectory) { nextPaths += file } } } return findSourceFiles(nextPaths, sourceFiles) } private fun writeOutputProfile(groupedProfiles: Map>, outputFile: File) { val writer = outputFile.printWriter() writer.use { groupedProfiles.forEach { entry -> val groupName = entry.key val profileEntries = entry.value writer.write("# $groupName\n\n") profileEntries.forEach { line -> writer.write("$line\n") } writer.write("\n") } } } companion object { // A class file in the ART HRF format. // We don't care about static inner classes, or methods to locate them in the source tree. val CLASS_PATTERN = Regex("""(H|S|P)?L(\w+/[^$;]*)""") // Identifies a Java | Kotlin Source folder from a checkout. val PATH_PATTERN = Regex("""(.*)/src/main/(java|kotlin)/(.*)(\.(java|kt)?)""") internal fun File.requiredExists(): File { require(exists()) { "$absolutePath does not exist." } return this } } } fun main(args: Array) { SplitBaselineProfiles().main(args) }