Skip to content

Commit 4b8bcd5

Browse files
committed
lazybox: support compile_commands in AOSP C++
1 parent fd57f24 commit 4b8bcd5

File tree

4 files changed

+269
-3
lines changed

4 files changed

+269
-3
lines changed

lazybox/src/main/kotlin/cfig/lazybox/App.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package cfig.lazybox
22

3+
import cfig.lazybox.staging.AospCompiledb
34
import cfig.lazybox.staging.DiffCI
45
import cfig.lazybox.staging.Perfetto
56
import cfig.lazybox.staging.RepoWorker
67
import cfig.lazybox.sysinfo.BootChart
78
import cfig.lazybox.sysinfo.CpuInfo
89
import cfig.lazybox.sysinfo.Pidstat
910
import cfig.lazybox.sysinfo.SysInfo
11+
import cfig.lazybox.sysinfo.ThermalInfo
1012
import com.fasterxml.jackson.databind.ObjectMapper
1113
import org.slf4j.LoggerFactory
1214
import java.io.File
@@ -20,15 +22,17 @@ fun main(args: Array<String>) {
2022
println("Usage: args: (Array<String>) ...")
2123
println(" or: function [arguments]...")
2224
println("\nCurrently defined functions:")
23-
println("\tcpuinfo sysinfo sysstat pidstat bootchart")
25+
println("\tcpuinfo sysinfo sysstat pidstat bootchart thermal_info compiledb")
2426
println("\nCommand Usage:")
2527
println("bootchart: generate Android bootchart")
2628
println("pidstat : given a pid, profile its CPU usage")
2729
println("tracecmd : analyze trace-cmd report")
2830
println("cpuinfo : get cpu info from /sys/devices/system/cpu/")
2931
println("sysinfo : get overall system info from Android")
32+
println("thermal_info : get thermal info from /sys/class/thermal/")
3033
println("\nIncubating usage:")
3134
println("apps : get apk file list from Android")
35+
println("compiledb : generate compilation database for AOSP")
3236
println("dmainfo : parse /d/dma_buf/bufinfo")
3337
println("diffci : find changelist files from CI server based on date and time ranges")
3438
println("repo_lfs : pull LFS files from Git repositories managed by 'repo'")
@@ -48,6 +52,9 @@ fun main(args: Array<String>) {
4852
if (args[0] == "sysinfo") {
4953
SysInfo().run()
5054
}
55+
if (args[0] == "thermal_info") {
56+
ThermalInfo().run()
57+
}
5158
if (args[0] == "sysstat") {
5259
println("adb shell /data/vendor/sadc -F -L -S ALL 2 20 /data/vendor")
5360
}
@@ -116,4 +123,7 @@ fun main(args: Array<String>) {
116123
if (args[0] == "perfetto") {
117124
Perfetto().run(args.drop(1).toTypedArray())
118125
}
126+
if (args[0] == "compiledb") {
127+
AospCompiledb().run()
128+
}
119129
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package cfig.lazybox.staging
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper
4+
import java.io.File
5+
import java.io.FileInputStream
6+
import java.util.regex.Pattern
7+
import java.util.zip.GZIPInputStream
8+
9+
class AospCompiledb {
10+
data class CompileCommand(
11+
val directory: String,
12+
val command: String,
13+
val file: String
14+
)
15+
16+
fun findAndroidRoot(logFile: File): String {
17+
// Get absolute path of the log file
18+
val logAbsPath = logFile.absoluteFile
19+
20+
// The log is in out/verbose.log.gz, so Android root is the parent of the "out" directory
21+
var currentDir = logAbsPath.parentFile // This should be "out" directory
22+
while (currentDir != null && currentDir.name != "out") {
23+
currentDir = currentDir.parentFile
24+
}
25+
26+
return if (currentDir != null) {
27+
// Go up one more level to get the Android root (parent of "out")
28+
currentDir.parentFile?.absolutePath ?: System.getProperty("user.dir")
29+
} else {
30+
// Fallback: try to find Android root by looking for typical Android files
31+
var dir = logAbsPath.parentFile
32+
while (dir != null) {
33+
if (File(dir, "build/make").exists() || File(dir, "Makefile").exists() || File(dir, "build.gradle").exists()) {
34+
return dir.absolutePath
35+
}
36+
dir = dir.parentFile
37+
}
38+
System.getProperty("user.dir")
39+
}
40+
}
41+
42+
fun parseVerboseLog(gzFile: File, androidRoot: String): List<CompileCommand> {
43+
val compileCommands = mutableListOf<CompileCommand>()
44+
val ninjaCommandPattern = Pattern.compile("""^\[(\d+)/(\d+)\]\s+(.+)$""")
45+
46+
GZIPInputStream(FileInputStream(gzFile)).bufferedReader().use { reader ->
47+
reader.lineSequence().forEach { line ->
48+
val matcher = ninjaCommandPattern.matcher(line)
49+
if (matcher.matches()) {
50+
val command = matcher.group(3)
51+
52+
// Only process compilation commands (with -c flag), not linking commands
53+
if (command.contains(" -c ") && command.contains("clang")) {
54+
val compileCommand = parseCompilationCommand(command, androidRoot)
55+
if (compileCommand != null) {
56+
compileCommands.add(compileCommand)
57+
}
58+
}
59+
}
60+
}
61+
}
62+
63+
return compileCommands
64+
}
65+
66+
fun parseCompilationCommand(commandLine: String, androidRoot: String): CompileCommand? {
67+
try {
68+
// Parse the command to extract compiler path, flags, and source file
69+
val parts = splitCommandLine(commandLine)
70+
if (parts.isEmpty()) return null
71+
72+
// Find the compiler executable
73+
val compilerIndex = parts.indexOfFirst { it.contains("clang") }
74+
if (compilerIndex == -1) return null
75+
76+
// Find source files (typically .c, .cpp, .cc files that are not output files)
77+
val sourceFiles = findSourceFiles(parts)
78+
if (sourceFiles.isEmpty()) return null
79+
80+
// Build the clean command (without PWD prefix)
81+
val cleanCommand = buildCleanCommand(parts, compilerIndex)
82+
83+
// Create compile command for each source file
84+
val sourceFile = sourceFiles.first() // Take the first source file
85+
86+
return CompileCommand(
87+
directory = androidRoot,
88+
command = cleanCommand,
89+
file = sourceFile
90+
)
91+
} catch (e: Exception) {
92+
println("Warning: Failed to parse command: ${e.message}")
93+
return null
94+
}
95+
}
96+
97+
98+
fun splitCommandLine(commandLine: String): List<String> {
99+
// Remove PWD= prefix if present
100+
val cleanCommand = commandLine.replace(Regex("""PWD=[^\s]+\s*"""), "")
101+
102+
// Simple command line splitting (handles basic quoting)
103+
val parts = mutableListOf<String>()
104+
var current = StringBuilder()
105+
var inQuotes = false
106+
var escapeNext = false
107+
108+
for (char in cleanCommand) {
109+
when {
110+
escapeNext -> {
111+
current.append(char)
112+
escapeNext = false
113+
}
114+
char == '\\' -> {
115+
escapeNext = true
116+
}
117+
char == '"' -> {
118+
inQuotes = !inQuotes
119+
}
120+
char == ' ' && !inQuotes -> {
121+
if (current.isNotEmpty()) {
122+
parts.add(current.toString())
123+
current = StringBuilder()
124+
}
125+
}
126+
else -> {
127+
current.append(char)
128+
}
129+
}
130+
}
131+
132+
if (current.isNotEmpty()) {
133+
parts.add(current.toString())
134+
}
135+
136+
return parts
137+
}
138+
139+
fun findSourceFiles(parts: List<String>): List<String> {
140+
val sourceExtensions = setOf(".c", ".cpp", ".cc", ".cxx", ".c++")
141+
return parts.filter { part ->
142+
sourceExtensions.any { ext -> part.endsWith(ext) } &&
143+
!part.startsWith("-") && // Not a flag
144+
!part.contains("crtbegin") && // Not a crt file
145+
!part.contains("crtend") &&
146+
File(part).extension in sourceExtensions.map { it.substring(1) }
147+
}
148+
}
149+
150+
fun buildCleanCommand(parts: List<String>, compilerIndex: Int): String {
151+
// Join all parts starting from the compiler
152+
return parts.drop(compilerIndex).joinToString(" ")
153+
}
154+
155+
fun run() {
156+
val logFile = File("out/verbose.log.gz")
157+
val outputFile = File("compile_commands.json")
158+
159+
if (!logFile.exists()) {
160+
println("Error: verbose.log.gz not found in out/ directory")
161+
return
162+
}
163+
164+
// Find Android root directory from verbose log location
165+
val androidRoot = findAndroidRoot(logFile)
166+
println("Android root directory: $androidRoot")
167+
168+
println("Parsing verbose build log...")
169+
val compileCommands = parseVerboseLog(logFile, androidRoot)
170+
171+
println("Found ${compileCommands.size} compilation commands")
172+
173+
// Generate JSON
174+
val json = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(compileCommands)
175+
176+
outputFile.writeText(json)
177+
println("Generated compile_commands.json with ${compileCommands.size} entries")
178+
179+
}
180+
}

lazybox/src/main/kotlin/cfig/lazybox/sysinfo/BootChart.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ class BootChart {
99
companion object {
1010
private val log = LoggerFactory.getLogger(BootChart::class.java)
1111
fun run() {
12+
/*
1213
"adb wait-for-device".check_call()
1314
"adb root".check_call()
1415
"adb wait-for-device".check_call()
1516
"adb shell touch /data/bootchart/enabled".check_call()
16-
Helper.adbCmd("reboot")
17+
"adb reboot".check_call()
18+
//Helper.adbCmd("reboot")
1719
"adb wait-for-device".check_call()
1820
"adb root".check_call()
1921
"adb wait-for-device".check_call()
@@ -29,6 +31,7 @@ class BootChart {
2931
TimeUnit.SECONDS.sleep(1)
3032
}
3133
}
34+
*/
3235
"header proc_stat.log proc_ps.log proc_diskstats.log".split("\\s".toRegex()).forEach {
3336
val LOGROOT = "/data/bootchart/"
3437
"adb pull ${LOGROOT}$it".check_call()
@@ -38,4 +41,4 @@ class BootChart {
3841
"xdg-open bootchart.png".check_call()
3942
}
4043
}
41-
}
44+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package cfig.lazybox.sysinfo
2+
3+
import cfig.helper.Helper
4+
import org.slf4j.LoggerFactory
5+
import java.io.File
6+
import java.io.FileOutputStream
7+
import java.nio.file.Files
8+
import java.nio.file.Paths
9+
import kotlin.io.path.deleteIfExists
10+
import kotlin.io.path.writeText
11+
12+
class ThermalInfo {
13+
private val log = LoggerFactory.getLogger(ThermalInfo::class.java)
14+
15+
fun makeTar(tarFile: String, srcDir: String) {
16+
val pyScript =
17+
"""
18+
import os, sys, subprocess, gzip, logging, shutil, tarfile, os.path
19+
def makeTar(output_filename, source_dir):
20+
with tarfile.open(output_filename, "w:xz") as tar:
21+
tar.add(source_dir, arcname=os.path.basename(source_dir))
22+
makeTar("%s", "%s")
23+
""".trim()
24+
val tmp = Files.createTempFile(Paths.get("."), "xx.", ".yy")
25+
tmp.writeText(String.format(pyScript, tarFile, srcDir))
26+
("python " + tmp.fileName).check_call()
27+
tmp.deleteIfExists()
28+
}
29+
30+
fun run() {
31+
"adb wait-for-device".check_call()
32+
"adb root".check_call()
33+
val prefix = "thermal_info"
34+
File(prefix).let {
35+
if (it.exists()) {
36+
log.info("purging directory $prefix/ ...")
37+
it.deleteRecursively()
38+
}
39+
}
40+
File(prefix).mkdir()
41+
42+
val thermalZonesOutput = Helper.powerRun("adb shell ls /sys/class/thermal", null)
43+
val thermalZones = String(thermalZonesOutput.get(0)).split("\\s+".toRegex()).filter { it.startsWith("thermal_zone") }
44+
45+
if (thermalZones.isEmpty()) {
46+
log.warn("No thermal zones found.")
47+
} else {
48+
thermalZones.forEach { zone ->
49+
log.info("pulling info from $zone")
50+
val zoneDir = File("$prefix/$zone")
51+
zoneDir.mkdir()
52+
FileOutputStream("${zoneDir.path}/type").use {
53+
SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/type", it, false)
54+
}
55+
FileOutputStream("${zoneDir.path}/temp").use {
56+
SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/temp", it, false)
57+
}
58+
}
59+
}
60+
61+
makeTar("$prefix.tar.xz", prefix)
62+
File(prefix).deleteRecursively()
63+
log.info("$prefix.tar.xz is ready")
64+
}
65+
66+
private fun String.check_call() {
67+
val ret = Helper.powerRun(this, null)
68+
val ret2 = String(ret.get(0)).trim()
69+
if (ret2.isNotEmpty()) {
70+
log.info(ret2)
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)