Skip to content

Latest commit

 

History

History
762 lines (586 loc) · 32.5 KB

File metadata and controls

762 lines (586 loc) · 32.5 KB

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:

See repositories with the #cppsm topic.

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 | 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:

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.

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:

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 .cppsm directory, then the provides directory of the submodule is added with the add_conventional_targets_under function.
  • Otherwise when a project submodule contains a CMakeLists.txt script, then the directory is added to the project with add_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.

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.

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.

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.

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

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:

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:

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.