Designing CMaize’s C++ Target Classes
Designing CMaize’s Target Component described the design of the language agnostic pieces of CMaize’s Target component. This page describes the design of the Target component specific to C++ targets.
What are the C++ Target Classes?
As the name suggests, the C++ target classes are the classes in CMaize’s Target component which describe C++ targets including libraries and executables.
Why Do We Need C++ Target Classes?
CMaize’s C++ target classes will provide an object-oriented representation of CMake’s native C++ targets. C++ target objects are needed to ensure that C++ targets can be treated in a manner that is compatible with CMaize’s target support for coding languages not supported by traditional CMake. CMaize’s target classes also help extract away much of the repetition resulting from using CMake’s C++ targets directly.
C++ Target Considerations
To motivate the considerations for the C++ Target classes consider the traditional CMake workflow for setting up C++ targets:
# Step 1: Declare the target for the library
add_library(...)
# Step 2: Set compiler features needed by the target
target_compile_features(...)
# Step 3: Set up the include directories for the target. This includes the
# include paths needed to build the target and the include paths the
# target will need after it has been installed.
target_include_directories(...)
# Step 4: Ensure that the header files defining the public API of the target
# will be installed.
install(...)
# Step 5: Register the dependencies of the target
target_link_libraries(...)
# Step 6: Add source files to the target (if there are any)
target_source(...)
# Step 7: Decide where to install the target and configure the packaging
# file for finding the target
install(...)
# Step 8: Install the packaging file.
install(...)
The above workflow is for C++ libraries, but is nearly identical to that of
an executable except that when building an executable step 1 calls
add_executable
instead of add_library
.
This leads to the following considerations:
- CMake target creation
Designing CMaize’s Target Component called for each CMaize target to have an underlying CMake target. The various C++ target classes will be responsible for actually creating and initializing those targets.
- C++ libraries and executables
Software written in C++ is comprised of two types of objects: libraries and executables. The C++ target classes must be able to represent executables and libraries.
- header-only libraries
Many modern C++ libraries are header-only. Conceptually, such libraries follow the same workflow, but skip step 6 (adding source files to the target). Unfortunately, CMake requires header-only libraries to provide different inputs for a number of the steps (in the above code snippet, the actual differences are part of the ellipses and are thus not shown). The C++ target classes should abstract away the differences between setting-up header-only and compiled libraries.
- abstract base
As the above code snippet showed, the process for configuring and installing a C++ library is nearly identical to that of configuring and installing a C++ executable. The CMaize objects should factor out the common routines and abstract away the differences. From an object-oriented design standpoint this suggests a common abstract base class.
- install location
CMake is great at managing targets during the build process; however, CMake requires the package maintainer to more or less manually configure how the target will behave after installation. This includes ensuring the package can still find all of its files and dependencies. This process is facilitated by having each target know where its install location will be.
C++ Target Design
Fig. 9 is a class diagram for the classes responsible
for modeling C++ targets. Following from consideration abstract base
we have introduced the CXXTarget
class to factor out the common
infrastructure. The CXXTarget
class implements the make_target
function
defined by the BuildTarget
class in terms of a series of virtual protected
member functions, i.e, _create_target
, _set_compile_features
,
_set_include_directories
, _set_link_libraries
, and _set_sources
.
Following from the C++ libraries and executables consideration, we
derive from CXXTarget
the CXXExecutable
and CXXLibrary
classes.
These classes simply override _create_target
so that it calls
add_executable
or add_library
, respectively. Finally, to address the
header-only libraries consideration we derive the
CXXInterfaceLibrary
class from the CXXLibrary
class.
Proposed API
To create a C++ library:
set(include_files ...) # Somehow get a list of header files
set(source_files ...) # Somehow get a list of source files
# Create a library called "my_first_cmaize_cxx_library" and assign it to the
# variable cxx_library
CXXLibrary(CTOR cxx_library "my_first_cmaize_cxx_library")
# Actually create the CMake target powering the library
CXXLibrary(make_target
"${cxx_library}"
INCLUDES "${include_files}"
SOURCES "${source_files}"
)
Summary
- CMake target creation
The literal CMake targets are created by the derived classes when overriding
CXXTarget
’s_create_target
function.- C++ libraries and executables
The
CXXLibrary
andCXXExecutable
classes respectively model a C++ library and a C++ executable.- header-only libraries
The
CXXInterfaceLibrary
class derives from theCXXLibrary
class and modifies the workflows so that the underlying CMake calls are consistent with declaring a header-only library.- abstract base
The
CXXTarget
class has been introduced to factor out common infrastructure.