Android 配置 C++开发环境是进行 Android 平台上 C++开发所必需的一项任务。
1. 解决的问题
在 Android 开发过程中,如果需要进行 C++方面的开发,选择好用的 IDE 非常重要,很多人会使用 Source Insight 进行开发,首先它是收费软件,其次 Source Insight 对于资源消耗挺大,会造成电脑的卡顿。
我们知道,Android 的模块使用的是 Android.bp 或者 Android.mk 进行构建的,目前并没有 IDE 可以解析这些配置文件。
2. 实践步骤
下面以 Android12 为例说明
解决 C++工程索引问题,需要得到 compile_commands.json,然后配置 vscode + clangd 进行索引。
2.1. 生成 compile_commands.json
官方文档:build/soong/docs/compdb.md
需要导出以下环境变量:
$ export SOONG_GEN_COMPDB=1 $ export SOONG_GEN_COMPDB_DEBUG=1
建议加入到~/.bashrc
文件中,默认导入到环境变量中。
使用如下命令触发编译生成 compile_commands.json
$ make nothing
生成的 compile_commands.json 目录:
out/soong/development/ide/compdb/compile_commands.json
注意:这种方式生成的 compile_commands.json 包含了所有使用 Android.bp 定义的模块的编译数据库,使用 Android.mk 编译的模块并不包含在内。
2.2. 配置 vscode
首先需要使用 vscode 远程登录到服务器,然后增加 clangd 配置,索引一遍即可。
2.3. 热更新
添加新模块、删除模块、变更源代码的头文件后,只需要编译该模块即可,使用mm
或者mmm
或者make <target>
都可以,不需要编译整个系统。
2.4. 精简 compile_commands.json
你可能会发现,make nothing
后生成的 compile_commands.json 大概有 300M,如果打开整个 Android 目录会非常慢,一般来说,我们只会打开一个仓库,如frameworks
仓库,所以我们需要精简 compile_commands.json,然后打开特定仓库进行索引,以加快 vscode 的响应速度。
以下给出的是精简 compile_commands.json 的 python 脚本:
import json import sys from typing import * def simply_compile_commands_json(completed_json_file: 'str', simplified_json_file: 'str', repositories: 'List[str]'): with open(completed_json_file) as input_file: command_content = input_file.read() command_json = json.loads(command_content) target_command_list = [] for command in command_json: file: 'str' = command['file'] if any((file.startswith(repository) for repository in repositories)): target_command_list.append(command) with open(simplified_json_file, "w") as output_file: output_file.write(json.dumps(target_command_list, indent=4)) if __name__ == '__main__': if len(sys.argv) != 4: print('Usage: python3 {} <complete.json> <simply.json> <repo[,repo[,repo]...]>'.format(sys.argv[0])) print('Eg: python3 {} ./compile_commands.json.big ./compile_commands.json system,hardware,frameworks'.format(sys.argv[0])) exit(1) else: input_compile_commands = sys.argv[1] output_compile_commands = sys.argv[2] repositories = sys.argv[3].split(',') simply_compile_commands_json(input_compile_commands, output_compile_commands, repositories)
2.5. for kernel
对于驱动部分的代码,其实也可以使用 compile_commands.json 配合 vscode 进行索引,从 Kernel 5.10 开始就预置了脚本用于生成 compile_commands.json。
kernel-5.10$ python3 ./scripts/clang-tools/gen_compile_commands.py
生成的 compile_commands.json 在 kernel-5.10 目录下。
2.6. for Android.mk module
前面提到,对于 Android.mk 定义的模块,以上的方式是不会生成到 compile_commands.json 中的。
对于这类模块,我提出的想法是自己解析编译的命令,自己生成 compile_commands.json!
第一个问题是如何得到编译的完整命令:
其实只需要在 Android.mk 中加入如下参数,就可以打印出完整的编译命令:
LOCAL_CFLAGS += -v LOCAL_CXXFLAGS += -v
如何批量往 Android.mk 中添加以上 flag?可以通过如下脚本:
import os import re import sys import time def toggle_verbose_cflag(path: 'str'): lines = [] try: with open(path, 'r') as input: lines = input.readlines() except Exception: return verbose_cflag_count = 0 for line in lines: verbose_cflag_count += line.count('LOCAL_CFLAGS += -v') verbose_cflag_count += line.count('LOCAL_CXXFLAGS += -v') if verbose_cflag_count > 0: # remove LOCAL_CFLAGS += -v # remove LOCAL_CXXFLAGS += -v temp = [] for line in lines: if line.count('LOCAL_CFLAGS += -v') > 0 or line.count('LOCAL_CXXFLAGS += -v') > 0: pass else: temp.append(line) lines = temp else: # add LOCAL_CFLAGS += -v # add LOCAL_CXXFLAGS += -v temp = [] for line in lines: if line.strip().startswith('include') and not line.strip().startswith(r'include $(CLEAR_VARS)'): m = re.match(r'^s*', line) spaces = m.group(0) if m else '' temp.append(spaces + 'LOCAL_CFLAGS += -v' + os.linesep) temp.append(spaces + 'LOCAL_CXXFLAGS += -v' + os.linesep) temp.append(line) lines = temp with open(path, 'w') as output: output.writelines(lines) def update_androidmk(directory): # 获取当前时间戳(以秒为单位) current_time = int(time.time()) # 遍历指定目录下的所有文件和子目录 for filename in os.listdir(directory): # 构建完整的文件路径或子目录路径 path = os.path.join(directory, filename) # 如果这是一个文件(而不是一个目录),则更新其修改时间 if os.path.isfile(path): os.utime(path, (current_time, current_time)) if filename == 'Android.mk': toggle_verbose_cflag(path) # 否则,如果这是一个目录,则递归地处理其内容 elif os.path.isdir(path): update_androidmk(path) if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: python3 {} directory".format(sys.argv[0])) exit(1) else: directory = sys.argv[1] update_androidmk(directory)
通过以上脚本,给定目录,可以一键往其中的 Android.mk 添加/删除 verbose flag。
第二个问题是如何解析编译命令,生成 compile_commands.json
可以通过如下脚本实现:
import json import sys def build_compile_commands(input_file: 'str', output_file: 'str', android_top: 'str', compiler='clang'): with open(input_file) as input: target_lines = [] compile_command_list = [] for line in input: if line.count(compiler) > 0 and ( line.count('.c') > 0 or line.count('.cpp') > 0 ): target_lines.append(line.strip()) # print(len(target_lines)) for rule in target_lines: if rule.count('"') >= 2: compile_command = {} compile_command['directory'] = android_top compile_command['arguments'] = [] compile_command_file = '' items = rule.split(' ') for item in items: item = item.replace('"', '') if item.count(compiler) > 0: item = android_top + '/' + item compile_command['arguments'].append(item) if item.count('.c') > 0 or item.count('.cpp') > 0: if item.count('/') > 0: compile_command_file = item if compile_command_file != '': compile_command['file'] = compile_command_file compile_command_list.append(compile_command) with open(output_file, 'w') as output: print(json.dumps(compile_command_list, indent=4), file=output) if __name__ == '__main__': if len(sys.argv) < 4: print("Usage: python3 {} <input_file> <output_file> <android_top> [compiler]".format(sys.argv[0])) exit(1) else: input_file = sys.argv[1] output_file = sys.argv[2] android_top = sys.argv[3] compiler = 'clang' if len(sys.argv) > 4: compiler = sys.argv[4] build_compile_commands(input_file, output_file, android_top, compiler)
简单来说,就是先从编译日志中找到编译命令的行,这种行最基本的特征的会包含clang
或者clang++
关键字,compile_commands.json 中有 3 项需要填,directory 指的是安卓根目录,这个可以直接传入该脚本,注意需要是绝对路径;file 指的是这条命令编译的是哪个源文件,这个可以从编译命令中拿到,特征是以.c 或者.cpp 结尾的那一项,最后是 arguments,这一项是从编译命令中拷贝过去的,注意其中需要处理的是 clang 或者 clang++,需要加上安卓根目录路径。
以上就是使用 vscode 配置 android 开发 c++的全部内容!