引言
现假设你在走读某个以cmake方式构建的大工程,如llvm中clang。突然看到某段代码时,突然脑中冒出一个小idea:这里若不用A,而是用B会如何。你会怎样去测试这个小想法呢?
- 在当前代码库中直接修改,构建,运行新生成可执行程序
- 使用git的分支管理,先新建一个分支切换过程,再使用方式 1
- 将整个项目拷贝到新的地方,在新的地方修改代码、构建、运行
方式1直接污染了原始代码,且测试代码经常为临时性修改,版本管理混乱。
方式2一定程度上避免了方式1的版本管理混乱问题,但分支间切换可能会造成项目的build重新重新进行,大工程构建很耗时,一通操作+等待之后,可能都忘了为神马要做这个修改。
方式3,比方式2更耗时,更繁琐,且很占用磁盘。
怎么办?我就是想简简单单做的小测试啊!!!小idea组成和触发大idea,小idea实现受挫,直接扼杀大idea。所以小idea实现的便利性、时间和空间成本是完成大idea至关重要的因素。
设计
这里提出一种out-of-project的解决方式。
- 新建一个empty目录,然后cd to it,让我们从0开始。开始前明确我们要修改的源码名
src_name
、和最终源码会编入的target(exe/so文件), - 原始cmake构建时输出
compile_commands.json
。可通过CMakeLists.txt中加set(CMAKE_EXPORT_COMPILE_COMMANDS on)
, 或者cmake命令调用时命令参数中加-DCMAKE_EXPORT_COMPILE_COMMANDS=on
- 将
src_name
源文件从大项目中拷贝到PWD,按照你的小idea进行随意修改 - 从
compile_commands.json
中选出我们想修改的c/cpp的源文件编译命令,修改其中输入文件为PWD/src_name, 输出的object文件也修改为PWD的源文件名src_name
对应的对象文件名object_name
,这里取object_name=src_name+'.o'
。 - 从项目的目录中搜索target对应的link.txt文件,文件中指定了该target的链接命令。修改其中链接命令中用到对象文件名为PWD的
object_name
,输出为PWD的target,保持链接过程中用到的其他输入不变。这样就做到了仅仅替换target链接用到的指定src_name
文件,完成偷梁换柱的目的。若src_name
首先被编入某个静态库,然后被链接到target,则直接在链接命令的前面加上PWD的object_name
,静态库中的src_name
中所有实现链接时将不会被使用。
上述out-of-project的解决方案,带来的构建耗时、磁盘占用基本都做到了最小,且不会污染源代码。
实现
这里写个简单的python脚本,i_want_to_replace.py
, 使用方法
python i_want_to_replace.py src_name.cpp target_name /path/to/project/build
结果生成一个makefile模板,Makefile.in
。同时打印中会提醒拷贝src_name
的操作。
#i_want_to_replace.py
import sys
import os
import json
def usage():
print('%s src_name target build_path' % sys.argv[0])
def find_cdb_in_build_path(build_path):
ans = os.path.join(build_path, 'compile_commands.json')
if os.path.exists(ans):
return ans
print('compile_commands not found!')
return None
def find_command_in_compile_db(cbd_name, src_name):
cbd_data = json.load(open(cbd_name))
for entry in cbd_data:
if src_name in entry['file']:
print('you can use the old code as a model by type:')
print('cp ' + entry['file'] + ' .')
return entry['command']
def replace_compile_cmd_with_pwd_src(cmd, src_name):
cmd_list = cmd.split()
idx = 0
out_obj = src_name+'.o'
new_compile_cmd = []
found_output_obj = False
while idx < len(cmd_list):
if cmd_list[idx] == '-o': #found output
new_compile_cmd.append('-o')
new_compile_cmd.append(out_obj)
found_output_obj = True
idx += 2
continue
if cmd_list[idx].endswith(src_name): #found input
new_compile_cmd.append(src_name)
idx += 1
continue
new_compile_cmd.append(cmd_list[idx])
idx += 1
if not found_output_obj:
new_compile_cmd.append('-o')
new_compile_cmd.append(out_obj)
return ' '.join(new_compile_cmd)
def find_all_link_txt_file(path, ans):
if os.path.isdir(path):
for sub_file in os.listdir(path):
find_all_link_txt_file(os.path.join(path, sub_file), ans)
else:
if path.endswith('link.txt'):
ans.append(path)
def find_link_cmd_of_target(build_path, target_name):
link_txts = []
find_all_link_txt_file(build_path, link_txts)
ans = ['cd']
for link_file in link_txts:
if target_name in link_file:
ans.append('/'.join(link_file.split('/')[:-3]))
ans.append(' && ' + open(link_file).read())
return ' '.join(ans)
def replace_link_cmd_with_pwd_object(link_cmd, obj_name):
cmd_list = link_cmd.split()
idx = 0
ans = []
found_direct_object_input = False
while idx < len(cmd_list):
if cmd_list[idx].endswith(obj_name):
ans.append('${PWD}/'+obj_name)
found_direct_object_input = True
idx += 1
elif cmd_list[idx] == '-o':
ans.append('-o')
ans.append('${PWD}/app')
idx += 2
else:
ans.append(cmd_list[idx])
idx += 1
if not found_direct_object_input:
ans.append('${PWD}/'+obj_name)
return ' '.join(ans)
def main():
if len(sys.argv) < 4:
usage()
return
src_name = sys.argv[1]
target_name = sys.argv[2]
build_path = sys.argv[3]
cbd_name = find_cdb_in_build_path(build_path)
if not cbd_name:
return
cmd = find_command_in_compile_db(cbd_name, src_name)
new_compile_cmd = replace_compile_cmd_with_pwd_src(cmd, src_name)
link_cmd = find_link_cmd_of_target(build_path, target_name)
new_link_cmd = replace_link_cmd_with_pwd_object(link_cmd, src_name+'.o')
with open('Makefile.in','w') as f:
f.write('app:\n')
f.write('\t'+new_compile_cmd+'\n')
f.write('\t'+new_link_cmd+'\n')
if __name__ == '__main__':
main()
One more word
很享受从0开始的搞事,如在一个空旷的练武场上随意挥洒拳脚,如在一张白纸随意书写,如一颗糖在纯净水中迅速化开。
反之,像糖在高渗透压水化不开,在山一样的工程代码中修改和测试,手和脑都像被定住了一样,久久无法动弹。