Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Use VS Code, CMake, and Batch Files to Simplify Your C++ Builds

5.00/5 (2 votes)
3 Oct 2023CPOL3 min read 9.5K  
Presenting a simple to create understandable and maintainable builds for projects with dependencies
Build scripts are often complicated, sometimes even requiring things like Python or Perl to make them work. Here's a simple way to handle even moderately complicated projects.

Note: Code isn't included directly because it's expected you'll be using github repos for your projects. There are valid github repos used in the examples.

Introduction

I hate build scripts. I hate the fact that I need them. They usually turn into a zoo to maintain and it's just yet another obstacle between myself and the compiled binary.

I learned enough CMake to happen on a clever little way to compose projects (with the help of shell scripts - batch files in this case) with dependent projects that doesn't rely on a big mess of a build environment.

Maybe it's just enough to be dangerous, but it works for me. Maybe it will work for you.

Prerequisites

  • You'll need VS Code.
  • You'll need the Microsoft CMake VS Code extension.
  • In addition, you'll need the Microsoft VS Code C++ extension.
  • You'll also need a C++ compiler (GCC, MSVC, Clang, whatever).
  • You'll need git installed from git-scm.org and in your path.

Note: The demonstration uses Windows and batch files, but you can easily adapt this to Linux (or both) and use *nix shell scripts.

Creating the Shell Scripts

First in each library that has dependent libraries, create a "fetch_deps.cmd" file. You'll use that to fetch any dependencies.

BAT
@rmdir my_lib /S /Q
@git clone https://github.com/my-account/my_lib my_lib

This will fetch "my_lib" from its git repo into a "my_lib" folder, first removing the folder if it exists.

You would add two lines like this for any dependent project in your project that requires the dependencies. You may also need to recursively fetch_deps.cmd in a dependent project. I do this in my htcw_uix library to fetch my htcw_gfx library and its dependencies. Here's my fetch_deps.cmd for htcw_uix:

BAT
@rmdir htcw_gfx /S /Q
@git clone https://github.com/codewitch-honey-crisis/gfx htcw_gfx
@.\htcw_gfx\fetch_deps.cmd

Notice the third line where fetch_deps.cmd in htcw_gfx is called.

And a slightly more complicated one (used in my htcw_gfx library):

BAT
@rmdir htcw_data /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_data
@rmdir htcw_ml /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_ml
@.\htcw_ml\fetch_deps.cmd 

Creating the CMakeLists.txt Files

These are relatively straightforward. For each library, you want to export its includes and build its sources. Here's a simple example:

cmake_minimum_required(VERSION 3.24)
project(my_lib VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_library(my_lib src/my_lib.cpp)
target_link_libraries(my_lib my_dependency1 my_dependency2)
target_include_directories(my_lib PUBLIC
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}"
)

Here, we've created my_lib which has two dependencies: my_dependency1 and my_dependency2. You'd include those in your fetch_deps.cmd for this my_lib project.

Note that we export the PROJECT_BINARY_DIR as well as the source include directory. This apparently avoids problems with certain project configurations, so I include it.

Rubber, Meet Road

htcw_bits

Putting it together, let's see a real world example, starting with my htcw_bits library which has no dependencies, and no source files - just includes:

fetch_deps.cmd

None needed

CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(htcw_bits VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_library(htcw_bits INTERFACE)
target_include_directories(htcw_bits INTERFACE
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}"
)

Here, INTERFACE is the magic sauce to say we want a header only library.

htcw_io

This library has some source files as well as header files, plus has htcw_bits as a dependency.

fetch_deps.cmd

BAT
@rmdir htcw_bits /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_bits

CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(htcw_io VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_library(htcw_io src/io_stream.cpp)
target_link_libraries(htcw_io htcw_bits)
target_include_directories(htcw_io PUBLIC
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}"
)

htcw_ml

This library is my markup parser library for embedded and IoT. It depends on htcw_io.

fetch_deps.cmd

BAT
@rmdir htcw_io /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_io
@.\htcw_io\fetch_deps.cmd

CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(htcw_ml VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_library(htcw_ml INTERFACE)
target_include_directories(htcw_ml INTERFACE
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}"
)

Note there we don't reference htcw_io, because this library - htcw_ml, is header only and so doesn't link.

htcw_data

This library, like htcw_bits, has no dependencies.

fetch_deps.cmd

None needed

CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(htcw_data VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_library(htcw_data INTERFACE)
target_include_directories(htcw_data INTERFACE
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}"
)

Main Project

Say we wanted to create a project that used htcw_data and htcw_ml together.

Let's put a ./lib folder under the project directory, but don't navigate into it.

fetch_deps.cmd

Now make a fetch_deps.cmd, as before, but it works slightly differently:

BAT
@rmdir lib /S /Q
@mkdir lib
@cd lib
@git clone https://github.com/codewitch-honey-crisis/htcw_ml htcw_ml
@.\htcw_ml\fetch_deps.cmd
@git clone https://github.com/codewitch-honey-crisis/htcw_data htcw_data
@cd ..

CMakeLists.txt

Here's an example CMakeLists.txt for this project:

cmake_minimum_required(VERSION 3.24)

project(example VERSION 1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "")

add_subdirectory(lib/htcw_bits)
add_subdirectory(lib/htcw_data)
add_subdirectory(lib/htcw_io)
add_subdirectory(lib/htcw_ml)

add_executable(example 
    src/main.cpp
)
target_link_libraries(example htcw_bits htcw_io htcw_data htcw_ml)
target_include_directories(example PUBLIC
    "${PROJECT_SOURCE_DIR}"
    "${PROJECT_BINARY_DIR}"
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_SOURCE_DIR}/src"
  )

And that's it! Now you can run fetch_deps.cmd from the root project folder, and then open in VS Code, right click on the root CMakeLists.txt and click "Build All Projects".

Enjoy!

History

  • 3rd October, 2023 - Initial submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)