首页 Android C++ 生成 compile_commands.json
文章
取消

Android C++ 生成 compile_commands.json

Android C++ 程序开发现状

在 Android 下开发 C++ 程序,我见过绝大多数人都是不使用任何语法插件,就靠硬写,写完之后再根据编译报错来修改语法错误。这也怪不得程序员,一方面,Android 使用 Arm 平台的 clang 编译器,跟 x86 平台的开发环境并不是很兼容;另一方面,Android 要求我们将 C++ 程序放在 vendor 目录下,但是我们包含的头文件却是去 kernel/include 下面找的。如果想要自己配置插件的开发环境,通常都是一顿操作猛如虎,结果还是各种报错。

Bear

Bear 是一个用来生成包含编译时选项的数据库的工具。通常它将输出文件 compile_commands.json,可以供给例如 vscode 中的 Microsoft C/C++ 插件或者 vim 中的 YouCompleteMe 插件使用,让插件可以正确的解析当前 C++ 源文件的各种依赖信息,例如头文件包含路径。

但是如果想要在 Android 开发环境中使用 Bear 有个很大的问题,那就是 mm/mma 这类命令它不能被 Bear 识别,即使你将其封装为脚本,最后得到的 compile_commands.json 也是空空如也的。

compdb

但是深入了解 Android 之后,我发现其实 Android 内置有 compdb 可以用来生成 compile_commands.json,流程上只需要设置几个环境变量即可:

1
2
3
4
5
6
7
8
cd /path/to/android/root    # Android 源码根路径
source build/envsetup.sh
lunch xxxx-userdebug
cd /path/to/app/dir         # 项目 Android.mk/Android.bp 所在目录
export SOONG_GEN_COMPDB=1
export SOONG_GEN_COMPDB_DEBUG=1
export SOONG_LINK_COMPDB_TO=$(pwd)
mm

等待一段时间后,就会在 /path/to/app/dir 目录下看到生成好的 compile_commands.json 了。需要注意的是,有些平台似乎不接受 SOONG_LINK_COMPDB_TO,不管怎么设置都固定生成在 Android 源码根目录,所以如果你在项目目录找不到该文件或者该文件无效,就去 Android 根目录看看。

如果还是没有 compile_commands.json,我们也可以借助 ninja 来生成,建议使用 Github 上最新的 ninja,否则可能会生成空文件:

1
2
cd /path/to/android/root    # Android 源码根路径
ninja -f out/combined-kona.ninja -t compdb | tee ./compile_commands.json

注意此处 combined-xxx.ninja 文件叫什么名字取决于你所使用的平台。

通常这个文件大的离谱,我这边生成的有 300 MB,这样如果直接给插件用的话,会导致加载时间过长。因此,我们需要对该文件进行一些裁剪。

compile_commands.json 的基本格式是:

1
2
3
4
5
6
7
8
9
10
[
    {
        "directory": "/path/to/android/root",
        "arguments": [
            "xxx"
        ],
        "file": "xxx"
    },
    ...
]

如果是 ninja 生成的 compile_commands.json,格式会有些许不同,它会用 command 字段替换掉 arguments 字段,二者都是有效的,都可以被 vscode/YouCompleteMe 等识别。

我们需要关注 file 字段,这里指示了每个参与编译的 cpp 文件。我们可以将只包含我们模块内的 cpp 文件的那些 json 块单独提取出来,组成一个 KB 级别的小 compile_commands.json 文件,这样,插件读取该文件会更轻松。

这里用 python 简单写个处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python3
import sys
import json
import os
from pathlib import Path

if len(sys.argv) != 4:
    print("Usage: python3 main.py <project directory> <input compiledb path> <output compiledb path>")
    sys.exit()

files = Path(sys.argv[1])
result = list(map(os.path.realpath, map(str, list(files.rglob("*.cpp")) + list(files.rglob("*.c")))))

with open(sys.argv[2]) as compdb_input:
    compdb_origin = json.load(compdb_input)
    compdb_gen = []
    for obj in compdb_origin:
        full_path = obj["directory"] + '/' + obj["file"]
        if full_path in result:
            print("Find file: %s" % full_path)
            compdb_gen.append(obj)

with open(sys.argv[3], "w") as compdb_out:
    compdb_out.write(json.dumps(compdb_gen, indent=1))

用法是 python3 main.py 模块路径 输入的json文件路径 输出的json文件路径

最后,配置好对应插件即可,例如对于 vscode,我们需要在 .vscode/c_cpp_properties.json 中添加下面高亮行的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "intelliSenseMode": "linux-gcc-x64",
            "compileCommands": "compile_commands.json"
        }
    ],
    "version": 4
}

重新打开一下工作区,然后用鼠标追踪一下 #include 的头文件,会发现这些头文件都指向了 Android 目录的,而不再是本机的 /usr/include/

本文由作者按照 CC BY 4.0 进行授权

C++20 Concept

Type-C 接口 CC 针脚的工作模式