Project Setup¶
File Structure¶
The Most Basic structure of a c++ project looks as follows:
❯ tree -L 1
[ 332] ./
├── [ 346] build/
├── [ 0] ext/
├── [ 0] include/
├── [ 16] src/
├── [ 0] tests/
├── [ 518] vcpkg/
├── [ 186] CMakeLists.txt
├── [ 82] compile_commands.json -> /home/parth/Desktop/programming/cpp/learncpp.com/chap0/build/compile_commands.json
├── [ 0] README.md
├── [4.1K] tasks.py
├── [ 348] vcpkg-configuration.json
└── [ 38] vcpkg.json
Organization¶
Systems¶
CMake
: Used to generate Build System Configurations.Ninja
: Build system to compile the project.gcc
: Compiler for the project.vcpkg
: Package Manager for C/C++. Should be added as submodule to your project.invoke
: Python Library to run the CMake Configurations.
Folders¶
build/
: Stores the build files and executablesext/
: Stores external librariesinclude/
: Stores the.h
and.hpp
header files made by us.src/
: Holds the.c
and.cpp
files made by us.tests/
: Holds the Tests for the codebasevcpkg/
: Holds Microsoft C++ Package Manager
Files¶
CmakeLists.txt
: Contains the build guide for the codebase including pointing towards the needed libraries and compilation options.compile_commands.json
: Contains the Intellisense information for the IDEs.README.md
: Project Information for people to build and understandtasks.py
: Script containinginvoke
which makes it easier to call CMake with custom commands.vcpkg.json
: Used byvcpkg
to hold the libraries requested by user.
Setup Process¶
-
mkdir && cd
Project Directory -
Initialize Git
- Add
vcpkg
as submodule to your project - Add the essential libraries for the project using
vcpkg
:- Initialize
vcpkg
: - Add Libraries:
- Initialize
- Write the
CMakeLists.txt
for the project. - Write the
tasks.py
""" This requires invoke to be installed on the machine (not venv), which can be done via: yay -S python-invoke """ from datetime import datetime from hashlib import md5 from pathlib import Path from shutil import rmtree as shutil_rmtree from typing import Optional from invoke import task PROJECT: str = "chap0" SRC_PATH: Path = Path(__file__).parent VCPKG_TOOLCHAIN = SRC_PATH / "vcpkg/scripts/buildsystems/vcpkg.cmake" WORKSPACE: Path = Path("/tmp/builds/cpp") MD5: Optional[str] = None BUILD_PATH: Optional[Path] = Path(f"{SRC_PATH}/build") INSTALL_PATH: Optional[Path] = None def get_md5(content: str) -> str: global MD5 if MD5 is None: MD5 = md5(str.encode(content)).hexdigest() return MD5 def get_cmake_workspace() -> Path: hash = get_md5(str(SRC_PATH)) return WORKSPACE / f"{PROJECT}_{SRC_PATH.name}_{hash}" def get_build_path() -> Path: global BUILD_PATH if BUILD_PATH is None: BUILD_PATH = get_cmake_workspace() / "build" return BUILD_PATH def get_install_path() -> Path: global INSTALL_PATH if INSTALL_PATH is None: INSTALL_PATH = get_cmake_workspace() / "install" return INSTALL_PATH @task def info(c, topic="all"): """Show project info.""" if topic == "all": print(f"Project = {PROJECT}") print(f"Source path = {SRC_PATH}") print(f"Build path = {get_build_path()}") print(f"Install path = {get_install_path()}") elif topic == "build_path": print(get_build_path()) elif topic == "install_path": print(get_install_path()) else: print("Error: Valid 'topic' names are 'build_path'/'install_path'") @task def config(c): """Run Make configure.""" do_config(c) def do_config(c): build_path = get_build_path() build_path.mkdir(parents=True, exist_ok=True) cmd = [ "cmake", "-S", str(SRC_PATH), "-B", str(build_path), "-GNinja", "-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", f"-DCMAKE_TOOLCHAIN_FILE={str(VCPKG_TOOLCHAIN)}", ] c.run(" ".join(cmd), pty=True) # Symlink compile_commands.json src_ccdb_file = SRC_PATH / "compile_commands.json" build_ccdb_file = build_path / "compile_commands.json" if build_ccdb_file.exists(): if not src_ccdb_file.exists(): src_ccdb_file.symlink_to(build_ccdb_file) @task def build(c, config=False): """Run builds via CMake.""" build_path = get_build_path() if not build_path.exists(): if config: do_config(c) else: print("Error: build path doesn't exist.") return cmd = ["cmake", "--build", str(build_path)] c.run(" ".join(cmd), pty=True) @task def install(c): """Run install via CMake.""" build_path = get_build_path() install_path = get_install_path() if not build_path.exists(): print("Error: build path doesn't exist.") return cmd = ["cmake", "--install", str(build_path)] c.run(" ".join(cmd), env={"DESTDIR": install_path}, pty=True) @task def clean(c): """Clean build directory.""" # Don't clean during CppCon and the week before/after _, week, _ = datetime.now().isocalendar() if week in (36, 37, 38): print(f"I'm sorry I can't do that Dave as the current week is {week}.") return build_path = get_build_path() if build_path.exists(): shutil_rmtree(build_path) print(f"Cleaned {build_path}") else: print("Build path absent. Nothing to do.") @task(pre=[clean]) def clean_all(c): """Clean build and install directory.""" install_path = get_install_path() if install_path.exists(): shutil_rmtree(install_path) print(f"Cleaned {install_path}") else: print("Install path absent. Nothing to do.") @task def ls(c): """List files using lsd and skip vcpkg and .cache folder""" cmd = [ "lsd", "--tree", "--ignore-glob vcpkg", "--ignore-glob .cache", ] c.run(" ".join(cmd), pty=True)
- Run the Invoke Command to configure and build your project for first time.
This step will generate the
build
directory and thecompile_commands.json
which will help with project intellisense. - Add
.clang-format
file in the root of the project. This file contains the guidelines to format the project. You can select the guidelines from one of the many poplular ones found here. Add one to your project as follows: - Add
.clang-tidy
for Linting. It will help you provide diagnostics related to code errors, static analysis and code quality. Guide on clang-tidy can be found here. It can be added to project as follows:--- # Enable ALL the things! Except not really # misc-non-private-member-variables-in-classes: the options don't do anything Checks: "*,\ -google-readability-todo,\ -altera-*,\ -fuchsia-*,\ fuchsia-multiple-inheritance,\ -llvm-header-guard,\ -llvm-include-order,\ -llvmlibc-*,\ -misc-non-private-member-variables-in-classes" WarningsAsErrors: '' CheckOptions: - key: 'bugprone-argument-comment.StrictMode' value: 'true' # Prefer using enum classes with 2 values for parameters instead of bools - key: 'bugprone-argument-comment.CommentBoolLiterals' value: 'true' - key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts' value: 'true' - key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression' value: 'true' - key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison' value: 'true' - key: 'readability-simplify-boolean-expr.ChainedConditionalReturn' value: 'true' - key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment' value: 'true' - key: 'readability-uniqueptr-delete-release.PreferResetCall' value: 'true' - key: 'cppcoreguidelines-init-variables.MathHeader' value: '<cmath>' - key: 'cppcoreguidelines-narrowing-conversions.PedanticMode' value: 'true' - key: 'readability-else-after-return.WarnOnUnfixable' value: 'true' - key: 'readability-else-after-return.WarnOnConditionVariables' value: 'true' - key: 'readability-inconsistent-declaration-parameter-name.Strict' value: 'true' - key: 'readability-qualified-auto.AddConstToQualified' value: 'true' - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration' value: 'true' # These seem to be the most common identifier styles - key: 'readability-identifier-naming.AbstractClassCase' value: 'lower_case' - key: 'readability-identifier-naming.ClassCase' value: 'CamelCase' - key: 'readability-identifier-naming.ClassConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.ClassMemberCase' value: 'lower_case' - key: 'readability-identifier-naming.ClassMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstantMemberCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstantParameterCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstantPointerParameterCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstexprFunctionCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstexprMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.ConstexprVariableCase' value: 'lower_case' - key: 'readability-identifier-naming.EnumCase' value: 'lower_case' - key: 'readability-identifier-naming.EnumConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.FunctionCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalConstantPointerCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalFunctionCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalPointerCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalVariableCase' value: 'lower_case' - key: 'readability-identifier-naming.InlineNamespaceCase' value: 'lower_case' - key: 'readability-identifier-naming.LocalConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.LocalConstantPointerCase' value: 'lower_case' - key: 'readability-identifier-naming.LocalPointerCase' value: 'lower_case' - key: 'readability-identifier-naming.LocalVariableCase' value: 'lower_case' - key: 'readability-identifier-naming.MacroDefinitionCase' value: 'UPPER_CASE' - key: 'readability-identifier-naming.MemberCase' value: 'lower_case' - key: 'readability-identifier-naming.MethodCase' value: 'lower_case' - key: 'readability-identifier-naming.NamespaceCase' value: 'lower_case' - key: 'readability-identifier-naming.ParameterCase' value: 'lower_case' - key: 'readability-identifier-naming.ParameterPackCase' value: 'lower_case' - key: 'readability-identifier-naming.PointerParameterCase' value: 'lower_case' - key: 'readability-identifier-naming.PrivateMemberCase' value: 'lower_case' - key: 'readability-identifier-naming.PrivateMemberPrefix' value: 'm_' - key: 'readability-identifier-naming.PrivateMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.ProtectedMemberCase' value: 'lower_case' - key: 'readability-identifier-naming.ProtectedMemberPrefix' value: 'm_' - key: 'readability-identifier-naming.ProtectedMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.PublicMemberCase' value: 'lower_case' - key: 'readability-identifier-naming.PublicMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.ScopedEnumConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.StaticConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.StaticVariableCase' value: 'lower_case' - key: 'readability-identifier-naming.StructCase' value: 'CamelCase' - key: 'readability-identifier-naming.TemplateParameterCase' value: 'CamelCase' - key: 'readability-identifier-naming.TemplateTemplateParameterCase' value: 'CamelCase' - key: 'readability-identifier-naming.TypeAliasCase' value: 'lower_case' - key: 'readability-identifier-naming.TypedefCase' value: 'lower_case' - key: 'readability-identifier-naming.TypeTemplateParameterCase' value: 'CamelCase' - key: 'readability-identifier-naming.UnionCase' value: 'lower_case' - key: 'readability-identifier-naming.ValueTemplateParameterCase' value: 'CamelCase' - key: 'readability-identifier-naming.VariableCase' value: 'lower_case' - key: 'readability-identifier-naming.VirtualMethodCase' value: 'lower_case' ...
- Commit the project and push to github.
- Exit the Neovim and restart again to trigger
clangd
. Theclangd
will look forcompile_commands.json
in the root of the project folder which will contain all the intellisense for the added packages and files.
Usage¶
Intellisense¶
For a correctly configured lsp-zero
and clangd
, the intellisense will be picked up automatically using the compiled_commands.json
file.
clang-format¶
Set the lsp-zero`` to autoformat buffer on save to apply formatting on saving and make sure to have
.clang-format` file in root of the project directory.
clang-tidy¶
You can either run clang-tidy
manually or the clangd
will automatically pick the .clang-tidy
file from root of project and run it to the files on each buffer saves for linting and static analysis.
Build¶
You can build the project by running:
This will save the build files in the folder build/
and can be run from it.