VPM is a framework for managing source level C++ packages and their dependencies, with a goal to provide flexible configuration management optmized for large scale software development. It is not intended for deployment and installation of software to end users, but to help with the composition of software from individually versioned modules, called packages.
The framework is built over CMake
and constitutes a set of CMake
scripts that deal with fetching and combining packages into a build, and command line tool (vpm
) to make the invocation of the framework more convenient.
Packages live in one or more package roots in a structured layout:
<package_root>/<package>/<version>[-<variant>]/...
Internally a package has one or both of:
- A
configure.cmake
script. This script is included in all dependent packages. It exposes public properties of the package, like include directories, import targets, macros, and variables. - A
CMakeLists.txt
script. This script is included in the normalCMake
way to build the package.
They way VPM implements configuration managements is through package versions. Versions can be selected globally or directly when specifying dependencies within a package, and only a single version of a particular package is used throughout a single build. From the point of view of the framework, versions are immutable once fetched, and the framework will make no attempt to update existing packages. The suggested way to deal with package upgrades is therefore to maintain immutable release versions, and change the version number when bugs are fixed or new features are added. This is also cleaner and more explict from a package consumer point of view than pulling repositories for changes. For development lines, it is advisable to clone and maintain repos manually.
Typically versions correspond to branches in a git repo. Using tags could be an alternative to branches for release versions, and more clearly communicates immutability. However, tags would require a clone of some branch and subsequent checkout of the tag, whereas branches can be cloned directly. This simplifies the fetch command configuration, and also easily allows for shallow clones of auto-downloaded packages. Tags are currently not supported.
When looking for packages, the framework searches the package roots based on the version and variant specified for each package. These properties determinde the possible physical locations for a package. SCM branches, tags, etc, are never inspected to search for or validates versions.
Variants are a way to set up multiple different implementations of a package that all provide the same public interface and functionality. These would typically be used to package up pre-built libraries for different architectures and platforms separately, or to search for those libraries on the system, or even build them from source. Different variants point to different source repositories (potentially in different package roots and/or from different package repositories and hosts), and the repository name is defined as <package>-<variant>
. Variant names are arbitrary and user defined.
The vpm framework lives in the package tree, so first create a place to store packages. E.g.,
mkdir ~/packages/
Now create a directory for the vpm package itself, and clone the framework into a versioned subdirectory.
mkdir ~/packages/vpm
git clone git@github.com:ancapdev/vpm -b 1.06.00 ~/packages/vpm/1.06.00
Finally, build the vpm
tool. The framework has 2 build modes, self build, and package build. Its default mode is a self build, so simply run CMake
the standard way to build vpm
. Later, the vpm
tool will take care of invoking CMake with the necessary arguments to run a package build.
mkdir ~/build && mkdir ~/build/vpm && cd ~/build/vpm
cmake ~/packages/vpm/1.06.00
make
The vpm
tool is a standalone binary with no dependencies. Copy it to your path to use it. E.g.,
cp ./vpm ~/bin/
The tool captures many of its default options from the environment at build time, such as the framework path, the package roots, the CMake
generator to use, and the architecture width. These can optionally be overriden by adding a .vpm.yml configuration file to the user home directory. An example config file exists in the framework directory.
In order for the framework to find and download packages, one or more repository descriptions must be added to one or more of the package roots. Repository descriptions live in .repos
subdirectories under the package roots. Typically, package repository descriptions will be git repositories themselves. For example, to add the https://github.com/ancapdev repository, clone git@github.com:ancapdev/packages.meta
into the .repos
folder. The name for the repository is arbitrary, but if more than 1 exists, they will be searched in alphabetical order when fetching new packages.
mkdir ~/packages/.repos
git clone git@github.com:ancapdev/packages.meta ~/packages/.repos/ancapdev
From time to time, as new packages are added, it may be necessary to update the repository description. Do this the same way you would update any other repo. E.g.,
cd ~/packages/.repos/ancapdev
git pull
A package build can be initiated by invoking CMake
directly, or through the vpm
tool. The vpm
tool isn't strictly required, but it makes passing many of the necessary parameters to CMake
and the framework much more convenient.
vpm
supports a set of commands with a general syntax:
vpm <command> <arguments>
To get help on a specific command, run the help
command:
vpm help <command>
This will provide a description for a command and list all its options.
Arguments are either named options or unnamed. Options come in 3 forms:
- Flags: have no value, only
-<key>
. - Enumerations:
-<key>=<value>
, where value is constrained to a set. - Strings:
-<key>=<value>
, where value is any string.
Keys can be shortened to their shortest unambiguous key prefix. For example:
vpm mbuild crunch.base -what
Will run the mbuild
command with the -whatif
option.
If enumeration keys can be determined unambiguously from their values, they can be provided by value only through the +<value>
syntax. For example:
vpm mbuild crunch.base +ninja
Will run the mbuild
command with the -generator=ninja
option.
mbuild
is the meta build command. It's responsible for generating build files from packages by invoking CMake
on the vpm framework and passsing all the necessary arguments. It takes the following form:
vpm mbuild <package1>..<packageN> [options]
Where <package1>..<packageN>
is a list of of packages to build. Each package argument takes the form <name>[?<variant>][@<version>]
. For example, to generate build files for crunch.base
version 1.00.00
, and all of its dependencies, run:
vpm mbuild crunch.base@1.00.00
A full list of options can be retrieved by running vpm help mbuild
.
Configuration takes place in a few different places
The vpm
configuration in $HOME/.vpm.yml
can be used to specify some properties that affect the build. In terms of configuration, it can set the config package.
The config package is a place to set global compile flags and settings that are the same for the whole build, to ensure such settings are consistent and the resulting libraries compose correctly. Typically this package will be the same across an organization or a team, and used across all builds. The default config package is vpm.config
.
Some configuration can take place on the command line through arguments to vpm mbuild
or directly to CMake. Most importantly, package versions and variants can be specified via the syntax described above, and the config package can be overriden.
VPM will also look for a configure.cmake
script in the build directory, and this is intended to be used for more complex configuration scenarios. configure.cmake
can set any CMake
variables, but more importantly can set package versions and variants. Note however that configure.cmake
is included before the config package and before any of the dependency machinery has been created. This is to allow variables that alter the behavior of these components to be specified by the user, but it also means configure.cmake
must limits its actions to configuration only.
VPM looks for 3 files in the root of each package, all of which are optional.
This script is included in every dependent package and is used to publish include directories and other state or functionality. For example, a package foo's configure.cmake
might look like:
# Bring in dependencies that are required for foo's public headers.
vpm_depend(bar)
# Make include files visible to dependent packages
vpm_include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
# Some feature support variable
set(FOO_SUPPORTS_XYZ FALSE)
# Macro that can be used by dependent packages
macro(foo_do_something)
This is the normal CMakeLists.txt
as would be in a standard CMake
build. Additionally there are VPM specific macros and variables available to configure dependencies and check compatibilities. A typical CMakeLists.txt
might something like:
# Bring in own configure.cmake script. Ensures this is only included once per CMakeLists.txt
vpm_depend_self()
# Add dependency on boost.
# Set default version and variant in case none has been configured globally.
# Also check minimum version for the features we require.
vpm_set_default_variant(boost proxy)
vpm_set_default_version(boost 1.55.0)
vpm_minimum_version(boost 1.53.0)
vpm_depend(boost)
# Add the foo library with a few source files
vpm_add_library(foo
include/foo/foo.hpp
src/foo.cpp)
# Link foo to bar
vpm_add_link_dependency(foo bar)
This is a special file, which if present tells VPM to defer inclusion of this package until all other packages have been processed. This can be useful for generating output that depends on the global state of the build, such as embedding information about the packages used.
Aside from the files used by the framework, package layout is up to package authors. For consistency, the suggested layout is:
<package>/include/<package>/...
<package>/src/...
<package>/doc/...
<package>/test/...
<package>/example/...
Signature | Description |
---|---|
vpm_depend(<package>..<package>) |
Add a dependency on one or more packages |
vpm_depend_self() |
Add a dependency on the current package to include the package's own configure.cmake script, while ensuring it is only included once within the current CMakeLists.txt |
vpm_set_version(<package> <version>) |
Set the version to use for a package |
vpm_set_versions((<package> <version>)..(<package> <version>)) |
Set the versions to use for multiple packages |
vpm_set_variant(<package> <variant>) |
Set the variant to use for a package |
vpm_set_variants((<package> <variant>)..(<package> <variant>)) |
Set the variants to use for multiple packages |
vpm_set_default_version(<package> <version>) |
Set the version to use for a package if it has not already been set. Prefer this form when setting versions from within packages |
vpm_set_default_versions((<package> <version>)..(<package> <version>)) |
Call vpm_set_default_version for multiple packages |
vpm_set_default_variant(<package> <variant>) |
Set the variant to use for a package if it has not already been set. Prefer this form when setting variants from within packages |
vpm_set_default_variants((<package> <variant>)..(<package> <variant>)) |
Call vpm_set_default_variant for multiple packages |
vpm_get_version(<version_var> <package>) |
Get the configured version for a package. This does not mean that the package has been included |
vpm_get_variant(<variant_var> <package>) |
Get the configured variant for a package. This does not mean that the package has been included |
vpm_include_directories(<directory> ... <directory>) |
Equivalent of CMake include_directories() |
vpm_minimum_framework_version(<version>) |
Specificy the minimum version of the VPM framework that must be used. Will fail the build if the framework version is lower than <version> |
vpm_minimum_version(<package> <version>) |
Specificy the minimum version of a package that must be used. Will fail the build if the package version is lower than <version> |
vpm_add_library(<name> <file>..<file>) |
Equivalent of CMake add_library() |
vpm_add_executable(<name> <file>..<file>) |
Equivalent of CMake add_executable() |
vpm_add_link_dependencies(<target> <dependency>..<dependency>) |
Add libraries to link with <target> . Works transitively for library targets |
vpm_add_build_dependencies(<target> <dependency>..<dependency>) |
Ensure dependencies are built before <target> |
vpm_add_build_and_link_dependencies(<target> <dependency>..<dependency>) |
Ensure dependencies are built before <target> and add dependencies to link with <target> |
Name | Value |
---|---|
VPM_CURRENT_PACKAGE_IS_ROOT |
TRUE if the package who's CMakeLists.txt is currently being configured was part of the set of packages specified directly by the user. I.e., not brought in through a dependency. Otherwise FALSE |
VPM_CURRENT_PACKAGE |
The name of the package who's CMakeLists.txt is currently being configured. |
VPM_CURRENT_PACKAGE_DIR |
The path to the package who's CMakeLists.txt is currently being configured. |
VPM_FRAMEWORK_VERSION |
The version of this framework |