lazybox: support compile_commands in AOSP C++

master
cfig 4 months ago
parent de2ae91469
commit f77bf84cb2

@ -1,5 +1,6 @@
package cfig.lazybox
import cfig.lazybox.staging.AospCompiledb
import cfig.lazybox.staging.DiffCI
import cfig.lazybox.staging.Perfetto
import cfig.lazybox.staging.RepoWorker
@ -7,6 +8,7 @@ import cfig.lazybox.sysinfo.BootChart
import cfig.lazybox.sysinfo.CpuInfo
import cfig.lazybox.sysinfo.Pidstat
import cfig.lazybox.sysinfo.SysInfo
import cfig.lazybox.sysinfo.ThermalInfo
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import java.io.File
@ -20,15 +22,17 @@ fun main(args: Array<String>) {
println("Usage: args: (Array<String>) ...")
println(" or: function [arguments]...")
println("\nCurrently defined functions:")
println("\tcpuinfo sysinfo sysstat pidstat bootchart")
println("\tcpuinfo sysinfo sysstat pidstat bootchart thermal_info compiledb")
println("\nCommand Usage:")
println("bootchart: generate Android bootchart")
println("pidstat : given a pid, profile its CPU usage")
println("tracecmd : analyze trace-cmd report")
println("cpuinfo : get cpu info from /sys/devices/system/cpu/")
println("sysinfo : get overall system info from Android")
println("thermal_info : get thermal info from /sys/class/thermal/")
println("\nIncubating usage:")
println("apps : get apk file list from Android")
println("compiledb : generate compilation database for AOSP")
println("dmainfo : parse /d/dma_buf/bufinfo")
println("diffci : find changelist files from CI server based on date and time ranges")
println("repo_lfs : pull LFS files from Git repositories managed by 'repo'")
@ -48,6 +52,9 @@ fun main(args: Array<String>) {
if (args[0] == "sysinfo") {
SysInfo().run()
}
if (args[0] == "thermal_info") {
ThermalInfo().run()
}
if (args[0] == "sysstat") {
println("adb shell /data/vendor/sadc -F -L -S ALL 2 20 /data/vendor")
}
@ -116,4 +123,7 @@ fun main(args: Array<String>) {
if (args[0] == "perfetto") {
Perfetto().run(args.drop(1).toTypedArray())
}
if (args[0] == "compiledb") {
AospCompiledb().run()
}
}

@ -0,0 +1,180 @@
package cfig.lazybox.staging
import com.fasterxml.jackson.databind.ObjectMapper
import java.io.File
import java.io.FileInputStream
import java.util.regex.Pattern
import java.util.zip.GZIPInputStream
class AospCompiledb {
data class CompileCommand(
val directory: String,
val command: String,
val file: String
)
fun findAndroidRoot(logFile: File): String {
// Get absolute path of the log file
val logAbsPath = logFile.absoluteFile
// The log is in out/verbose.log.gz, so Android root is the parent of the "out" directory
var currentDir = logAbsPath.parentFile // This should be "out" directory
while (currentDir != null && currentDir.name != "out") {
currentDir = currentDir.parentFile
}
return if (currentDir != null) {
// Go up one more level to get the Android root (parent of "out")
currentDir.parentFile?.absolutePath ?: System.getProperty("user.dir")
} else {
// Fallback: try to find Android root by looking for typical Android files
var dir = logAbsPath.parentFile
while (dir != null) {
if (File(dir, "build/make").exists() || File(dir, "Makefile").exists() || File(dir, "build.gradle").exists()) {
return dir.absolutePath
}
dir = dir.parentFile
}
System.getProperty("user.dir")
}
}
fun parseVerboseLog(gzFile: File, androidRoot: String): List<CompileCommand> {
val compileCommands = mutableListOf<CompileCommand>()
val ninjaCommandPattern = Pattern.compile("""^\[(\d+)/(\d+)\]\s+(.+)$""")
GZIPInputStream(FileInputStream(gzFile)).bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
val matcher = ninjaCommandPattern.matcher(line)
if (matcher.matches()) {
val command = matcher.group(3)
// Only process compilation commands (with -c flag), not linking commands
if (command.contains(" -c ") && command.contains("clang")) {
val compileCommand = parseCompilationCommand(command, androidRoot)
if (compileCommand != null) {
compileCommands.add(compileCommand)
}
}
}
}
}
return compileCommands
}
fun parseCompilationCommand(commandLine: String, androidRoot: String): CompileCommand? {
try {
// Parse the command to extract compiler path, flags, and source file
val parts = splitCommandLine(commandLine)
if (parts.isEmpty()) return null
// Find the compiler executable
val compilerIndex = parts.indexOfFirst { it.contains("clang") }
if (compilerIndex == -1) return null
// Find source files (typically .c, .cpp, .cc files that are not output files)
val sourceFiles = findSourceFiles(parts)
if (sourceFiles.isEmpty()) return null
// Build the clean command (without PWD prefix)
val cleanCommand = buildCleanCommand(parts, compilerIndex)
// Create compile command for each source file
val sourceFile = sourceFiles.first() // Take the first source file
return CompileCommand(
directory = androidRoot,
command = cleanCommand,
file = sourceFile
)
} catch (e: Exception) {
println("Warning: Failed to parse command: ${e.message}")
return null
}
}
fun splitCommandLine(commandLine: String): List<String> {
// Remove PWD= prefix if present
val cleanCommand = commandLine.replace(Regex("""PWD=[^\s]+\s*"""), "")
// Simple command line splitting (handles basic quoting)
val parts = mutableListOf<String>()
var current = StringBuilder()
var inQuotes = false
var escapeNext = false
for (char in cleanCommand) {
when {
escapeNext -> {
current.append(char)
escapeNext = false
}
char == '\\' -> {
escapeNext = true
}
char == '"' -> {
inQuotes = !inQuotes
}
char == ' ' && !inQuotes -> {
if (current.isNotEmpty()) {
parts.add(current.toString())
current = StringBuilder()
}
}
else -> {
current.append(char)
}
}
}
if (current.isNotEmpty()) {
parts.add(current.toString())
}
return parts
}
fun findSourceFiles(parts: List<String>): List<String> {
val sourceExtensions = setOf(".c", ".cpp", ".cc", ".cxx", ".c++")
return parts.filter { part ->
sourceExtensions.any { ext -> part.endsWith(ext) } &&
!part.startsWith("-") && // Not a flag
!part.contains("crtbegin") && // Not a crt file
!part.contains("crtend") &&
File(part).extension in sourceExtensions.map { it.substring(1) }
}
}
fun buildCleanCommand(parts: List<String>, compilerIndex: Int): String {
// Join all parts starting from the compiler
return parts.drop(compilerIndex).joinToString(" ")
}
fun run() {
val logFile = File("out/verbose.log.gz")
val outputFile = File("compile_commands.json")
if (!logFile.exists()) {
println("Error: verbose.log.gz not found in out/ directory")
return
}
// Find Android root directory from verbose log location
val androidRoot = findAndroidRoot(logFile)
println("Android root directory: $androidRoot")
println("Parsing verbose build log...")
val compileCommands = parseVerboseLog(logFile, androidRoot)
println("Found ${compileCommands.size} compilation commands")
// Generate JSON
val json = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(compileCommands)
outputFile.writeText(json)
println("Generated compile_commands.json with ${compileCommands.size} entries")
}
}

