Last active
March 11, 2025 02:31
-
-
Save kristopherjohnson/17fc6b6d0f02ac4e2fd41bd2e2fac289 to your computer and use it in GitHub Desktop.
Script for generating a new CMake-based C++ project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
generate_cpp_project.py | |
This script generates a standard C++ project structure with CMake build system. | |
The generated project includes: | |
- A main executable | |
- A library with header files | |
- Unit tests using Doctest | |
- CMake configuration with CPack support | |
- Makefile with common targets | |
- README, LICENSE, and .gitignore files | |
Usage: | |
./generate_cpp_project.py [options] | |
Options: | |
--name NAME Set the project name (default: example) | |
--std STD Set the C++ standard (default: 17) | |
-h, --help Show this help message | |
""" | |
import os | |
import sys | |
import argparse | |
import shutil | |
from pathlib import Path | |
def parse_args(): | |
"""Parse command line arguments.""" | |
parser = argparse.ArgumentParser( | |
description="Generate a standard C++ project structure with CMake build system." | |
) | |
parser.add_argument( | |
"--name", default="example", help="Name of the project (default: example)" | |
) | |
parser.add_argument( | |
"--std", default="17", help="C++ standard to use (default: 17)" | |
) | |
return parser.parse_args() | |
def create_directory_structure(project_name): | |
"""Create the project directory structure.""" | |
directories = [ | |
"", | |
"lib", | |
"lib/src", | |
"lib/include", | |
f"lib/include/{project_name}", | |
"src", | |
"test", | |
] | |
for directory in directories: | |
Path(f"{project_name}/{directory}").mkdir(parents=True, exist_ok=True) | |
print(f"Created directory structure for project '{project_name}'") | |
def create_cmakelists(project_name, cpp_standard): | |
"""Create the main CMakeLists.txt file.""" | |
cmakelists_content = f"""cmake_minimum_required(VERSION 3.14) | |
project({project_name} VERSION 0.1.0 LANGUAGES CXX) | |
# Set C++ standard | |
set(CMAKE_CXX_STANDARD {cpp_standard}) | |
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
set(CMAKE_CXX_EXTENSIONS OFF) | |
# Include directories | |
include_directories(lib/include) | |
# Set output directories | |
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${{CMAKE_BINARY_DIR}}/lib) | |
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${{CMAKE_BINARY_DIR}}/lib) | |
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${{CMAKE_BINARY_DIR}}/bin) | |
# Build library | |
file(GLOB LIB_SOURCES "lib/src/*.cpp") | |
add_library({project_name}_lib ${{LIB_SOURCES}}) | |
target_include_directories({project_name}_lib PUBLIC lib/include) | |
# Build executable | |
file(GLOB APP_SOURCES "src/*.cpp") | |
add_executable({project_name} ${{APP_SOURCES}}) | |
target_link_libraries({project_name} PRIVATE {project_name}_lib) | |
# Testing with Doctest | |
include(FetchContent) | |
FetchContent_Declare( | |
doctest | |
URL https://github.com/doctest/doctest/archive/v2.4.11.tar.gz | |
) | |
FetchContent_MakeAvailable(doctest) | |
# Build tests | |
file(GLOB TEST_SOURCES "test/*.cpp") | |
add_executable(run_tests ${{TEST_SOURCES}}) | |
target_link_libraries(run_tests PRIVATE {project_name}_lib doctest::doctest) | |
target_compile_definitions(run_tests PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) | |
enable_testing() | |
add_test(NAME unit_tests COMMAND run_tests) | |
# Installation | |
install(TARGETS {project_name} | |
RUNTIME DESTINATION bin | |
LIBRARY DESTINATION lib | |
ARCHIVE DESTINATION lib) | |
install(TARGETS {project_name}_lib | |
RUNTIME DESTINATION bin | |
LIBRARY DESTINATION lib | |
ARCHIVE DESTINATION lib) | |
install(DIRECTORY lib/include/ DESTINATION include) | |
# CPack configuration | |
include(InstallRequiredSystemLibraries) | |
set(CPACK_RESOURCE_FILE_LICENSE "${{CMAKE_CURRENT_SOURCE_DIR}}/LICENSE") | |
set(CPACK_PACKAGE_VERSION_MAJOR "${{PROJECT_VERSION_MAJOR}}") | |
set(CPACK_PACKAGE_VERSION_MINOR "${{PROJECT_VERSION_MINOR}}") | |
set(CPACK_PACKAGE_VERSION_PATCH "${{PROJECT_VERSION_PATCH}}") | |
include(CPack) | |
""" | |
with open(f"{project_name}/CMakeLists.txt", "w") as f: | |
f.write(cmakelists_content) | |
print(f"Created CMakeLists.txt") | |
def create_library_files(project_name): | |
"""Create the library source and header files.""" | |
# Header file | |
header_content = f"""#ifndef {project_name.upper()}_LIBRARY_H | |
#define {project_name.upper()}_LIBRARY_H | |
namespace {project_name} {{ | |
/** | |
* @brief A simple function that returns an integer | |
* @param value The input value | |
* @return The input value multiplied by 2 | |
*/ | |
int multiply_by_two(int value); | |
}} // namespace {project_name} | |
#endif // {project_name.upper()}_LIBRARY_H | |
""" | |
with open(f"{project_name}/lib/include/{project_name}/library.h", "w") as f: | |
f.write(header_content) | |
# Source file | |
source_content = f"""#include "{project_name}/library.h" | |
namespace {project_name} {{ | |
int multiply_by_two(int value) {{ | |
return value * 2; | |
}} | |
}} // namespace {project_name} | |
""" | |
with open(f"{project_name}/lib/src/library.cpp", "w") as f: | |
f.write(source_content) | |
print(f"Created library files") | |
def create_main_file(project_name): | |
"""Create the main source file.""" | |
main_content = f"""#include <iostream> | |
#include "{project_name}/library.h" | |
int main(int argc, char* argv[]) {{ | |
try {{ | |
std::cout << "Welcome to {project_name}!" << std::endl; | |
int input = 21; | |
int result = {project_name}::multiply_by_two(input); | |
std::cout << input << " multiplied by 2 is " << result << std::endl; | |
return 0; | |
}} catch (const std::exception& e) {{ | |
std::cerr << "error: " << e.what() << std::endl; | |
return 1; | |
}} | |
}} | |
""" | |
with open(f"{project_name}/src/main.cpp", "w") as f: | |
f.write(main_content) | |
print(f"Created main.cpp") | |
def create_test_file(project_name): | |
"""Create the test file.""" | |
test_content = f"""#include <doctest/doctest.h> | |
#include "{project_name}/library.h" | |
TEST_CASE("Testing the multiply_by_two function") {{ | |
SUBCASE("Testing with positive values") {{ | |
CHECK({project_name}::multiply_by_two(1) == 2); | |
CHECK({project_name}::multiply_by_two(2) == 4); | |
CHECK({project_name}::multiply_by_two(10) == 20); | |
}} | |
SUBCASE("Testing with zero") {{ | |
CHECK({project_name}::multiply_by_two(0) == 0); | |
}} | |
SUBCASE("Testing with negative values") {{ | |
CHECK({project_name}::multiply_by_two(-1) == -2); | |
CHECK({project_name}::multiply_by_two(-5) == -10); | |
}} | |
}} | |
""" | |
with open(f"{project_name}/test/test_main.cpp", "w") as f: | |
f.write(test_content) | |
print(f"Created test_main.cpp") | |
def create_makefile(project_name): | |
"""Create the Makefile.""" | |
makefile_content = """# Makefile for CMake-based C++ project | |
.PHONY: all build test run clean install package debug release | |
# Default build type | |
BUILD_TYPE ?= Debug | |
all: build | |
build: | |
cmake -S . -B build -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -Wno-dev | |
cmake --build build --config $(BUILD_TYPE) | |
test: build | |
cd build && ctest -C $(BUILD_TYPE) --output-on-failure | |
run: build | |
./build/bin/$(notdir $(CURDIR)) | |
clean: | |
rm -rf build | |
install: build | |
cmake --install build --config $(BUILD_TYPE) | |
package: build | |
cd build && cpack -C $(BUILD_TYPE) | |
debug: | |
$(MAKE) BUILD_TYPE=Debug build | |
release: | |
$(MAKE) BUILD_TYPE=Release build | |
""" | |
with open(f"{project_name}/Makefile", "w") as f: | |
f.write(makefile_content) | |
print(f"Created Makefile") | |
def create_readme(project_name, cpp_standard): | |
"""Create the README.md file.""" | |
readme_content = f"""# {project_name} | |
A C++ project template with CMake build system. | |
## Requirements | |
- CMake 3.14 or higher | |
- C++{cpp_standard} compatible compiler | |
## Building the Project | |
### Using CMake Directly | |
```bash | |
# Configure | |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
# Build | |
cmake --build build --config Debug | |
``` | |
For a Release build: | |
```bash | |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release | |
cmake --build build --config Release | |
``` | |
### Using the Makefile | |
```bash | |
# Debug build (default) | |
make build | |
# Release build | |
make BUILD_TYPE=Release build | |
# or | |
make release | |
``` | |
## Running | |
```bash | |
# Using CMake build output directly | |
./build/bin/{project_name} | |
# Using Makefile | |
make run | |
``` | |
## Testing | |
```bash | |
# Using CTest directly | |
cd build && ctest -C Debug --output-on-failure | |
# Using Makefile | |
make test | |
``` | |
## Installing | |
```bash | |
# Using CMake directly | |
cmake --install build | |
# Using Makefile | |
make install | |
``` | |
## Packaging | |
```bash | |
# Using CPack directly | |
cd build && cpack -C Debug | |
# Using Makefile | |
make package | |
``` | |
## Project Structure | |
- `lib/`: Library code | |
- `include/`: Library header files | |
- `src/`: Library source files | |
- `src/`: Main application source files | |
- `test/`: Test source files | |
## License | |
This project is licensed under the MIT License - see the LICENSE file for details. | |
""" | |
with open(f"{project_name}/README.md", "w") as f: | |
f.write(readme_content) | |
print(f"Created README.md") | |
def create_gitignore(): | |
"""Create a .gitignore file.""" | |
gitignore_content = """# Prerequisites | |
*.d | |
# Compiled Object files | |
*.slo | |
*.lo | |
*.o | |
*.obj | |
# Precompiled Headers | |
*.gch | |
*.pch | |
# Compiled Dynamic libraries | |
*.so | |
*.dylib | |
*.dll | |
# Fortran module files | |
*.mod | |
*.smod | |
# Compiled Static libraries | |
*.lai | |
*.la | |
*.a | |
*.lib | |
# Executables | |
*.exe | |
*.out | |
*.app | |
# Build directories | |
build/ | |
out/ | |
cmake-build-*/ | |
# IDE files | |
.idea/ | |
.vscode/ | |
.vs/ | |
*.swp | |
*.swo | |
*~ | |
# CMake | |
CMakeLists.txt.user | |
CMakeCache.txt | |
CMakeFiles/ | |
CMakeScripts/ | |
Testing/ | |
Makefile.in | |
cmake_install.cmake | |
install_manifest.txt | |
compile_commands.json | |
CTestTestfile.cmake | |
_deps/ | |
# macOS | |
.DS_Store | |
.AppleDouble | |
.LSOverride | |
# Windows | |
Thumbs.db | |
ehthumbs.db | |
Desktop.ini | |
# Package files | |
*.tar.gz | |
*.tar.bz2 | |
*.zip | |
*.deb | |
*.rpm | |
*.dmg | |
*.pkg | |
""" | |
with open(f"{project_name}/.gitignore", "w") as f: | |
f.write(gitignore_content) | |
print(f"Created .gitignore") | |
def create_license(project_name): | |
"""Create a LICENSE file with MIT license.""" | |
current_year = 2025 # Using current year from the description | |
license_content = f"""MIT License | |
Copyright (c) {current_year} {project_name} | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
""" | |
with open(f"{project_name}/LICENSE", "w") as f: | |
f.write(license_content) | |
print(f"Created LICENSE file") | |
def main(): | |
"""Main function.""" | |
args = parse_args() | |
global project_name | |
project_name = args.name | |
global cpp_standard | |
cpp_standard = args.std | |
print(f"Generating C++ project '{project_name}' with C++{cpp_standard} standard...") | |
# Create project structure | |
create_directory_structure(project_name) | |
create_cmakelists(project_name, cpp_standard) | |
create_library_files(project_name) | |
create_main_file(project_name) | |
create_test_file(project_name) | |
create_makefile(project_name) | |
create_readme(project_name, cpp_standard) | |
create_gitignore() | |
create_license(project_name) | |
print(f"\nProject '{project_name}' has been generated successfully!") | |
print(f"To build the project:") | |
print(f" cd {project_name}") | |
print(f" make build") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is the prompt I gave to Claude.ai to produce the first version of this script:
Claude neglected to include the necessary
global
declarations inmain()
, but otherwise did well.I tried this prompt with a few ChatGPT models as well, but the scripts it generated were terrible.