如何使用CMake为单个目标生成编译数据库?
Hey there! Let's break down how to deal with that annoying situation where the same source file (like f.c in your example) shows up multiple times in the compile_commands.json file generated by cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=1. This happens when you add a single source file to multiple CMake targets—each target gets its own compile entry since the compiler flags might differ between targets. Here are three practical ways to handle it:
1. Filter Entries for a Specific Target
If you only care about the compile flags for one particular target, you can use a tool like jq to filter the database down to just the entries relevant to that target.
For example, to keep only entries for f.c that belong to a target with a specific define (like -DTARGET_A):
jq '.[] | select(.file == "f.c" and .command | test("-DTARGET_A"))' compile_commands.json > filtered_compile_commands.json
Or if each target compiles in a unique directory, filter by the directory field:
jq '.[] | select(.directory == "./build/target_a")' compile_commands.json > filtered_compile_commands.json
2. Merge Duplicate Entries (For Compatible Flags)
If the compile flags for f.c across targets are compatible (no conflicting defines or options), you can merge all the flags into a single entry for the file. Here's a quick Python script to do that:
import json from collections import defaultdict # Load the original database with open('compile_commands.json', 'r') as f: compile_db = json.load(f) # Group entries by source file path file_entries = defaultdict(list) for entry in compile_db: file_path = entry['file'] # Split the command into parts and extract flags (skip output/source files) cmd_parts = entry['command'].split() flags = [part for part in cmd_parts if not part.endswith(('.o', '.c', '.cpp')) and part != file_path] file_entries[file_path].extend(flags) # Build merged entries merged_db = [] for file_path, flags in file_entries.items(): # Get the first entry to reuse directory/output details base_entry = next(e for e in compile_db if e['file'] == file_path) # Remove duplicate flags while preserving order unique_flags = list(dict.fromkeys(flags)) # Reconstruct the compile command merged_cmd = ' '.join([base_entry['command'].split()[0]] + unique_flags + [file_path, '-o', base_entry['output']]) merged_db.append({ 'directory': base_entry['directory'], 'command': merged_cmd, 'file': file_path, 'output': base_entry['output'] }) # Save the merged database with open('merged_compile_commands.json', 'w') as f: json.dump(merged_db, f, indent=2)
Just run this script in the same directory as your compile_commands.json—it’ll generate a merged version with one entry per source file. Note: This only works if flags don’t conflict (e.g., don’t use both -DDEBUG and -DNDEBUG for the same file across targets).
3. Fix It at the CMake Level (Root Cause Solution)
The cleanest way to avoid duplicate entries is to restructure your CMake project so shared source files aren’t added directly to multiple targets. Instead, turn the shared code into a library, then link that library to your targets:
# Create a static library for shared code (f.c) add_library(shared_utils STATIC f.c) # Link the library to your executables instead of adding f.c directly add_executable(target_a main_a.c) target_link_libraries(target_a shared_utils) add_executable(target_b main_b.c) target_link_libraries(target_b shared_utils)
Now f.c will only appear once in the compile database (under the shared_utils target), and your executables will link against the precompiled library. This is the best approach for maintainability too!
内容的提问来源于stack exchange,提问作者Virgile

