Grab is a CMake module that makes it easy to include and manage external libraries in your project. Each project pins the versions it builds against in its own library database.
Libraries are only the start: anything that lives in a git repository, such as data and the tools that process it, can be grabbed too. A project can then describe a whole versioned environment, and CMake how its parts work together (see Integrating components and Describing a pipeline).
Grab ships with a reference catalog of common libraries. Organizations adapt it in their own fork to list the libraries they approve.
Usage
Organization setup
Fork the Grab repository and update libs.db.csv to match your
organization’s needs. This is the reference catalog: projects start from it,
but pin their own versions.
Project setup
Copy Grab from your organization’s fork into the project and commit it:
mkdir your-project
cd your-project
git init
git clone --depth 1 --branch v0.1 https://code-hub.mooo.com/gpuoti/grab.git
rm -rf grab/.git
touch CMakeLists.txt
Keep in grab/libs.db.csv the libraries the project uses, at the versions the
release builds against, then include Grab in your CMake configuration:
cmake_minimum_required( VERSION 3.15 )
include( grab/grab.cmake )
project( hello CXX )
grab( spdlog )
grab( cxxopts )
grab( catch2 )
First use
Every project keeps its own library database next to grab.cmake, in the
grab/ directory, under version control with the project:
your-project/
├── CMakeLists.txt
└── grab/
├── grab.cmake
└── libs.db.csv
Each line of libs.db.csv describes one library as <name> | <tag> | <url>.
Blank lines and lines starting with # are ignored. Use master or your
branch name if a library has no tag yet. This is the database of the
examples/hello project:
spdlog | v1.17.0 | https://github.com/gabime/spdlog
cxxopts | v3.3.1 | https://github.com/jarro2783/cxxopts
catch2 | v3.15.0 | https://github.com/catchorg/Catch2
|
Note
|
The format of libs.db.csv will change. It is not stable in version
0.1, but we expect it to stay compatible or convertible.
|
Only the libraries listed there can be grabbed. If the database is missing,
CMake stops at the first grab() call and tells you where it looked for it:
CMake Error at grab/grab.cmake:37 (message):
Library database
/path/to/your-project/grab/libs.db.csv
does not exist.
The database belongs to your project: create it in that directory (its name
comes from GRAB_LIBS_DB, `libs` by default). Each line must have the
following structure:
<name> | <tag> | <url>
Create the file with the libraries you need and configure again. The same
happens if you select a database with GRAB_LIBS_DB (see
Switching library sets) and its file is not there yet.
Integrating components
A project has a single Grab and a single library database. They describe the
build environment, so they live in a meta component, which also holds the
top-level CMakeLists.txt. The components leave Grab out:
integration/
├── meta/
│ ├── CMakeLists.txt
│ └── grab/
│ ├── grab.cmake
│ └── libs.db.csv
└── component/
├── CMakeLists.txt
└── src/
cmake_minimum_required( VERSION 3.15 )
project( integration NONE )
include( grab/grab.cmake )
add_subdirectory( ../component component )
add_library( component INTERFACE )
target_include_directories( component INTERFACE src )
Configure from meta, with cmake -S meta. In a real project the component
comes from its own repository, through an entry in libs.db.csv.
A component developed on its own has a meta of its own, used while
developing it. When the component is integrated, the meta of the integrating
project takes its place and the component’s grab/ is not brought along.
Including Grab a second time is an error, because the project would have two databases:
CMake Error at component/grab/grab.cmake:8 (message):
Grab is already loaded from /path/to/integration/meta/grab, it cannot be
included again from /path/to/integration/component/grab.
The grab/ directory in /path/to/integration/component/grab describes the
development environment of a component, not the integrated one. Keep grab/
and its database only in the project that describes the build environment,
and let the components leave it out.
Describing a pipeline
Grab fetches whatever lives in a git repository, so an environment can bundle
more than libraries: versioned data, and the tools that process it. A component
that is not code still has a CMakeLists.txt: it only tells CMake where its
content is.
add_library( data INTERFACE )
set_target_properties( data PROPERTIES
DATA_DIR ${CMAKE_CURRENT_LIST_DIR}/files
DATA_FILES ${CMAKE_CURRENT_LIST_DIR}/files/input.txt )
add_library( tool INTERFACE )
set_target_properties( tool PROPERTIES
TOOL_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/tool.cmake )
meta grabs them at the versions of its database, and CMake describes the
pipeline: which command runs on which data. The urls below are placeholders:
to try the example, point them at your own repositories.
# The versions of the environment: what the pipeline runs on.
# The urls are placeholders: point them at your own repositories.
data | v1 | https://example.com/pipeline-data.git
tool | v1 | https://example.com/pipeline-tool.git
cmake_minimum_required( VERSION 3.15 )
project( pipeline NONE )
include( grab/grab.cmake )
grab( data )
grab( tool )
add_custom_command(
OUTPUT result.txt
COMMAND ${CMAKE_COMMAND}
-DIN=$<TARGET_PROPERTY:data,DATA_FILES>
-DOUT=${CMAKE_CURRENT_BINARY_DIR}/result.txt
-P $<TARGET_PROPERTY:tool,TOOL_SCRIPT>
DEPENDS $<TARGET_PROPERTY:data,DATA_FILES>
$<TARGET_PROPERTY:tool,TOOL_SCRIPT> )
add_custom_target( pipeline ALL DEPENDS result.txt )
Run it with cmake --build <build> --target pipeline. Listing the files in
DEPENDS makes CMake rerun the step whenever the data or the tool change, that
is, whenever their tag changes in the database.
Data and tools are expected to be in git repositories, even if that is not the best way to publish them: Grab does not download archives or binaries yet.
How it works
Grab uses CMake’s built-in FetchContent module to download and integrate
the requested libraries. Each dependency must support CMake-based builds.
Customization
Dependency location
By default, resolved dependencies are placed in the _deps directory inside
your build directory (managed by FetchContent). You can override this by
setting GRAB_BINARY_DIR before including Grab.
The following example places dependencies in the source tree instead:
cmake_minimum_required( VERSION 3.15 )
project( your-project )
set( GRAB_BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps )
include( grab/grab.cmake )
grab( plog )
grab( cxxopts )
|
Important
|
This is just a placeholder example. Placing dependencies in the source tree is generally not recommended. |
Switching library sets
Organizations working across projects from different eras may need to maintain
multiple library databases to handle compatibility requirements. Grab supports
this through the GRAB_LIBS_DB variable (default: libs).
For example, suppose fancy-lib has two flavors:
-
latest— defined inlibs.db.csv(the default) -
legacy— defined in a separatelegacy.db.csvfile
Projects that require the legacy flavor can opt in by setting GRAB_LIBS_DB
before including Grab:
cmake_minimum_required( VERSION 3.15 )
project( your-project )
set( GRAB_LIBS_DB legacy )
include( grab/grab.cmake )
grab( plog )
grab( cxxopts )
Both database files sit next to grab.cmake, in the grab/ directory. Only
the selected one is loaded.
License
This project is licensed under the MIT License. See the LICENSE file for details.