C++ submodule manager GitHub stars Gitter Build Status Code Coverage

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 .

Contents

cppsm command

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!

Installation

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:

Quick tour

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

Subcommands

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:

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:

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

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.

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).

Travis CI

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:

Variables

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.

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.

TRACE=0|1

By default scripts do not output trace information to reduce noise. Set TRACE=1 explicitly to see trace output.

Conventions

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.

Conventional project structure

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:

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.

Conventional target structure

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:

Conventional project versioning

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.

Conventional library versioning

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.