@ -9,11 +9,13 @@ class BootChart {
companion object {
private val log = LoggerFactory.getLogger(BootChart::class.java)
fun run() {
/*
"adb wait-for-device".check_call()
"adb root".check_call()
"adb wait-for-device".check_call()
"adb shell touch /data/bootchart/enabled".check_call()
Helper.adbCmd("reboot")
"adb reboot".check_call()
//Helper.adbCmd("reboot")
"adb wait-for-device".check_call()
"adb root".check_call()
"adb wait-for-device".check_call()
@ -29,6 +31,7 @@ class BootChart {
TimeUnit.SECONDS.sleep(1)
}
}
*/
"header proc_stat.log proc_ps.log proc_diskstats.log".split("\\s".toRegex()).forEach {
val LOGROOT = "/data/bootchart/"
"adb pull ${LOGROOT}$it".check_call()
@ -38,4 +41,4 @@ class BootChart {
"xdg-open bootchart.png".check_call()
}
}
}
}

@ -0,0 +1,73 @@
package cfig.lazybox.sysinfo
import cfig.helper.Helper
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
class ThermalInfo {
private val log = LoggerFactory.getLogger(ThermalInfo::class.java)
fun makeTar(tarFile: String, srcDir: String) {
val pyScript =
"""
import os, sys, subprocess, gzip, logging, shutil, tarfile, os.path
def makeTar(output_filename, source_dir):
with tarfile.open(output_filename, "w:xz") as tar:
tar.add(source_dir, arcname=os.path.basename(source_dir))
makeTar("%s", "%s")
""".trim()
val tmp = Files.createTempFile(Paths.get("."), "xx.", ".yy")
tmp.writeText(String.format(pyScript, tarFile, srcDir))
("python " + tmp.fileName).check_call()
tmp.deleteIfExists()
}
fun run() {
"adb wait-for-device".check_call()
"adb root".check_call()
val prefix = "thermal_info"
File(prefix).let {
if (it.exists()) {
log.info("purging directory $prefix/ ...")
it.deleteRecursively()
}
}
File(prefix).mkdir()
val thermalZonesOutput = Helper.powerRun("adb shell ls /sys/class/thermal", null)
val thermalZones = String(thermalZonesOutput.get(0)).split("\\s+".toRegex()).filter { it.startsWith("thermal_zone") }
if (thermalZones.isEmpty()) {
log.warn("No thermal zones found.")
} else {
thermalZones.forEach { zone ->
log.info("pulling info from $zone")
val zoneDir = File("$prefix/$zone")
zoneDir.mkdir()
FileOutputStream("${zoneDir.path}/type").use {
SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/type", it, false)
}
FileOutputStream("${zoneDir.path}/temp").use {
SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/temp", it, false)
}
}
}
makeTar("$prefix.tar.xz", prefix)
File(prefix).deleteRecursively()
log.info("$prefix.tar.xz is ready")
}
private fun String.check_call() {
val ret = Helper.powerRun(this, null)
val ret2 = String(ret.get(0)).trim()
if (ret2.isNotEmpty()) {
log.info(ret2)
}
}
}
Loading…
Cancel
Save