Updated on 2023-10-03
Presenting a simple to create understandable and maintainable builds for projects with dependencies
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.
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.
Note: The demonstration uses Windows and batch files, but you can easily adapt this to Linux (or both) and use *nix shell scripts.
First in each library that has dependent libraries, create a "fetch_deps.cmd" file. You'll use that to fetch any dependencies.
@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:
@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):
@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
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.
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:
None needed
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.
This library has some source files as well as header files, plus has htcw_bits as a dependency.
@rmdir htcw_bits /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_bits
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}"
)
This library is my markup parser library for embedded and IoT. It depends on htcw_io.
@rmdir htcw_io /S /Q
@git clone https://github.com/codewitch-honey-crisis/htcw_io
@.\htcw_io\fetch_deps.cmd
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.
This library, like htcw_bits, has no dependencies.
None needed
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}"
)
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.
Now make a fetch_deps.cmd, as before, but it works slightly differently:
@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 ..
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!