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:
Additional features:
See repositories with the #cppsm
topic .
cppsm
commandcppsm
commandThe 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 | bash
using curl
or the pipe
wget -qO- https://raw.githubusercontent.com/cppsm/cppsm-cli/master/install | bash
using 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 completion
Optional dependencies:
For optional code formatting
you need to have both
clang-format
and
prettier
commands in path.
For optional auto completion
of GitHub urls you must have either
curl
or
wget
command and
jq
command in path.
For optional watch commands
you must have either fswatch
or
watchexec
command in path.
Here is a quick tour of basic cppsm
functionality.
Create a new empty project:
mkdir PROJECT && cd "$_"
cppsm init
At 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/hello
Start hacking:
emacs internals/program/hello.cpp &
cppsm test-watch
Format project files inplace:
cppsm format
Clone an existing project:
cppsm clone URL BRANCH
Or clone an existing project using plain git:
git clone -b BRANCH URL/NAME.git
cd NAME
git submodule update --init # NOTE: non-recursive
Add a required library:
cppsm add requires URL/NAME.git BRANCH
Remove a previously required library:
cppsm remove requires/NAME/BRANCH
Upgrade all required libraries:
cppsm upgrade
Below is reference documentation for each cppsm
subcommand. Each subcommand
also prints out short instructions to the console in case the invocation is
incorrect.
cppsm
When invoked without specifying a subcommand, cppsm
displays a brief usage
instruction and cppsm
version information.
cppsm add (requires|equipment) <url> <branch>
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.
cppsm build
Sets up a build directory and builds the project. See
cppsm setup
for the configuration variables.
cppsm build-watch
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.
cppsm clone <url> <branch>
Clones the specified cppsm compatible repository and its dependencies.
cppsm format
Formats project files inplace using clang-format and prettier .
cppsm init
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.
cppsm init-hello
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
cppsm init-library
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
cppsm list
Prints out a dependency tree of submodules. This command exits with an error code in case any problems are found in the dependency tree.
cppsm remove <path>
Removes a previously required submodule. Note that this command does not remove submodules transitively.
cppsm setup
Sets up a build directory. The build directory name is determined based on the configuration variables.
Configuration variables:
CMAKE_BUILD_TYPE=Debug|Release
specifies which configuration to use .
CMAKE_TOOLCHAIN_FILE=''|'...'
specifies toolchain file to use .
CC=cc|gcc|clang|emcc|...
specifies which C compiler to use .
CXX=c++|g++|clang++|emcc|...
specifies which C++ compiler to use .
CLEAN=0|1
specifies whether the build directory
should be recreated from scratch.
COVERAGE=0|1
specifies whether the build
should be configured to generate coverage information. Currently code coverage
is only supported on GCC and
Clang .
cppsm test
Sets up a build directory, builds the project, and runs the tests. See
cppsm setup
for the configuration variables.
cppsm test-watch
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.
cppsm update
Pulls the current git branch and updates all cppsm managed submodules to the versions in the branch.
cppsm upgrade
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
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.
add_conventional_targets_under(directory)
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
.
add_conventional_targets_provided_under(directory)
Recursively descends into the specified directory tree stopping at project submodule directories and adds those submodules to the project.
.cppsm
directory, then the provides
directory of the submodule is added with the
add_conventional_targets_under
function.CMakeLists.txt
script, then
the directory is added to the project with
add_subdirectory
.add_conventional_targets()
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
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.
add_conventional_executable(name)
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.
add_conventional_executable_tests(...)
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
add_conventional_library(${LIBRARY_NAME}_${LIBRARY_VERSION})
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.
Configuration variables:
CLANG=1|0
specifies whether to build with
Clang . Set CLANG=0
explicitly to skip building
with Clang .
CODECOV=0|1
specifies whether a code
coverage test is executed and results are pushed to
Codecov . Set CODECOV=1
explicitly to enable code
coverage.
EMSCRIPTEN=0|1
specifies whether to
also build with Emscripten . Set EMSCRIPTEN=1
explicitly to build with Emscripten .
FORMAT_CHECK=1|0
specifies whether
to check that source files are formatted as with
cppsm format
. Set FORMAT_CHECK=0
explicitly to disable
the format check.
GCC=1|0
specifies whether to build with
GCC . Set GCC=0
explicitly to skip building with
GCC .
INSTALL_WAIT=0|1
specifies whether
installation of additional packages is performed concurrently with builds when
possible. Set INSTALL_WAIT=1
explicitly to wait for installations to
complete before starting any builds.
UPGRADE_CHECK=1|0
specifies
whether, after running tests, a cppsm upgrade
is performed
and, if something is upgraded, tests are rerun. Set UPGRADE_CHECK=0
explicitly to skip the upgrade and conditional rerun of tests.
VS2017=1|0
specifies whether to build with
Visual C++ 2017. Set VS2017=0
explicitly to skip building with
Visual C++ 2017.
VS2019=1|0
specifies whether to build with
Visual C++ 2019. Set VS2019=0
explicitly 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.
CTEST_OUTPUT_ON_FAILURE=1|0
By default CTest is configured to
log output in case a test fails .
Set CTEST_OUTPUT_ON_FAILURE=0
explicitly to override the default.
QUIET=1|0
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=QUIET
controls the --quiet
switch for Git commands.
MSBUILD_VERBOSITY=QUIET|MINIMAL|NORMAL|DETAILED|DIAGNOSTIC
controls the
/VERBOSITY
switch for
MSBuild . If
MSBUILD_VERBOSITY
is not explicitly set and QUIET=1
then
MSBUILD_VERBOSITY
will be set to QUIET
.
XCODE_VERBOSITY=quiet|verbose
optionally passes either -quiet
or -verbose
flag to the xcodebuild
command. If XCODE_VERBOSITY
is not explicitly set and QUIET=1
then
XCODE_VERBOSITY
will be set to quiet
.
NUMBER_OF_PROCESSORS=1|2|...
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_PROCESSORS
controls the
--parallel
option of cmake --build
.
N_PARALLEL_TEST=NUMBER_OF_PROCESSORS
controls the
--parallel
option of ctest
.
N_PARALLEL_UPDATE=NUMBER_OF_PROCESSORS
controls the
--jobs
option of git submodule update
.
TRACE=0|1
By default scripts do not output trace information to reduce noise. Set
TRACE=1
explicitly to see trace output.
XTRACE=TRACE
controls whether to
set -x
to 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:
equipment
directory
may contain any number of project submodules that the
project internally depends upon.internals
directory
may contain any number of target directories that are
internal to the project.provides
directory
may contain any number of target directories that are
provided for dependant projects.requires
directory
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:
include/${LIBRARY_NAME}_${LIBRARY_VERSION}
and library
directories.testing
directory.program
directory.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.