The C++ submodule manager is fundamentally a set of conventions for structuring projects with Git and CMake rather than just a new tool. The goal is to make it economical to compose projects from independently developed packages. You can basically spin off a library with CI integration and with dependencies to other libraries and have it usable as a manageable dependency in other projects in a matter of minutes or less.
Basic features:
- Decentralized package storage via Git
- Deterministic dependencies via Git
- Cross-platform via Git and CMake
Additional features:
- Command line tool to automate operations on projects
- Generate project boilerplate
- Add, remove, upgrade dependencies
- Code formatting
- Build, test, and watch
- …
- CMake functions to minimize boilerplate
- Travis CI integration
- Codecov integration
See repositories with the #cppsm topic.
cppsmcommand- Supported platforms
- Installation
- Quick tour
- Subcommands
- CMake
- Travis CI
- Variables
- Conventions
The cppsm command is basically a set of fairly simple
Bash scripts that automate
various operations on projects adhering to the C++ submodule manager
conventions. All the hard work is already done by
Git, CMake, and other tools and
services used. Any cppsm project can be used and developed without the cppsm
command itself.
The cppsm command has been developed
with Bash and used at least
under macOS, Linux
(Ubuntu), Cygwin, and
Git BASH.
There currently is no "native" Windows support. I personally have no need for such support as I'm comfortable using Cygwin and Git BASH. If you'd like to work on native Windows support, or support for some other platform, then you are welcome to contribute to the project!
At the moment the cppsm scripts have only been tested under
Bash (compatible) shell
implementations. I would like to support other shells in the future.
Contributions in that regard are also welcome!
To install the cppsm command you need to clone its
repository and add its bin directory to
your PATH.
Automated installation
using the install
script is as easy as running the pipe
curl -s https://raw.githubusercontent.com/cppsm/cppsm-cli/master/install | bashusing curl or the pipe
wget -qO- https://raw.githubusercontent.com/cppsm/cppsm-cli/master/install | bashusing wget.
Manual installation is
not hard either. Just clone the
cppsm-cli repository somewhere, e.g.
$HOME/.cppsm:
git clone --single-branch https://github.com/cppsm/cppsm-cli.git "$HOME/.cppsm"And add the following lines to your
$HOME/.bashrc or $HOME/.bash_profile:
CPPSM="$HOME/.cppsm"
export PATH="$CPPSM/bin:$PATH"
. "$CPPSM/bash_completion" # NOTE: This installs optional Bash completionOptional dependencies:
-
For optional code formatting you need to have both
clang-formatandprettiercommands in path. -
For optional auto completion of GitHub urls you must have either
curlorwgetcommand andjqcommand in path. -
For optional watch commands you must have either
fswatchorwatchexeccommand in path.
Here is a quick tour of basic cppsm functionality.
Create a new empty project:
mkdir PROJECT && cd "$_"
cppsm initAt this point you could try adding dependencies or writing code, but let's actually try the hello world example:
cppsm init-hello
cppsm test
.build*/internals/helloStart hacking:
emacs internals/program/hello.cpp &
cppsm test-watchFormat project files inplace:
cppsm formatClone an existing project:
cppsm clone URL BRANCHOr clone an existing project using plain git:
git clone -b BRANCH URL/NAME.git
cd NAME
git submodule update --init # NOTE: non-recursiveAdd a required library:
cppsm add requires URL/NAME.git BRANCHRemove a previously required library:
cppsm remove requires/NAME/BRANCHUpgrade all required libraries:
cppsm upgradeBelow is reference documentation for each cppsm subcommand. Each subcommand
also prints out short instructions to the console in case the invocation is
incorrect.
When invoked without specifying a subcommand, cppsm displays a brief usage
instruction and cppsm version information.
Adds a new cppsm compatible submodule and recursively the submodules it requires to the project. This command is idempotent and can be run to e.g. add new transitive dependencies after updating submodules.
Sets up a build directory and builds the project. See
cppsm setup for the configuration variables.
Sets up a build directory, builds the project, and starts a file system watch to
build the project on any changes to project files. See
cppsm setup for the configuration variables.
Clones the specified cppsm compatible repository and its dependencies.
Formats project files inplace using clang-format and prettier.
Initializes a new project with cppsm configuration files when run in an empty
directory or updates an existing project to use the latest configuration files.
See also cppsm init-hello and
cppsm init-library.
Configuration variables:
-
NAME='...'specifies the base name for the project and defaults to the name of the current directory. -
VERSION='v1'|'...'specifies the branch and version suffix for the project.
Initializes a new project with an example "Hello, world!" program. This is only
intended for educational purposes. See also cppsm init and
cppsm init-library.
CMakeLists.txt
equipment/
testing.cpp/
v1/
provides/
CMakeLists.txt
include/
testing_v1/
[...]
library/
[...]
internals/
CMakeLists.txt
testing/
message_test.cpp
program/
hello.cpp
provides/
CMakeLists.txt
include/
message_v1/
hello.hpp
library/
hello.cpp
Initializes a new project with boilerplate for a simple library project in an
empty directory. See also cppsm init and
cppsm init-hello.
CMakeLists.txt
internals/
CMakeLists.txt
testing/
compile_synopsis_test.cpp
provides/
CMakeLists.txt
include/
${LIBRARY_NAME}_${LIBRARY_VERSION}/
synopsis.hpp
Prints out a dependency tree of submodules. This command exits with an error code in case any problems are found in the dependency tree.
Removes a previously required submodule. Note that this command does not remove submodules transitively.
Sets up a build directory. The build directory name is determined based on the configuration variables.
Configuration variables:
-
CMAKE_BUILD_TYPE=Debug|Releasespecifies which configuration to use. -
CMAKE_TOOLCHAIN_FILE=''|'...'specifies toolchain file to use. -
CXX=c++|g++|clang++|emcc|...specifies which C++ compiler to use. -
CLEAN=0|1specifies whether the build directory should be recreated from scratch. -
COVERAGE=0|1specifies whether the build should be configured to generate coverage information. Currently code coverage is only supported on GCC and Clang.
Sets up a build directory, builds the project, and runs the tests. See
cppsm setup for the configuration variables.
Sets up a build directory, builds the project, runs the tests, and starts a file
system watch to build and test the project on any changes to project files. See
cppsm setup for the configuration variables.
Pulls the current git branch and updates all cppsm managed submodules to the versions in the branch.
Upgrades all cppsm managed submodules to latest remote versions and runs
cppsm init to update configuration files.
CMake boilerplate is provided for projects and simple libraries, tests, and executables.
conventional-project.cmake
is a CMake script that (only) defines a number of CMake functions for projects
that adhere to the project structure.
Typically, when using the C++ submodule manager, one does not directly call these functions as they are called by the automatically generated boilerplate code.
Recursively descends into the specified directory tree stopping at directories
containing aCMakeLists.txt script and adds those
target directories to the project with
add_subdirectory.
Recursively descends into the specified directory tree stopping at project submodule directories and adds those submodules to the project.
- If a project submodule contains a
.cppsmdirectory, then theprovidesdirectory of the submodule is added with theadd_conventional_targets_underfunction.
- Otherwise when a project submodule contains a
CMakeLists.txtscript, then the directory is added to the project withadd_subdirectory.
Adds conventional targets into a project.
Specifically, calls
and, when called from the top-level of a CMake source tree, also calls
conventional-targets.cmake
is a CMake script that (only) defines a number of CMake functions for defining
targets that adhere to the
conventional target structure.
Adds an executable target with the given name. Assumes that the target directory
has implementation files matching the pattern program/*.{cpp,hpp}.
CMakeLists.txt
program/
*.{cpp,hpp}
Add dependencies using
target_link_libraries
separately.
Adds an executable test target per file matching pattern testing/*.cpp. The
arguments given to add_conventional_executable_tests are passed to
target_link_libraries
for each added test target.
CMakeLists.txt
testing/
*.cpp
Adds a library target with the given name. Assumes that the target directory has
public header files matching the pattern
include/${LIBRARY_NAME}_${LIBRARY_VERSION}/*.hpp
and implementation files matching the pattern library/*.{cpp,hpp}.
CMakeLists.txt
include/
${LIBRARY_NAME}_${LIBRARY_VERSION}/
*.hpp
library/
*.(cpp|hpp)
Note that inside include there is a directory whose name
${LIBRARY_NAME}_${LIBRARY_VERSION}
suggests that it should include both the library name and its major version. The
intention of the directory is to differentiate between the header files of
different targets (and their major versions).
A Travis CI
configuration file
and CI script is
provided to build and test both Debug and Release builds on various OS
(Linux, OS X, Windows) and compiler configurations
(Clang, GCC,
Visual C++,
Emscripten). Just add your project to Travis CI.
- Linux
- Clang (7)
- GCC (9)
- Emscripten (2)
- OS X
- Apple Clang (12)
- GCC (10)
- Emscripten (2)
- Windows
- MinGW GCC (8)
- Visual C++ (2017, 2019)
Configuration variables:
-
CLANG=1|0specifies whether to build with Clang. SetCLANG=0explicitly to skip building with Clang. -
CODECOV=0|1specifies whether a code coverage test is executed and results are pushed to Codecov. SetCODECOV=1explicitly to enable code coverage. -
EMSCRIPTEN=0|1specifies whether to also build with Emscripten. SetEMSCRIPTEN=1explicitly to build with Emscripten. -
FORMAT_CHECK=1|0specifies whether to check that source files are formatted as withcppsm format. SetFORMAT_CHECK=0explicitly to disable the format check. -
GCC=1|0specifies whether to build with GCC. SetGCC=0explicitly to skip building with GCC. -
INSTALL_WAIT=0|1specifies whether installation of additional packages is performed concurrently with builds when possible. SetINSTALL_WAIT=1explicitly to wait for installations to complete before starting any builds. -
UPGRADE_CHECK=1|0specifies whether, after running tests, acppsm upgradeis performed and, if something is upgraded, tests are rerun. SetUPGRADE_CHECK=0explicitly to skip the upgrade and conditional rerun of tests. -
VS2017=1|0specifies whether to build with Visual C++ 2017. SetVS2017=0explicitly to skip building with Visual C++ 2017. -
VS2019=1|0specifies whether to build with Visual C++ 2019. SetVS2019=0explicitly to skip building with Visual C++ 2019.
Several environment variables can be set to change the default behavior of one
or more cppsm commands. These variables can be used both on the CI
and also when using cppsm commands locally.
By default CTest is configured to
log output in case a test fails.
Set CTEST_OUTPUT_ON_FAILURE=0 explicitly to override the default.
By default various commands are invoked with quiet settings to reduce noise. Set
QUIET=0 explicitly to see more output from various commands.
-
GIT_QUIET=QUIETcontrols the--quietswitch for Git commands. -
MSBUILD_VERBOSITY=QUIET|MINIMAL|NORMAL|DETAILED|DIAGNOSTICcontrols the/VERBOSITYswitch for MSBuild. IfMSBUILD_VERBOSITYis not explicitly set andQUIET=1thenMSBUILD_VERBOSITYwill be set toQUIET. -
XCODE_VERBOSITY=quiet|verboseoptionally passes either-quietor-verboseflag to thexcodebuildcommand. IfXCODE_VERBOSITYis not explicitly set andQUIET=1thenXCODE_VERBOSITYwill be set toquiet.
By default the number of processors is auto detected and parallelism is set
based on the number of processors. Set NUMBER_OF_PROCESSORS explicitly to
desired number to override the default.
-
N_PARALLEL_BUILD=NUMBER_OF_PROCESSORScontrols the--paralleloption ofcmake --build. -
N_PARALLEL_TEST=NUMBER_OF_PROCESSORScontrols the--paralleloption ofctest. -
N_PARALLEL_UPDATE=NUMBER_OF_PROCESSORScontrols the--jobsoption ofgit submodule update.
By default scripts do not output trace information to reduce noise. Set
TRACE=1 explicitly to see trace output.
XTRACE=TRACEcontrols whether toset -xto enable Bash xtrace.
C++ submodule manager projects adhere to conventions to make it simple to operate on projects and targets programmatically and also to make room for both independently developed projects and different versions of a single project to coexist.
In order to understand how these conventions translate into practice, it can be
helpful to play with an example. The cppsm init-hello
script is written for this purpose to generate a simple example project.
Every cppsm project must conform to the project structure, whose rules are
codified into functions defined in the
conventional-project.cmake script and many of
the subcommands of the cppsm command.
A project contains a .cppsm subdirectory
(containing the boilerplate files)
and four optional directories as follows:
- The
equipmentdirectory may contain any number of project submodules that the project internally depends upon. - The
internalsdirectory may contain any number of target directories that are internal to the project. - The
providesdirectory may contain any number of target directories that are provided for dependant projects. - The
requiresdirectory may contain any number of project submodules that the provided targets depend upon.
A project submodule either
contains a project or just a CMakeLists.txt script. In the former
case the submodule is treated as a cppsm project whose targets under
provides will be added to the build and in the latter
case the submodule is treated as a foreign CMake project to be added to the
build.
A target directory simply
contains a CMakeLists.txt script that defines targets.
Note that in the common case when only a single target directory is needed under
internals or provides, there
is no need to create a nested directory for it and the CMakeLists.txt script
can go directly under the internals or
provides directory.
In a cppsm project one may optionally structure targets according to conventions
codified into functions defined in the
conventional-targets.cmake script. In that case
a target directory may simultaneously contain:
- A library in the
include/${LIBRARY_NAME}_${LIBRARY_VERSION}andlibrarydirectories. - Any number of executable tests
in the
testingdirectory. - An executable program in the
programdirectory.
C++ submodule manager projects and project submodules are versioned such that the branches of a project are named after their (major) version numbers and the version numbers are part of the paths of submodules containing C++ submodule manager projects.
The ${PROJECT_NAME} of a C++
submodule manager project is taken to be the last (/ separated) part of the
URL of the Git project sans the .git suffix.
The ${PROJECT_VERSION} of a
cppsm project is the name of a branch.
When added as a submodule dependency, a cppsm project whose name is
${PROJECT_NAME} and whose version is
${PROJECT_VERSION} goes either to
equipment/${PROJECT_NAME}/${PROJECT_VERSION} or to
requires/${PROJECT_NAME}/${PROJECT_VERSION} depending
on the nature of the dependency.
In a cppsm project one may optionally version libraries such that their header
files start with a directory name of the form ${LIBRARY_NAME}_${VERSION},
which is also the name of the CMake library target
and of the namespace inside of which all the public code of the library resides
in.
Note that when combined with the project versioning conventions this allows a single project to potentially use multiple (major or incompatible) versions of a single project or library. This can be very important in large projects composed of separately develop subprojects or libraries.
Note that often LIBRARY_NAME may be the same as
PROJECT_NAME and LIBRARY_VERSION may be the same as
PROJECT_VERSION, but this is not required.