Skip to content

Commit 25278af

Browse files
committed
feat(rules): add cmake-guidelines.md to prevent target name conflicts
1 parent dfc0b78 commit 25278af

1 file changed

Lines changed: 269 additions & 0 deletions

File tree

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
paths: ["**/CMakeLists.txt", "**/*.cmake"]
3+
---
4+
5+
# CMake Guidelines
6+
7+
> **Purpose**: Prevent CMake target name conflicts and build system issues
8+
> **Impact**: Eliminates build failures caused by duplicate or reserved target names
9+
10+
## Core Principle
11+
12+
Always use unique, project-prefixed target names to avoid conflicts with CMake reserved names, dependency targets, and targets in other subdirectories.
13+
14+
## Reserved Target Names
15+
16+
Never use these names as CMake targets - they are reserved by CMake or common conventions:
17+
18+
| Reserved Name | Reason | Alternative |
19+
|---------------|--------|-------------|
20+
| `test` | Reserved by CTest | `${PROJECT_NAME}_test` |
21+
| `install` | Reserved by CMake | `${PROJECT_NAME}_install` |
22+
| `clean` | Reserved by CMake | N/A (don't create) |
23+
| `all` | Reserved by CMake | N/A (don't create) |
24+
| `package` | Reserved by CPack | `${PROJECT_NAME}_package` |
25+
| `rebuild_cache` | Reserved by CMake | N/A |
26+
| `edit_cache` | Reserved by CMake | N/A |
27+
| `uninstall` | Common convention | `${PROJECT_NAME}_uninstall` |
28+
29+
## Naming Conventions
30+
31+
### Library Targets
32+
33+
```cmake
34+
# Use project name as prefix
35+
add_library(${PROJECT_NAME}_core STATIC
36+
src/core.cpp
37+
)
38+
39+
add_library(${PROJECT_NAME}_utils STATIC
40+
src/utils.cpp
41+
)
42+
43+
# For header-only libraries
44+
add_library(${PROJECT_NAME}_headers INTERFACE)
45+
```
46+
47+
### Executable Targets
48+
49+
```cmake
50+
# Main executable - can use project name directly
51+
add_executable(${PROJECT_NAME}
52+
src/main.cpp
53+
)
54+
55+
# Additional executables with descriptive suffixes
56+
add_executable(${PROJECT_NAME}_cli
57+
src/cli_main.cpp
58+
)
59+
60+
add_executable(${PROJECT_NAME}_server
61+
src/server_main.cpp
62+
)
63+
```
64+
65+
### Test Targets
66+
67+
```cmake
68+
# Individual test executables
69+
add_executable(${PROJECT_NAME}_test_unit
70+
tests/unit_tests.cpp
71+
)
72+
73+
add_executable(${PROJECT_NAME}_test_integration
74+
tests/integration_tests.cpp
75+
)
76+
77+
# Register with CTest
78+
add_test(NAME ${PROJECT_NAME}_unit_tests
79+
COMMAND ${PROJECT_NAME}_test_unit
80+
)
81+
```
82+
83+
### Alias Targets
84+
85+
```cmake
86+
# Create namespaced aliases for external consumption
87+
add_library(MyProject::core ALIAS ${PROJECT_NAME}_core)
88+
add_library(MyProject::utils ALIAS ${PROJECT_NAME}_utils)
89+
```
90+
91+
## Pre-Creation Verification
92+
93+
Before adding a new target, verify it doesn't conflict:
94+
95+
### 1. Check Existing Targets
96+
97+
```bash
98+
# List all targets in the build directory
99+
cmake --build build --target help 2>/dev/null | grep -v "^\.\.\." | sort
100+
101+
# Or using CMake directly
102+
cd build && cmake --graphviz=targets.dot .. && cat targets.dot
103+
```
104+
105+
### 2. Search for Potential Conflicts
106+
107+
```bash
108+
# Search for target definitions in all CMakeLists.txt
109+
grep -rn "add_library\|add_executable" --include="CMakeLists.txt" .
110+
111+
# Check imported targets from dependencies
112+
grep -rn "IMPORTED" --include="*.cmake" .
113+
```
114+
115+
### 3. Verify Against Dependencies
116+
117+
```cmake
118+
# In CMakeLists.txt, check if target exists before creating
119+
if(NOT TARGET ${PROJECT_NAME}_mylib)
120+
add_library(${PROJECT_NAME}_mylib STATIC src/mylib.cpp)
121+
endif()
122+
```
123+
124+
## Common Conflict Scenarios
125+
126+
### Scenario 1: Duplicate Target in Subdirectory
127+
128+
```
129+
CMake Error: add_library cannot create target "utils" because another
130+
target with the same name already exists.
131+
```
132+
133+
**Solution**: Use project-prefixed names
134+
135+
```cmake
136+
# Bad
137+
add_library(utils ...) # Conflicts with parent/sibling directory
138+
139+
# Good
140+
add_library(${PROJECT_NAME}_utils ...)
141+
```
142+
143+
### Scenario 2: Conflict with Dependency Target
144+
145+
```
146+
CMake Error: add_library cannot create target "core" because an imported
147+
target with the same name already exists.
148+
```
149+
150+
**Solution**: Check imported targets and use unique names
151+
152+
```cmake
153+
# Check if target already exists (from find_package)
154+
if(TARGET core)
155+
message(WARNING "Target 'core' already exists, using prefixed name")
156+
endif()
157+
158+
add_library(${PROJECT_NAME}_core ...)
159+
```
160+
161+
### Scenario 3: Reserved Name Collision
162+
163+
```
164+
CMake Error: The target name "test" is reserved or not valid for certain
165+
CMake features.
166+
```
167+
168+
**Solution**: Never use reserved names
169+
170+
```cmake
171+
# Bad
172+
add_executable(test tests/main.cpp)
173+
174+
# Good
175+
add_executable(${PROJECT_NAME}_tests tests/main.cpp)
176+
```
177+
178+
## Debugging Commands
179+
180+
### List All Targets
181+
182+
```bash
183+
# Using cmake --build
184+
cmake --build build --target help
185+
186+
# Using make (if using Makefiles)
187+
make -C build help
188+
189+
# Using ninja (if using Ninja)
190+
ninja -C build -t targets all
191+
```
192+
193+
### Visualize Target Dependencies
194+
195+
```bash
196+
# Generate Graphviz dot file
197+
cmake --graphviz=build/targets.dot -B build
198+
199+
# Convert to PNG (requires graphviz)
200+
dot -Tpng build/targets.dot -o build/targets.png
201+
```
202+
203+
### Find Target Definition
204+
205+
```bash
206+
# Search for where a target is defined
207+
grep -rn "add_library(mylib\|add_executable(mylib" --include="CMakeLists.txt" .
208+
209+
# Search in cmake modules
210+
grep -rn "add_library(mylib\|IMPORTED" --include="*.cmake" /usr/share/cmake
211+
```
212+
213+
## Best Practices
214+
215+
### Project Structure
216+
217+
```cmake
218+
# Top-level CMakeLists.txt
219+
cmake_minimum_required(VERSION 3.16)
220+
project(MyProject VERSION 1.0.0)
221+
222+
# Set project-wide naming prefix
223+
set(TARGET_PREFIX ${PROJECT_NAME})
224+
225+
# Add subdirectories
226+
add_subdirectory(src)
227+
add_subdirectory(tests)
228+
```
229+
230+
### Subdirectory Pattern
231+
232+
```cmake
233+
# src/CMakeLists.txt
234+
add_library(${TARGET_PREFIX}_core
235+
core/core.cpp
236+
core/utils.cpp
237+
)
238+
239+
target_include_directories(${TARGET_PREFIX}_core
240+
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
241+
)
242+
```
243+
244+
### Export Configuration
245+
246+
```cmake
247+
# For install and package export
248+
install(TARGETS ${PROJECT_NAME}_core ${PROJECT_NAME}_utils
249+
EXPORT ${PROJECT_NAME}Targets
250+
LIBRARY DESTINATION lib
251+
ARCHIVE DESTINATION lib
252+
RUNTIME DESTINATION bin
253+
)
254+
255+
install(EXPORT ${PROJECT_NAME}Targets
256+
FILE ${PROJECT_NAME}Targets.cmake
257+
NAMESPACE ${PROJECT_NAME}::
258+
DESTINATION lib/cmake/${PROJECT_NAME}
259+
)
260+
```
261+
262+
## Checklist Before Commit
263+
264+
- [ ] All targets use `${PROJECT_NAME}_` prefix
265+
- [ ] No reserved names used (test, install, clean, all, package)
266+
- [ ] Alias targets use namespace pattern (`MyProject::target`)
267+
- [ ] Test targets registered with CTest using unique names
268+
- [ ] No conflicts with imported dependency targets
269+
- [ ] `cmake --build build --target help` shows no duplicates

0 commit comments

Comments
 (0)