Compare commits

..

49 Commits

Author SHA1 Message Date
Lars Melchior
a575ab57b7 Allow overriding FetchContent using CPM (#352)
* Override FetchContent paramers, fixes #281

* add check to verify that dependency is cached

* update test comment

* rename test file and class

* update test indentation
2022-05-16 21:28:44 +02:00
Lava Block
ca421f0e0c Add liblava to "Built with CPM.cmake" (#346) 2022-04-10 18:59:05 +00:00
Robert Maynard
005f202655 Add per package CPM_DOWNLOAD controls (#336)
* Add per project CPM_DOWNLOAD controls

Introduces support for `CPM_<PACKAGE>_DOWNLOAD` variable ( and env )
which allows finer grained control.

* Address PR feedback on API names
2022-02-11 19:37:32 +01:00
Lars Bilke
865648e7bd Fixed typos in comments 2022-02-09 11:25:26 +00:00
Borislav Stanimirov
33bdbae902 List of contributors (#340)
* Updated copyright years. Same copyright holders in code and LICENSE

* Contributor list. Order is from contributors gh page
2022-02-07 20:21:43 +01:00
Tobias Hellmann
9d3fad01d4 Fix broken example for nlohmann/json (#338) 2022-01-27 22:59:28 +00:00
Robert Maynard
11c7ec8afa Use CMake 3.14+ documented way to pass the source dir -S. (#337)
The `-H<src_dir>` was never officially documented by CMake
2022-01-27 23:45:35 +01:00
Robert Maynard
4f7af69925 CMake 3.22 allows set/set(CACHE) to behave the same as set(X)/option(x) (#335)
By enabling CMP0126 we tell CMake that when constructing a CACHE variable
with the same name as a local variable to not unset the local variable.

By enabling CMPO126 it makes sure that CPM behavior around `OPTION` is
consistent if the option is created with `set(CACHE)` or with `option`.
2022-01-27 23:36:12 +01:00
Borislav Stanimirov
c58e98a0a0 Initial version of integration tests (#330)
* Initial commit for integration tests. Experimental. Playing with potential syntax

* Some experimental code to setup tests

* Piecewise building of CMakeLists

* First check

* Alternative approach. Using ruby's test/unit

* Parse CMakeCache. Separate lib

* First integration test

* Latest Format.cmake. Passing style

* Allow user-provided integration test dir. Allow reuse

* Separate class with utils for cache (no longer pure Hash)

* Allow running of tests from any dir

* Add integration tests to CI

* Use an in-source integration test directory

* Allow relative integration test dir from env

* Custom assertion for a success of CommandResult

* Windows-latest-latest

* Enrich CMakeCache class with more CPM data

* Added test for CPM-specific CMakeCache values

* Style

* Style

* test_update_single_package

* WiP for source cache test

* Small source_cache test

* Style

* Moved env clean to cleanup to make setup methods simpler (not require super)

* WiP for integration test documentation

* WiP for integration test documentation

* Project file creation tweaks

* Split docs into multiple files. Complete tutorial. Reference.

* Tips

* Typo

* Setup Ruby inistead of requiring windows-2022

* Revert "Setup Ruby inistead of requiring windows-2022"

This reverts commit 8aa2732145.
2022-01-19 00:18:41 +01:00
Lars Melchior
a27c66a3ea Remove badges (#327)
As we now use the unified CI workflow (#326), it seems we unfortunately [can't have separate badges for each OS](https://github.community/t/separate-workflow-badges-when-using-matrix-testing-possible/16708). As the display of the badges was to reference the cross-platform nature of the project, imo it can be replaced by adding a reference in the introduction instead.
2022-01-13 03:44:07 +02:00
Vinpasso
5cb7398a24 [Example] Disable JSON tests being included in ctest (#329)
* Disable JSON tests being included in ctest

If `CPMAddPackage` includes json without setting JSON_BuildTests off first then the target's ctest will include lots of json's tests.

* Bump example JSON version to avoid JSON tests
2022-01-12 20:29:59 +01:00
Borislav Stanimirov
262f1e0602 Unified GitHub workflow for tests on major operating systems (#326) 2022-01-11 21:16:04 +01:00
Dan Raviv
7cbef3efc8 Fix comment out-of-sync with modified code (#321) 2022-01-06 08:46:54 +02:00
DNKpp
939123d1b4 Fix <package>_SOURCE_DIR and _BINARY_DIR handling when caching is active (#314)
* fix: use CACHE variables instead relying on FetchContents mechanism

* apply cmake-format

Signed-off-by: DNKpp <DNKpp2011@gmail.com>
2021-12-30 16:20:37 +01:00
Lars Melchior
22078d5e0d add quotes around module arguments (#313) 2021-12-30 15:20:16 +02:00
Pavel Sokolov
96c268827b Pass DOWNLOAD_ONLY flag when CPM_pkg_SOURCE is set. (#308)
https://github.com/cpm-cmake/CPM.cmake/issues/307
2021-12-15 21:23:18 +01:00
Lars Melchior
dad37fbe4e Change example ordering and add note on boost (#305)
Reorders examples from simple to more complex. Also adds note on using `CPM_SOURCE_CACHE` for boost.
2021-12-12 15:09:50 +01:00
Lars Melchior
fed5f8e8a2 use official boost release (#304) 2021-12-05 16:08:47 +01:00
Lars Melchior
d65613e860 add warning when not using release version of CPM.cmake (#303) 2021-12-05 16:08:02 +01:00
Lars Melchior
718ea71759 Allow passing lists in the options (#302)
* allow list options by passing option parameter as string, not value

* add test

* remove debug message

* run formatter
2021-12-05 16:07:20 +01:00
Lars Melchior
6491382204 Remove travis badge from readme (#289)
Follow up on #283 to remove the now obsolete travis badge.
2021-09-29 13:48:40 +03:00
pgorgon-hem
634800c619 Added directory inside cache to make it shown more friendly in CLion (#268)
* Added directory inside cache to make it shown more friendly in CLion

* Changed hash generation to omit collision with old cache scheme. Removed moving old directory for compatibility reason.

* Added CPM_USE_NAMED_CACHE_DIRECTORIES option

* Fixed formatting

* Added description of CPM_USE_NAMED_CACHE_DIRECTORIES into README.md

Co-authored-by: Paweł Gorgoń <pgorgon@hem-e.com>
2021-09-15 21:39:31 +02:00
Andrea Barbadoro
f552da96bd Detecting when the cache is dirty (#284)
* Check that the cache working directory is clean

* cpm_check_working_dir_is_clean for non git repo will not terminate cmake

* sileneced check for git repo, in case of error

* style(CPM.cmake): fixed indentation for ERROR_QUIET

* refactor(CPM.cmake): added logic to handle a cache folder inside a (not correlated) git repo

this is accomplished by checking if we are in the directory where .git lives

* style(CPM.cmake): stray tab to spaces

* test(dirty-cache-check): added unit test to check cpm_check_working_dir_is_clean

the test creates a file in a folder and then uses git to create a repo in that folder, to check various conditions

* Update test/unit/dirty-cache-check.cmake

added user.name and user.email to comply with test machine where git is not completely configured

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>

* feat(cpm_check_working_dir_is_clean): now it takes a git tag (works with hashes too) to check for uncommitted and commited changes

* refactor(cpm_check_working_dir_is_clean): early return for the most common case of uncommited changes in a repo

* refactor(cpm_check_git_working_dir_is_clean): removed edgecase detection (a non-git folder in an unrelated git repo)

now the contract is that com_check_git_working_dir_is_clean is given the base folder of a git repo

Co-authored-by: Andrew Gribble <ag131012@renishaw.com>
Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>
2021-09-15 11:38:49 +02:00
Olivier Le Doeuff
ea6a8eb895 Fix CPMAddPackage when FetchContent_MakeAvailable already added package (#288)
Add fetchcontent_dependency unit test:
* This test should highlight the fact that cpm_add_subdirectory is always called, even when
  cpm_fetch_package isn't populating the dependency
* NO_CACHE YES highlight a bug introduced in 32b063eba5 where
  cpm_fetch_package was checking undefined ${lower_case_name}_POPULATED variable

https://github.com/cpm-cmake/CPM.cmake/issues/287
2021-08-29 22:02:18 +02:00
MixusMinimax
a5c22bf6e8 use quotes for CPM_FILE when creating FIND.cmake (#286) 2021-08-29 22:01:50 +02:00
Lars Melchior
c5cb85b2f1 Build examples in CI using GitHub (#283)
* build examples in CI using github

* update cxxopts style
2021-08-29 20:27:10 +02:00
pgorgon-hem
91585e3864 Added support for bitbucket repositories (#256)
* Added support for bitbucket repositories:
* added variable BITBUCKET_REPOSITORY
* added short syntax in form "bb:user/repo"
* added description of that to readme.md

* Added test for bitbucket short syntax

* Used elseif syntax in handling of git services (github, gitlab, bitbucket).

* Added HEMRND/TestingFramework example located on bitbucket.org

* Reformatted CMakeLists.txt in TestingFramework example

* Bumped version of TestingFramework. It supports older version of c++ standard.

Co-authored-by: Paweł Gorgoń <pgorgon@hem-e.com>
2021-06-08 19:15:40 +02:00
Stuart Dootson
9675d46517 Expand relative download directory to prevent unnecessary download (#267)
* Expand relative download directory to prevent unnecessary download

If `CMAKE_SOURCE_CACHE` (and thus `download_directory`) is set to a relative path, the condition `if(EXISTS ${download_directory})` will fail even if the required directory exists. Expanding the variable `download_directory` to an absolute path before the test will rectify this, preventing unnecessary downloads of the sources.

* iFixed up formatting

Co-authored-by: Stuart Dootson <stuart.dootson@rolls-royce.com>
2021-06-08 19:12:20 +02:00
Lars Melchior
7078e8286a Remove branch name references from README (#265)
As noted in #263, setting `GIT_TAG` to a branch name results in unexpected and unreproducible build behaviour, as it does not automatically update when the `HEAD` changes.
2021-06-08 15:55:03 +03:00
Lars Melchior
6a0277f16e Update CMake to 3.20 for integration tests (#264)
This allows us to use more recent projects in the examples
2021-06-08 15:27:31 +03:00
Lars Melchior
4502bf1e04 Forward exclude and subdir options when using local package override (#261)
* forward exclude and subdir options when using local package override.
fixes #260.

* run cmake format

* add SOURCE_SUBDIR parameter to cmake-format
2021-06-08 15:26:39 +03:00
Clare Macrae
dd3ba9792c Fix typo in README - omit -> emit (#258) 2021-06-02 09:36:29 +03:00
Lars Melchior
de5551e42c Update boost-cmake (#252)
* update boost-cmake

* run cmake-format
2021-05-18 16:29:52 +03:00
Paul Taylor
7644c3a40f Respect FETCHCONTENT_BASE_DIR if set by a user (#244)
* Respect `FETCHCONTENT_BASE_DIR` if set by a user

* Use a single `CPM_FETCHCONTENT_BASE_DIR` var
2021-04-16 17:27:51 +02:00
Lars Melchior
310efb9b17 Update more examples from the readme (#241)
* update examples from the readme

* add explainations to the examples
2021-03-26 18:31:35 +02:00
Lars Melchior
4fad2eac0a remove option consistency check (#240) 2021-03-25 19:24:44 +02:00
flagarde
ee08119642 Scope package options to avoid changing the local scope (#235)
* Fix #222

* Fix #222

* move policy change and local options to cpm_add_subdirectory

* change default policy as well

see https://gitlab.kitware.com/cmake/cmake/-/issues/20312

* add test

* update check to not use the NOT operator

(interestingly it works as expected locally)

* simplify test by not using options

* check options in tests

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>
2021-03-25 15:59:34 +01:00
Lars Melchior
259f1be8e2 update cxxopts and add reasonable options (#239) 2021-03-25 14:20:06 +02:00
Lars Melchior
32b063eba5 Fix support for source subdirectories (#238)
* add support for source subdirectories

* style fixes

* remove debug log

* grammar fix in comment
2021-03-25 10:42:51 +01:00
Lars Melchior
d64d816585 Make CPMFindPackage less prominent in the Readme (#236)
Giving `CPMFindPackage` a less prominent position, as in almost all cases, `CPMAddPackage` should be preferred.

Following a related discussion in #222.
2021-03-22 12:37:11 +01:00
Lars Melchior
8e8dcc9a8d moved test repostitory to CPM.cmake namespace (#228) 2021-03-04 11:01:01 +02:00
Lars Melchior
8afc2af4f9 update more examples to use shorthand syntax (#223)
* update more examples to use shorthand syntax

* revert google-benchmark as tests require googletest to be installed

* update comments

* always quote single-arguments

* undo accidental deletion
2021-02-25 09:53:28 +02:00
Borislav Stanimirov
a3d1048ad6 Infer package name and version from URL (#220)
* Function to extract name and version from url. Some tests

* Rewrite. Previous version was not safe enough. More tests

* Allow underscore as a name-version separator (<name>_<ver>)

* CPMAddPackage can infer name and version from url

* Allow URL parse from single arg and uncomment tests

* Info about shorthand syntax in README

* Fix style

* Fixed typo

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>

* Explicit hash algorithm in shorthand URL example.
Also added tests which include a hash algorithm provided
We can't document a default until it's confirmed here: https://gitlab.kitware.com/cmake/cmake/-/issues/21859

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>
2021-02-23 20:15:45 +01:00
Lars Melchior
492e762591 update examples to use new syntax where possible (#218) 2021-02-23 08:14:32 +02:00
Borislav Stanimirov
3f6cbe7383 Single-argument shorthand syntax for CPMAddPackage (#207)
* Added quotes in equality checks so lists can be compared

* Function to parse argument of CPMAddPackage in case a single one was provided

* Error on URL type in CPMAddPackage single-arg

* Fixed format

* Support single argument syntax of CPMAddPackage

* Documenting and showcasing the new shorthand syntax of CPMAddPackage

* Auto EXCLUDE_FROM_ALL for the shorthand syntax

* Fixed accidental paste of TOLOWER

* Document why some test cases are commented out

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>

* Update README.md

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>

* Removed GitHub as the default package shorthand provider

Co-authored-by: Lars Melchior <TheLartians@users.noreply.github.com>
2021-02-22 21:12:06 +01:00
Borislav Stanimirov
4aadac1972 Improved regex match in cpm_package_name_from_git_uri (#206) 2021-02-19 09:45:04 +01:00
Borislav Stanimirov
4cbf443363 If a name isn't provided, try to infer it from the git repo (#202)
* Added assert_not_defined check

* Function to get package name form git uri and tests

* Autofix format

* If name is not provided, try to infer it from the git repo

* Unset result of cpm_package_name_from_git_uri if there is no match
- Also reordered tests to ensure that the result is actually unset when needed

* Removed trailing spaces in README

* Updated the main example with the new minimal syntax

* Well... autofix format again

* Update error message for missing name to reflect the possible auto-infer step

* Autofix format... yet again :)
2021-02-17 12:41:25 +01:00
Borislav Stanimirov
2744b87f07 FATAL_ERROR if no NAME is provided (#201)
* FATAL_ERROR if no NAME is provided

* Fixed format
2021-02-16 14:54:31 +00:00
Lars Melchior
fd539b8ff3 Add dependencies using EXCLUDE_FROM_ALL flag (#198)
* add dependencies using EXCLUDE_FROM_ALL flag

* respect DOWNLOAD_ONLY flag

* set EXCLUDE_FROM_ALL as an optional flag

* use one value arg for consistency

* fix argument passing

* add unit test

* update cmake-format
2021-02-16 10:26:16 +00:00
75 changed files with 2085 additions and 420 deletions

View File

@@ -33,6 +33,8 @@ parse:
DOWNLOAD_NO_EXTRACT: 1
HTTP_USERNAME: 1
HTTP_PASSWORD: 1
EXCLUDE_FROM_ALL: 1
SOURCE_SUBDIR: 1
OPTIONS: +
cpmfindpackage:
pargs:

30
.github/workflows/examples.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Examples
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
gcc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: build all
env:
CC: gcc
CXX: g++
run: python3 examples/build_all.py
clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: build all
env:
CC: clang
CXX: clang++
run: python3 examples/build_all.py

View File

@@ -1,22 +0,0 @@
name: MacOS
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: run tests
run: |
cmake -Htest -Bbuild/test
cmake --build build/test --target test-verbose

View File

@@ -22,7 +22,7 @@ jobs:
pip3 install cmake_format==0.6.11 pyyaml
- name: configure
run: cmake -Htest/style -Bbuild/style
run: cmake -Stest/style -Bbuild/style
- name: check style
run: cmake --build build/style --target check-format

33
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Test
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
# windows-latest is windows-2019 which carries a pretty old version of ruby (2.5)
# we need at least ruby 2.7 for the tests
# instead of dealing with installing a modern version of ruby on 2019, we'll just use windows-2022 here
os: [ubuntu-latest, windows-2022, macos-latest]
steps:
- name: clone
uses: actions/checkout@v2
- name: unit tests
run: |
cmake -Stest -Bbuild/test
cmake --build build/test --target test-verbose
- name: integration tests
run: ruby test/integration/runner.rb
env:
CPM_INTEGRATION_TEST_DIR: ./build/integration

View File

@@ -1,29 +0,0 @@
name: Ubuntu
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: install updates
run: |
wget -O cmake.sh https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.sh
sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local
export PATH=/usr/local/bin:$PATH
cmake --version
- name: run tests
run: |
cmake -Htest -Bbuild/test
cmake --build build/test --target test-verbose

View File

@@ -1,22 +0,0 @@
name: Windows
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: run tests
run: |
cmake -Htest -Bbuild/test
cmake --build build/test --target test-verbose

View File

@@ -1,50 +0,0 @@
language: cpp
sudo: require
dist: xenial
common_sources: &all_sources
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty
python:
- 3.7
matrix:
include:
- os: linux
compiler: gcc
addons: &gcc8
apt:
sources: *all_sources
packages:
- g++-8
env:
- MATRIX_EVAL="export CC=gcc-8; export CXX=g++-8;"
- os: linux
compiler: clang
addons:
apt:
sources: *all_sources
packages:
- g++-8
- clang-6.0
env:
- MATRIX_EVAL="export CC=clang-6.0; export CXX=clang++-6.0;"
before_install:
# Update compilers
- eval "${MATRIX_EVAL}"
- echo "CC=$CC CXX=$CXX"
# Install a supported cmake version (>= 3.14)
- wget -O cmake.sh https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.sh
- sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local
- export PATH=/usr/local/bin:$PATH
- cmake --version
script:
# unit tests
- cmake -Htest -Bbuild/test
- cmake --build build/test --target test-verbose
# build examples
- python3 examples/build_all.py

32
CONTRIBUTORS.md Normal file
View File

@@ -0,0 +1,32 @@
# CPM.cmake Contributors
For detailed breakdown of individual contributions see the [Contributors page on GitHub](https://github.com/cpm-cmake/CPM.cmake/graphs/contributors)
* [Lars Melchior](https://github.com/TheLartians)
* [Borislav Stanimirov](https://github.com/iboB)
* [Paul T](https://github.com/DeveloperPaul123)
* [Leonardo Lima](https://github.com/leozz37)
* [pgorgon-hem](https://github.com/pgorgon-hem)
* [Xavier Muller](https://github.com/xmuller)
* NVIDIA CORPORATION via [Robert Maynard](https://github.com/robertmaynard)
* [Kingsley Chen](https://github.com/kingsamchen)
* [alexandreSalconiDenis](https://github.com/alexandreSalconiDenis)
* [DNKpp](https://github.com/DNKpp)
* [Olivier Le Doeuff](https://github.com/OlivierLDff)
* [Stuart Dootson](https://github.com/studoot)
* [Tobias Hellmann](https://github.com/Tobi823)
* [Giuseppe Cesarano](https://github.com/GiuseppeCesarano)
* [Pavel Sokolov](https://github.com/hacker-cb)
* [Claus Klein](https://github.com/ClausKlein)
* [Clare Macrae](https://github.com/claremacrae)
* [Kai Germaschewski](https://github.com/germasch)
* [Andrea Barbadoro](https://github.com/andijcr)
* [MixusMinimax](https://github.com/MixusMinimax)
* [Dan Raviv](https://github.com/danra)
* [Prabir Shrestha](https://github.com/prabirshrestha)
* [Paul Taylor](https://github.com/trxcllnt)
* [Ryan Mast](https://github.com/nightlark)
* [Vinpasso](https://github.com/Vinpasso)
* [Yotam Gingold](https://github.com/yig)
* [jecassis](https://github.com/jecassis)
* [Johel Ernesto Guerrero Peña](https://github.com/JohelEGP)

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Lars Melchior
Copyright (c) 2019-2022 Lars Melchior and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

190
README.md
View File

@@ -1,7 +1,3 @@
[![Build Status](https://travis-ci.com/cpm-cmake/CPM.cmake.svg?branch=master)](https://travis-ci.com/cpm-cmake/CPM.cmake)
[![Actions Status](https://github.com/cpm-cmake/CPM.cmake/workflows/MacOS/badge.svg)](https://github.com/cpm-cmake/CPM.cmake/actions)
[![Actions Status](https://github.com/cpm-cmake/CPM.cmake/workflows/Windows/badge.svg)](https://github.com/cpm-cmake/CPM.cmake/actions)
[![Actions Status](https://github.com/cpm-cmake/CPM.cmake/workflows/Ubuntu/badge.svg)](https://github.com/cpm-cmake/CPM.cmake/actions)
<br />
<p align="center">
@@ -11,7 +7,7 @@
# Setup-free CMake dependency management
CPM.cmake is a CMake script that adds dependency management capabilities to CMake.
CPM.cmake is a cross-platform CMake script that adds dependency management capabilities to CMake.
It's built as a thin wrapper around CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) module that adds version control, caching, a simple API [and more](#comparison-to-pure-fetchcontent--externalproject).
## Manage everything
@@ -27,9 +23,9 @@ For everything else, the targets can be created manually after the dependency ha
## Usage
After `CPM.cmake` has been [added](#adding-cpm) to your project, the function `CPMAddPackage` or `CPMFindPackage` can be used to fetch and configure a dependency.
After `CPM.cmake` has been [added](#adding-cpm) to your project, the function `CPMAddPackage` can be used to fetch and configure a dependency.
Afterwards, any targets defined in the dependency can be used directly.
`CPMFindPackage` and `CPMAddPackage` take the following named parameters.
`CPMAddPackage` takes the following named parameters.
```cmake
CPMAddPackage(
@@ -43,18 +39,44 @@ CPMAddPackage(
The origin may be specified by a `GIT_REPOSITORY`, but other sources, such as direct URLs, are [also supported](https://cmake.org/cmake/help/v3.11/module/ExternalProject.html#external-project-definition).
If `GIT_TAG` hasn't been explicitly specified it defaults to `v(VERSION)`, a common convention for git projects.
On the other hand, if `VERSION` hasn't been explicitly specified, CPM can automatically identify the version from the git tag in some common cases.
`GIT_TAG` can also be set to a specific commit or a branch name such as `master` to always download the most recent version.
The optional argument `FIND_PACKAGE_ARGUMENTS` can be specified to a string of parameters that will be passed to `find_package` if enabled (see below).
On the other hand, if `VERSION` hasn't been explicitly specified, CPM can automatically identify the version from the git tag in some common cases.
`GIT_TAG` can also be set to a specific commit or a branch name such as `master`, however this isn't recommended, as such packages will only be updated when the cache is cleared.
After calling `CPMAddPackage` or `CPMFindPackage`, the following variables are defined in the local scope, where `<dependency>` is the name of the dependency.
If an additional optional parameter `EXCLUDE_FROM_ALL` is set to a truthy value, then any targets defined inside the dependency won't be built by default. See the [CMake docs](https://cmake.org/cmake/help/latest/prop_tgt/EXCLUDE_FROM_ALL.html) for details.
A single-argument compact syntax is also supported:
```cmake
# A git package from a given uri with a version
CPMAddPackage("uri@version")
# A git package from a given uri with a git tag or commit hash
CPMAddPackage("uri#tag")
# A git package with both version and tag provided
CPMAddPackage("uri@version#tag")
```
In the shorthand syntax if the URI is of the form `gh:user/name`, it is interpreted as GitHub URI and converted to `https://github.com/user/name.git`. If the URI is of the form `gl:user/name`, it is interpreted as a [GitLab](https://gitlab.com/explore/) URI and converted to `https://gitlab.com/user/name.git`. If the URI is of the form `bb:user/name`, it is interpreted as a [Bitbucket](https://bitbucket.org/) URI and converted to `https://bitbucket.org/user/name.git`. Otherwise the URI used verbatim as a git URL. All packages added using the shorthand syntax will be added using the [EXCLUDE_FROM_ALL](https://cmake.org/cmake/help/latest/prop_tgt/EXCLUDE_FROM_ALL.html) flag.
The single-argument syntax also works for URLs:
```cmake
# An archive package from a given url. The version is inferred
CPMAddPackage("https://example.com/my-package-1.2.3.zip")
# An archive package from a given url with an MD5 hash provided
CPMAddPackage("https://example.com/my-package-1.2.3.zip#MD5=68e20f674a48be38d60e129f600faf7d")
# An archive package from a given url. The version is explicitly given
CPMAddPackage("https://example.com/my-package.zip@1.2.3")
```
After calling `CPMAddPackage`, the following variables are defined in the local scope, where `<dependency>` is the name of the dependency.
- `<dependency>_SOURCE_DIR` is the path to the source of the dependency.
- `<dependency>_BINARY_DIR` is the path to the build directory of the dependency.
- `<dependency>_ADDED` is set to `YES` if the dependency has not been added before, otherwise it is set to `NO`.
The difference between `CPMFindPackage` and `CPMAddPackage` is that `CPMFindPackage` will try to find a local dependency via CMake's `find_package` and fallback to `CPMAddPackage` if the dependency is not found.
This behaviour can be also modified globally via [CPM options](#options).
For using CPM.cmake projects with external package managers, such as conan or vcpkg, setting the variable [`CPM_USE_LOCAL_PACKAGES`](#options) will make CPM.cmake try to add a package through `find_package` first, and add it from source if it doesn't succeed.
In rare cases, this behaviour may be desirable by default. The function `CPMFindPackage` will try to find a local dependency via CMake's `find_package` and fallback to `CPMAddPackage`, if the dependency is not found.
## Full CMakeLists Example
@@ -69,12 +91,7 @@ add_executable(tests tests.cpp)
# add dependencies
include(cmake/CPM.cmake)
CPMAddPackage(
NAME Catch2
GITHUB_REPOSITORY catchorg/Catch2
VERSION 2.5.0
)
CPMAddPackage("gh:catchorg/Catch2@2.5.0")
# link dependencies
target_link_libraries(tests Catch2)
@@ -112,7 +129,7 @@ Dependencies using CPM will automatically use the updated script of the outermos
## Limitations
- **No pre-built binaries** For every new build directory, all dependencies are initially downloaded and built from scratch. To avoid extra downloads it is recommend to set the [`CPM_SOURCE_CACHE`](#CPM_SOURCE_CACHE) environmental variable. Using a caching compiler such as [ccache](https://github.com/TheLartians/Ccache.cmake) can drastically reduce build time.
- **Dependent on good CMakeLists** Many libraries do not have CMakeLists that work well for subprojects. Luckily this is slowly changing, however, until then, some manual configuration may be required (see the snippets [below](#snippets) for examples). For best practices on preparing projects for CPM, see the [wiki](https://github.com/cpm-cmake/CPM.cmake/wiki/Preparing-projects-for-CPM.cmake).
- **Dependent on good CMakeLists** Many libraries do not have CMakeLists that work well for subprojects. Luckily this is slowly changing, however, until then, some manual configuration may be required (see the snippets [below](#snippets) for examples). For best practices on preparing projects for CPM, see the [wiki](https://github.com/cpm-cmake/CPM.cmake/wiki/Preparing-projects-for-CPM.cmake).
- **First version used** In diamond-shaped dependency graphs (e.g. `A` depends on `C`@1.1 and `B`, which itself depends on `C`@1.2 the first added dependency will be used (in this case `C`@1.1). In this case, B requires a newer version of `C` than `A`, so CPM will emit a warning. This can be easily resolved by adding a new version of the dependency in the outermost project, or by introducing a [package lock file](#package-lock).
For projects with more complex needs and where an extra setup step doesn't matter, it may be worth to check out an external C++ package manager such as [vcpkg](https://github.com/microsoft/vcpkg), [conan](https://conan.io) or [hunter](https://github.com/ruslo/hunter).
@@ -135,15 +152,14 @@ CPM.cmake is a wrapper for CMake's FetchContent module and adds a number of feat
The most notable features are:
- A simpler to use API
- Version checking: CPM.cmake will check the version number of any added dependency and omit a warning if another dependency requires a more recent version.
- Options: any Options passed to a dependency are stored and compared on later use, so if another dependency tries to add an existing dependency with incompatible options a warning will be emitted to the user.
- Version checking: CPM.cmake will check the version number of any added dependency and emit a warning if another dependency requires a more recent version.
- Offline builds: CPM.cmake will override CMake's download and update commands, which allows new builds to be configured while offline if all dependencies [are available locally](#cpm_source_cache).
- Automatic shallow clone: if a version tag (e.g. `v2.2.0`) is provided and `CPM_SOURCE_CACHE` is used, CPM.cmake will perform a shallow clone of the dependency, which should be significantly faster while using less storage than a full clone.
- Overridable: all `CPMAddPackage` can be configured to use `find_package` by setting a [CMake flag](#cpm_use_local_packages), making it easy to integrate into projects that may require local versioning through the system's package manager.
- [Package lock files](#package-lock) for easier transitive dependency management.
- Dependencies can be overridden [per-build](#local-package-override) using CMake CLI parameters.
ExternalProject works similarly as FetchContent, however waits with adding dependencies until build time.
ExternalProject works similarly as FetchContent, however waits with adding dependencies until build time.
This has a quite a few disadvantages, especially as it makes using custom toolchains / cross-compiling very difficult and can lead to problems with nested dependencies.
## Options
@@ -167,6 +183,7 @@ You can use `CPM_SOURCE_CACHE` on GitHub Actions workflows [cache](https://githu
If set, CPM will forward all calls to `CPMFindPackage` as `CPMAddPackage`.
This is useful to create reproducible builds or to determine if the source parameters have all been set correctly.
This can also be set as an environmental variable.
This can be controlled on a per package basis with the `CPM_DOWNLOAD_<dependency name>` variable.
### CPM_USE_LOCAL_PACKAGES
@@ -174,11 +191,18 @@ CPM can be configured to use `find_package` to search for locally installed depe
If the option `CPM_LOCAL_PACKAGES_ONLY` is set, CPM will emit an error if the dependency is not found locally.
These options can also be set as environmental variables.
In the case that `find_package` requires additional arguments, the parameter `FIND_PACKAGE_ARGUMENTS` may be specified in the `CPMAddPackage` call. The value of this parameter will be forwarded to `find_package`.
### CPM_USE_NAMED_CACHE_DIRECTORIES
If set, CPM use additional directory level in cache to improve readability of packages names in IDEs like CLion. It changes cache structure, so all dependencies are downloaded again. There is no problem to mix both structures in one cache directory but then there may be 2 copies of some dependencies.
This can also be set as an environmental variable.
## Local package override
Library developers are often in the situation where they work on a locally checked out dependency at the same time as on a consumer project.
It is possible to override the consumer's dependency with the version by supplying the CMake option `CPM_<dependency name>_SOURCE` set to the absolute path of the local library.
For example, to use the local version of the dependency `Dep` at the path `/path/to/dep`, the consumer can be built with the following command.
For example, to use the local version of the dependency `Dep` at the path `/path/to/dep`, the consumer can be built with the following command.
```bash
cmake -Bbuild -DCPM_Dep_SOURCE=/path/to/dep
@@ -205,7 +229,7 @@ See the [wiki](https://github.com/cpm-cmake/CPM.cmake/wiki/Package-lock) for mor
## Built with CPM.cmake
Some amazing projects that are built using the CPM.cmake package manager.
Some amazing projects that are built using the CPM.cmake package manager.
If you know others, feel free to add them here through a PR.
<table>
@@ -234,6 +258,14 @@ If you know others, feel free to add them here through a PR.
<p align="center"><b>ModernCppStarter</b></p>
</a>
</td>
<td>
<a href="https://git.io/liblava">
<p align="center">
<img src="https://github.com/liblava.png" alt="liblava" width="100pt" />
</p>
<p align="center"><b>liblava - Modern Vulkan library</b></p>
</a>
</td>
</tr>
</table>
@@ -244,51 +276,56 @@ See the [wiki](https://github.com/cpm-cmake/CPM.cmake/wiki/More-Snippets) for mo
### [Catch2](https://github.com/catchorg/Catch2)
```cmake
CPMAddPackage("gh:catchorg/Catch2@2.5.0")
```
### [Range-v3](https://github.com/ericniebler/range-v3)
```Cmake
CPMAddPackage("gh:ericniebler/range-v3#0.11.0")
```
### [Yaml-cpp](https://github.com/jbeder/yaml-cpp)
```CMake
# as the tag is in an unusual format, we need to explicitly specify the version
CPMAddPackage("gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3")
```
### [nlohmann/json](https://github.com/nlohmann/json)
```cmake
CPMAddPackage(
NAME Catch2
GITHUB_REPOSITORY catchorg/Catch2
VERSION 2.5.0
NAME nlohmann_json
VERSION 3.9.1
GITHUB_REPOSITORY nlohmann/json
OPTIONS
"JSON_BuildTests OFF"
)
```
### [Boost (via boost-cmake)](https://github.com/Orphis/boost-cmake)
### [Boost ](https://github.com/boostorg/boost)
```CMake
# boost is a huge project and will take a while to download
# using `CPM_SOURCE_CACHE` is strongly recommended
CPMAddPackage(
NAME boost-cmake
GITHUB_REPOSITORY Orphis/boost-cmake
VERSION 1.67.0
NAME Boost
VERSION 1.77.0
GITHUB_REPOSITORY "boostorg/boost"
GIT_TAG "boost-1.77.0"
)
```
### [cxxopts](https://github.com/jarro2783/cxxopts)
```cmake
# the install option has to be explicitly set to allow installation
CPMAddPackage(
NAME cxxopts
GITHUB_REPOSITORY jarro2783/cxxopts
VERSION 2.2.0
OPTIONS
"CXXOPTS_BUILD_EXAMPLES Off"
"CXXOPTS_BUILD_TESTS Off"
)
```
### [Yaml-cpp](https://github.com/jbeder/yaml-cpp)
```CMake
CPMAddPackage(
NAME yaml-cpp
GITHUB_REPOSITORY jbeder/yaml-cpp
# 0.6.2 uses deprecated CMake syntax
VERSION 0.6.3
# 0.6.3 is not released yet, so use a recent commit
GIT_TAG 012269756149ae99745b6dafefd415843d7420bb
OPTIONS
"YAML_CPP_BUILD_TESTS Off"
"YAML_CPP_BUILD_CONTRIB Off"
"YAML_CPP_BUILD_TOOLS Off"
VERSION 2.2.1
OPTIONS "CXXOPTS_BUILD_EXAMPLES NO" "CXXOPTS_BUILD_TESTS NO" "CXXOPTS_ENABLE_INSTALL YES"
)
```
@@ -298,48 +335,13 @@ CPMAddPackage(
CPMAddPackage(
NAME benchmark
GITHUB_REPOSITORY google/benchmark
VERSION 1.4.1
OPTIONS
"BENCHMARK_ENABLE_TESTING Off"
VERSION 1.5.2
OPTIONS "BENCHMARK_ENABLE_TESTING Off"
)
if (benchmark_ADDED)
# compile with C++17
set_target_properties(benchmark PROPERTIES CXX_STANDARD 17)
endif()
```
### [nlohmann/json](https://github.com/nlohmann/json)
```cmake
CPMAddPackage(
NAME nlohmann_json
VERSION 3.6.1
# the git repo is incredibly large, so we download the archived include directory
URL https://github.com/nlohmann/json/releases/download/v3.6.1/include.zip
URL_HASH SHA256=69cc88207ce91347ea530b227ff0776db82dcb8de6704e1a3d74f4841bc651cf
)
if (nlohmann_json_ADDED)
add_library(nlohmann_json INTERFACE IMPORTED)
target_include_directories(nlohmann_json INTERFACE ${nlohmann_json_SOURCE_DIR})
endif()
```
### [Range-v3](https://github.com/ericniebler/range-v3)
```Cmake
CPMAddPackage(
NAME range-v3
URL https://github.com/ericniebler/range-v3/archive/0.5.0.zip
VERSION 0.5.0
# the range-v3 CMakeLists screws with configuration options
DOWNLOAD_ONLY True
)
if(range-v3_ADDED)
add_library(range-v3 INTERFACE IMPORTED)
target_include_directories(range-v3 INTERFACE "${range-v3_SOURCE_DIR}/include")
if(benchmark_ADDED)
# enable c++11 to avoid compilation errors
set_target_properties(benchmark PROPERTIES CXX_STANDARD 11)
endif()
```

View File

@@ -5,7 +5,7 @@
# MIT License
# -----------
#[[
Copyright (c) 2021 Lars Melchior and additional contributors
Copyright (c) 2019-2022 Lars Melchior and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -57,6 +57,13 @@ See https://github.com/cpm-cmake/CPM.cmake for more information."
endif()
endif()
if(CURRENT_CPM_VERSION MATCHES "development-version")
message(WARNING "Your project is using an unstable development version of CPM.cmake. \
Please update to a recent release if possible. \
See https://github.com/cpm-cmake/CPM.cmake for details."
)
endif()
set_property(GLOBAL PROPERTY CPM_INITIALIZED true)
option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies"
@@ -76,6 +83,10 @@ option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK
"Add all packages added through CPM.cmake to the package lock"
$ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK}
)
option(CPM_USE_NAMED_CACHE_DIRECTORIES
"Use additional directory of package name in cache on the most nested level."
$ENV{CPM_USE_NAMED_CACHE_DIRECTORIES}
)
set(CPM_VERSION
${CURRENT_CPM_VERSION}
@@ -132,7 +143,64 @@ if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
endif()
include(FetchContent)
include(CMakeParseArguments)
# Try to infer package name from git repository uri (path or url)
function(cpm_package_name_from_git_uri URI RESULT)
if("${URI}" MATCHES "([^/:]+)/?.git/?$")
set(${RESULT}
${CMAKE_MATCH_1}
PARENT_SCOPE
)
else()
unset(${RESULT} PARENT_SCOPE)
endif()
endfunction()
# Try to infer package name and version from a url
function(cpm_package_name_and_ver_from_url url outName outVer)
if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)")
# We matched an archive
set(filename "${CMAKE_MATCH_1}")
if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)")
# We matched <name>-<version> (ie foo-1.2.3)
set(${outName}
"${CMAKE_MATCH_1}"
PARENT_SCOPE
)
set(${outVer}
"${CMAKE_MATCH_2}"
PARENT_SCOPE
)
elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)")
# We couldn't find a name, but we found a version
#
# In many cases (which we don't handle here) the url would look something like
# `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly
# distinguish the package name from the irrelevant bits. Moreover if we try to match the
# package name from the filename, we'd get bogus at best.
unset(${outName} PARENT_SCOPE)
set(${outVer}
"${CMAKE_MATCH_1}"
PARENT_SCOPE
)
else()
# Boldly assume that the file name is the package name.
#
# Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but
# such cases should be quite rare. No popular service does this... we think.
set(${outName}
"${filename}"
PARENT_SCOPE
)
unset(${outVer} PARENT_SCOPE)
endif()
else()
# No ideas yet what to do with non-archives
unset(${outName} PARENT_SCOPE)
unset(${outVer} PARENT_SCOPE)
endif()
endfunction()
# Initialize logging prefix
if(NOT CPM_INDENT)
@@ -166,7 +234,7 @@ function(cpm_create_module_file Name)
if(NOT CPM_DONT_UPDATE_MODULE_PATH)
# erase any previous modules
file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake
"include(${CPM_FILE})\n${ARGN}\nset(${Name}_FOUND TRUE)"
"include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)"
)
endif()
endfunction()
@@ -183,13 +251,19 @@ function(CPMFindPackage)
endif()
endif()
if(CPM_DOWNLOAD_ALL)
set(downloadPackage ${CPM_DOWNLOAD_ALL})
if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME})
set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}})
elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}})
set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}})
endif()
if(downloadPackage)
CPMAddPackage(${ARGN})
cpm_export_variables(${CPM_ARGS_NAME})
return()
endif()
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}")
if(CPM_PACKAGE_ALREADY_ADDED)
cpm_export_variables(${CPM_ARGS_NAME})
return()
@@ -205,7 +279,7 @@ function(CPMFindPackage)
endfunction()
# checks if a package has been added before
function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION CPM_ARGS_OPTIONS)
function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION)
if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES)
CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION)
if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}")
@@ -214,17 +288,6 @@ function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION CPM_A
"${CPM_INDENT} requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})."
)
endif()
if(CPM_ARGS_OPTIONS)
foreach(OPTION ${CPM_ARGS_OPTIONS})
cpm_parse_option(${OPTION})
if(NOT "${${OPTION_KEY}}" STREQUAL "${OPTION_VALUE}")
message(
WARNING
"${CPM_INDENT} ignoring package option for ${CPM_ARGS_NAME}: ${OPTION_KEY} = ${OPTION_VALUE} (${${OPTION_KEY}})"
)
endif()
endforeach()
endif()
cpm_get_fetch_properties(${CPM_ARGS_NAME})
set(${CPM_ARGS_NAME}_ADDED NO)
set(CPM_PACKAGE_ALREADY_ADDED
@@ -240,10 +303,186 @@ function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION CPM_A
endif()
endfunction()
# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of
# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted
# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3
function(cpm_parse_add_package_single_arg arg outArgs)
# Look for a scheme
if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$")
string(TOLOWER "${CMAKE_MATCH_1}" scheme)
set(uri "${CMAKE_MATCH_2}")
# Check for CPM-specific schemes
if(scheme STREQUAL "gh")
set(out "GITHUB_REPOSITORY;${uri}")
set(packageType "git")
elseif(scheme STREQUAL "gl")
set(out "GITLAB_REPOSITORY;${uri}")
set(packageType "git")
elseif(scheme STREQUAL "bb")
set(out "BITBUCKET_REPOSITORY;${uri}")
set(packageType "git")
# A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine
# type
elseif(arg MATCHES ".git/?(@|#|$)")
set(out "GIT_REPOSITORY;${arg}")
set(packageType "git")
else()
# Fall back to a URL
set(out "URL;${arg}")
set(packageType "archive")
# We could also check for SVN since FetchContent supports it, but SVN is so rare these days.
# We just won't bother with the additional complexity it will induce in this function. SVN is
# done by multi-arg
endif()
else()
if(arg MATCHES ".git/?(@|#|$)")
set(out "GIT_REPOSITORY;${arg}")
set(packageType "git")
else()
# Give up
message(FATAL_ERROR "CPM: Can't determine package type of '${arg}'")
endif()
endif()
# For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs
# containing '@' can be used
string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}")
# Parse the rest according to package type
if(packageType STREQUAL "git")
# For git repos we interpret #... as a tag or branch or commit hash
string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}")
elseif(packageType STREQUAL "archive")
# For archives we interpret #... as a URL hash.
string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}")
# We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url
# should do this at a later point
else()
# We should never get here. This is an assertion and hitting it means there's a bug in the code
# above. A packageType was set, but not handled by this if-else.
message(FATAL_ERROR "CPM: Unsupported package type '${packageType}' of '${arg}'")
endif()
set(${outArgs}
${out}
PARENT_SCOPE
)
endfunction()
# Check that the working directory for a git repo is clean
function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean)
find_package(Git REQUIRED)
if(NOT GIT_EXECUTABLE)
# No git executable, assume directory is clean
set(${isClean}
TRUE
PARENT_SCOPE
)
return()
endif()
# check for uncommitted changes
execute_process(
COMMAND ${GIT_EXECUTABLE} status --porcelain
RESULT_VARIABLE resultGitStatus
OUTPUT_VARIABLE repoStatus
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET
WORKING_DIRECTORY ${repoPath}
)
if(resultGitStatus)
# not supposed to happen, assume clean anyway
message(WARNING "Calling git status on folder ${repoPath} failed")
set(${isClean}
TRUE
PARENT_SCOPE
)
return()
endif()
if(NOT "${repoStatus}" STREQUAL "")
set(${isClean}
FALSE
PARENT_SCOPE
)
return()
endif()
# check for committed changes
execute_process(
COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag}
RESULT_VARIABLE resultGitDiff
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET
WORKING_DIRECTORY ${repoPath}
)
if(${resultGitDiff} EQUAL 0)
set(${isClean}
TRUE
PARENT_SCOPE
)
else()
set(${isClean}
FALSE
PARENT_SCOPE
)
endif()
endfunction()
# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload
# FetchContent calls. As these are internal cmake properties, this method should be used carefully
# and may need modification in future CMake versions. Source:
# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152
function(cpm_override_fetchcontent contentName)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "")
if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
message(FATAL_ERROR "Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}")
endif()
string(TOLOWER ${contentName} contentNameLower)
set(prefix "_FetchContent_${contentNameLower}")
set(propertyName "${prefix}_sourceDir")
define_property(
GLOBAL
PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}")
set(propertyName "${prefix}_binaryDir")
define_property(
GLOBAL
PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}")
set(propertyName "${prefix}_populated")
define_property(
GLOBAL
PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} TRUE)
endfunction()
# Download and add a package from source
function(CPMAddPackage)
list(LENGTH ARGN argnLength)
if(argnLength EQUAL 1)
cpm_parse_add_package_single_arg("${ARGN}" ARGN)
set(flagArgs EXCLUDE_FROM_ALL)
# The shorthand syntax implies EXCLUDE_FROM_ALL
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES")
endif()
set(oneValueArgs
NAME
@@ -253,17 +492,20 @@ function(CPMAddPackage)
DOWNLOAD_ONLY
GITHUB_REPOSITORY
GITLAB_REPOSITORY
BITBUCKET_REPOSITORY
GIT_REPOSITORY
SOURCE_DIR
DOWNLOAD_COMMAND
FIND_PACKAGE_ARGUMENTS
NO_CACHE
GIT_SHALLOW
EXCLUDE_FROM_ALL
SOURCE_SUBDIR
)
set(multiValueArgs OPTIONS)
set(multiValueArgs URL OPTIONS)
cmake_parse_arguments(CPM_ARGS "${flagArgs}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
# Set default values for arguments
@@ -281,10 +523,10 @@ function(CPMAddPackage)
if(DEFINED CPM_ARGS_GITHUB_REPOSITORY)
set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git")
endif()
if(DEFINED CPM_ARGS_GITLAB_REPOSITORY)
elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY)
set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git")
elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY)
set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git")
endif()
if(DEFINED CPM_ARGS_GIT_REPOSITORY)
@@ -292,6 +534,11 @@ function(CPMAddPackage)
if(NOT DEFINED CPM_ARGS_GIT_TAG)
set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION})
endif()
# If a name wasn't provided, try to infer it from the git repo
if(NOT DEFINED CPM_ARGS_NAME)
cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME)
endif()
endif()
set(CPM_SKIP_FETCH FALSE)
@@ -304,8 +551,35 @@ function(CPMAddPackage)
endif()
endif()
if(DEFINED CPM_ARGS_URL)
# If a name or version aren't provided, try to infer them from the URL
list(GET CPM_ARGS_URL 0 firstUrl)
cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl)
# If we fail to obtain name and version from the first URL, we could try other URLs if any.
# However multiple URLs are expected to be quite rare, so for now we won't bother.
# If the caller provided their own name and version, they trump the inferred ones.
if(NOT DEFINED CPM_ARGS_NAME)
set(CPM_ARGS_NAME ${nameFromUrl})
endif()
if(NOT DEFINED CPM_ARGS_VERSION)
set(CPM_ARGS_VERSION ${verFromUrl})
endif()
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}")
endif()
# Check for required arguments
if(NOT DEFINED CPM_ARGS_NAME)
message(
FATAL_ERROR
"CPM: 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'"
)
endif()
# Check if package has been added before
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}")
if(CPM_PACKAGE_ALREADY_ADDED)
cpm_export_variables(${CPM_ARGS_NAME})
return()
@@ -316,10 +590,13 @@ function(CPMAddPackage)
set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE})
set(CPM_${CPM_ARGS_NAME}_SOURCE "")
CPMAddPackage(
NAME ${CPM_ARGS_NAME}
SOURCE_DIR ${PACKAGE_SOURCE}
NAME "${CPM_ARGS_NAME}"
SOURCE_DIR "${PACKAGE_SOURCE}"
EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}"
OPTIONS "${CPM_ARGS_OPTIONS}"
SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}"
DOWNLOAD_ONLY "${DOWNLOAD_ONLY}"
FORCE True
OPTIONS ${CPM_ARGS_OPTIONS}
)
cpm_export_variables(${CPM_ARGS_NAME})
return()
@@ -332,7 +609,7 @@ function(CPMAddPackage)
CPMAddPackage(${declaration})
cpm_export_variables(${CPM_ARGS_NAME})
# checking again to ensure version and option compatibility
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}")
return()
endif()
@@ -354,16 +631,6 @@ function(CPMAddPackage)
CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}")
if(CPM_ARGS_OPTIONS)
foreach(OPTION ${CPM_ARGS_OPTIONS})
cpm_parse_option(${OPTION})
set(${OPTION_KEY}
${OPTION_VALUE}
CACHE INTERNAL ""
)
endforeach()
endif()
if(DEFINED CPM_ARGS_GIT_TAG)
set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}")
elseif(DEFINED CPM_ARGS_SOURCE_DIR)
@@ -372,6 +639,13 @@ function(CPMAddPackage)
set(PACKAGE_INFO "${CPM_ARGS_VERSION}")
endif()
if(DEFINED FETCHCONTENT_BASE_DIR)
# respect user's FETCHCONTENT_BASE_DIR if set
set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR})
else()
set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps)
endif()
if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND})
elseif(DEFINED CPM_ARGS_SOURCE_DIR)
@@ -380,21 +654,46 @@ function(CPMAddPackage)
string(TOLOWER ${CPM_ARGS_NAME} lower_case_name)
set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS})
list(SORT origin_parameters)
string(SHA1 origin_hash "${origin_parameters}")
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
if(CPM_USE_NAMED_CACHE_DIRECTORIES)
string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG")
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME})
else()
string(SHA1 origin_hash "${origin_parameters}")
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
endif()
# Expand `download_directory` relative path. This is important because EXISTS doesn't work for
# relative paths.
get_filename_component(download_directory ${download_directory} ABSOLUTE)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory})
if(EXISTS ${download_directory})
# avoid FetchContent modules to improve performance
set(${CPM_ARGS_NAME}_BINARY_DIR ${CMAKE_BINARY_DIR}/_deps/${lower_case_name}-build)
set(${CPM_ARGS_NAME}_ADDED YES)
set(${CPM_ARGS_NAME}_SOURCE_DIR ${download_directory})
if(NOT CPM_ARGS_DOWNLOAD_ONLY AND EXISTS ${download_directory}/CMakeLists.txt)
cpm_add_subdirectory(
${download_directory} ${${CPM_ARGS_NAME}_BINARY_DIR} "${CPM_ARGS_EXCLUDE_FROM_ALL}"
)
cpm_store_fetch_properties(
${CPM_ARGS_NAME} "${download_directory}"
"${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build"
)
cpm_get_fetch_properties("${CPM_ARGS_NAME}")
if(DEFINED CPM_ARGS_GIT_TAG)
# warn if cache has been changed since checkout
cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN)
if(NOT ${IS_CLEAN})
message(WARNING "Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty")
endif()
endif()
set(CPM_SKIP_FETCH TRUE)
cpm_add_subdirectory(
"${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}"
"${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}"
"${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}"
)
set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}")
# As the source dir is already cached/populated, we override the call to FetchContent.
set(CPM_SKIP_FETCH TRUE)
cpm_override_fetchcontent(
"${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}"
BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}"
)
else()
# Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but
# it should guarantee no commit hash get mis-detected.
@@ -406,12 +705,12 @@ function(CPMAddPackage)
endif()
# remove timestamps so CMake will re-download the dependency
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/_deps/${lower_case_name}-subbuild)
file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild)
set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}")
endif()
endif()
cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(${ARGN})")
cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")")
if(CPM_PACKAGE_LOCK_ENABLED)
if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK)
@@ -431,7 +730,14 @@ function(CPMAddPackage)
cpm_declare_fetch(
"${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}"
)
cpm_fetch_package("${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" "${CPM_ARGS_EXCLUDE_FROM_ALL}")
cpm_fetch_package("${CPM_ARGS_NAME}" populated)
if(${populated})
cpm_add_subdirectory(
"${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}"
"${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}"
"${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}"
)
endif()
cpm_get_fetch_properties("${CPM_ARGS_NAME}")
endif()
@@ -465,7 +771,7 @@ macro(cpm_export_variables name)
endmacro()
# declares a package, so that any call to CPMAddPackage for the package name will use these
# arguments instead. Previous declarations will not be overriden.
# arguments instead. Previous declarations will not be overridden.
macro(CPMDeclarePackage Name)
if(NOT DEFINED "CPM_DECLARATION_${Name}")
set("CPM_DECLARATION_${Name}" "${ARGN}")
@@ -488,7 +794,7 @@ function(cpm_add_comment_to_package_lock Name)
endif()
endfunction()
# includes the package lock file if it exists and creates a target `cpm-write-package-lock` to
# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to
# update it
macro(CPMUsePackageLock file)
if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
@@ -542,61 +848,120 @@ function(cpm_get_fetch_properties PACKAGE)
if(${CPM_DRY_RUN})
return()
endif()
FetchContent_GetProperties(${PACKAGE})
string(TOLOWER ${PACKAGE} lpackage)
set(${PACKAGE}_SOURCE_DIR
"${${lpackage}_SOURCE_DIR}"
"${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}"
PARENT_SCOPE
)
set(${PACKAGE}_BINARY_DIR
"${${lpackage}_BINARY_DIR}"
"${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}"
PARENT_SCOPE
)
endfunction()
function(cpm_add_subdirectory SOURCE_DIR BINARY_DIR EXCLUDE)
if(EXCLUDE)
set(addSubdirectoryExtraArgs EXCLUDE_FROM_ALL)
else()
set(addSubdirectoryExtraArgs "")
function(cpm_store_fetch_properties PACKAGE source_dir binary_dir)
if(${CPM_DRY_RUN})
return()
endif()
add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs})
set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR
"${source_dir}"
CACHE INTERNAL ""
)
set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR
"${binary_dir}"
CACHE INTERNAL ""
)
endfunction()
# downloads a previously declared package via FetchContent
function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY EXCLUDE)
# adds a package as a subdirectory if viable, according to provided options
function(
cpm_add_subdirectory
PACKAGE
DOWNLOAD_ONLY
SOURCE_DIR
BINARY_DIR
EXCLUDE
OPTIONS
)
if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt)
if(EXCLUDE)
set(addSubdirectoryExtraArgs EXCLUDE_FROM_ALL)
else()
set(addSubdirectoryExtraArgs "")
endif()
if(OPTIONS)
# the policy allows us to change options without caching
cmake_policy(SET CMP0077 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
# the policy allows us to change set(CACHE) without caching
if(POLICY CMP0126)
cmake_policy(SET CMP0126 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0126 NEW)
endif()
foreach(OPTION ${OPTIONS})
cpm_parse_option("${OPTION}")
set(${OPTION_KEY} "${OPTION_VALUE}")
endforeach()
endif()
set(CPM_OLD_INDENT "${CPM_INDENT}")
set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:")
add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs})
set(CPM_INDENT "${CPM_OLD_INDENT}")
endif()
endfunction()
# downloads a previously declared package via FetchContent and exports the variables
# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope
function(cpm_fetch_package PACKAGE populated)
set(${populated}
FALSE
PARENT_SCOPE
)
if(${CPM_DRY_RUN})
message(STATUS "${CPM_INDENT} package ${PACKAGE} not fetched (dry run)")
return()
endif()
FetchContent_GetProperties(${PACKAGE})
string(TOLOWER "${PACKAGE}" lower_case_name)
if(NOT ${lower_case_name}_POPULATED)
FetchContent_Populate(${PACKAGE})
if(NOT DOWNLOAD_ONLY AND EXISTS ${${lower_case_name}_SOURCE_DIR}/CMakeLists.txt)
set(CPM_OLD_INDENT "${CPM_INDENT}")
set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:")
cpm_add_subdirectory(
${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} ${EXCLUDE}
)
set(CPM_INDENT "${CPM_OLD_INDENT}")
endif()
set(${populated}
TRUE
PARENT_SCOPE
)
endif()
cpm_store_fetch_properties(
${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR}
)
set(${PACKAGE}_SOURCE_DIR
${${lower_case_name}_SOURCE_DIR}
PARENT_SCOPE
)
set(${PACKAGE}_BINARY_DIR
${${lower_case_name}_BINARY_DIR}
PARENT_SCOPE
)
endfunction()
# splits a package option
function(cpm_parse_option OPTION)
string(REGEX MATCH "^[^ ]+" OPTION_KEY ${OPTION})
string(LENGTH ${OPTION} OPTION_LENGTH)
string(LENGTH ${OPTION_KEY} OPTION_KEY_LENGTH)
string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}")
string(LENGTH "${OPTION}" OPTION_LENGTH)
string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH)
if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH)
# no value for key provided, assume user wants to set option to "ON"
set(OPTION_VALUE "ON")
else()
math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1")
string(SUBSTRING ${OPTION} "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE)
string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE)
endif()
set(OPTION_KEY
"${OPTION_KEY}"
@@ -626,7 +991,7 @@ function(cpm_get_version_from_git_tag GIT_TAG RESULT)
endif()
endfunction()
# guesses if the git tag is a commit hash or an actual tag or a branch nane.
# guesses if the git tag is a commit hash or an actual tag or a branch name.
function(cpm_is_git_tag_commit_hash GIT_TAG RESULT)
string(LENGTH "${GIT_TAG}" length)
# full hash has 40 characters, and short hash has at least 7 characters.

View File

@@ -3,13 +3,25 @@ function(ASSERT_EQUAL)
message(FATAL_ERROR "assertion failed: invalid argument count: ${ARGC}")
endif()
if(NOT ${ARGV0} STREQUAL ${ARGV1})
if(NOT "${ARGV0}" STREQUAL "${ARGV1}")
message(FATAL_ERROR "assertion failed: '${ARGV0}' != '${ARGV1}'")
else()
message(STATUS "test passed: '${ARGV0}' == '${ARGV1}'")
endif()
endfunction()
function(ASSERT_NOT_EQUAL)
if(NOT ARGC EQUAL 2)
message(FATAL_ERROR "assertion failed: invalid argument count: ${ARGC}")
endif()
if("${ARGV0}" STREQUAL "${ARGV1}")
message(FATAL_ERROR "assertion failed: '${ARGV0}' == '${ARGV1}'")
else()
message(STATUS "test passed: '${ARGV0}' != '${ARGV1}'")
endif()
endfunction()
function(ASSERT_EMPTY)
if(NOT ARGC EQUAL 0)
message(FATAL_ERROR "assertion failed: input ${ARGC} not empty: '${ARGV}'")
@@ -24,11 +36,27 @@ function(ASSERT_DEFINED KEY)
endif()
endfunction()
function(ASSERT_NOT_DEFINED KEY)
if(DEFINED ${KEY})
message(FATAL_ERROR "assertion failed: '${KEY}' is defiend (${${KEY}})")
else()
message(STATUS "test passed: '${KEY}' is not defined")
endif()
endfunction()
function(ASSERT_TRUTHY KEY)
if(${${KEY}})
message(STATUS "test passed: '${KEY}' is set truthy")
else()
message(FATAL_ERROR "assertion failed: value of '${KEY}' is not true (${${KEY}})")
message(FATAL_ERROR "assertion failed: value of '${KEY}' is not truthy (${${KEY}})")
endif()
endfunction()
function(ASSERT_FALSY KEY)
if(${${KEY}})
message(FATAL_ERROR "assertion failed: value of '${KEY}' is not falsy (${${KEY}})")
else()
message(STATUS "test passed: '${KEY}' is set falsy")
endif()
endfunction()

View File

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTestingFrameworkExample)
include(../../cmake/CPM.cmake)
CPMAddPackage("bb:HEMRND/TestingFramework@0.4.1")
add_test_suites(TESTS SomeTest)

View File

@@ -0,0 +1,19 @@
#include <HEM/TestingFramework.hpp>
using namespace fakeit;
class ITest {
public:
virtual int getInt() = 0;
};
TEST_CASE("Testing Framework Test") {
constexpr int someIntValue = 123456;
Mock<ITest> testMock;
When(Method(testMock, getInt)).Return(someIntValue);
int readValue = testMock.get().getInt();
REQUIRE(readValue == someIntValue);
}

View File

@@ -8,12 +8,7 @@ include(../../cmake/CPM.cmake)
find_package(Threads REQUIRED)
CPMAddPackage(
NAME asio
VERSION 1.18.1
GITHUB_REPOSITORY chriskohlhoff/asio
GIT_TAG asio-1-18-1 # asio uses non-standard version tag, we must specify GIT_TAG
)
CPMAddPackage("gh:chriskohlhoff/asio#asio-1-18-1@1.18.1")
# ASIO doesn't use CMake, we have to configure it manually. Extra notes for using on Windows:
#

View File

@@ -6,11 +6,7 @@ project(CPMExampleBenchmark)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME fibonacci
GITLAB_REPOSITORY TheLartians/Fibonacci
VERSION 2.0
)
CPMAddPackage("gh:cpm-cmake/testpack-fibonacci@2.0")
CPMAddPackage(
NAME benchmark
@@ -20,7 +16,7 @@ CPMAddPackage(
)
if(benchmark_ADDED)
# Don't use C++14 because it doesn't work in some configurations.
# enable c++11 to avoid compilation errors
set_target_properties(benchmark PROPERTIES CXX_STANDARD 11)
endif()

View File

@@ -11,13 +11,11 @@ target_compile_features(CPMExampleBoost PRIVATE cxx_std_17)
include(../../cmake/CPM.cmake)
CPMFindPackage(
CPMAddPackage(
NAME Boost
GITHUB_REPOSITORY Orphis/boost-cmake
VERSION 1.67.0
FIND_PACKAGE_ARGUMENTS "COMPONENTS system"
VERSION 1.77.0
GITHUB_REPOSITORY "boostorg/boost"
GIT_TAG "boost-1.77.0"
)
find_package(Threads REQUIRED)
target_link_libraries(CPMExampleBoost PRIVATE Boost::system Threads::Threads)
target_link_libraries(CPMExampleBoost PRIVATE Boost::asio)

View File

@@ -26,7 +26,7 @@ for example in examples:
print("running example %s" % example.name)
print("================" + ('=' * len(example.name)))
project = Path(".") / 'build' / example.name
configure = runCommand('cmake -H%s -B%s' % (example, project))
configure = runCommand('cmake -S%s -B%s' % (example, project))
print(' ' + '\n '.join([line for line in configure.split('\n') if 'CPM:' in line]))
build = runCommand('cmake --build %s -- -j%i' % (project, os.cpu_count() / 2))
print(' ' + '\n '.join([line for line in build.split('\n') if 'Built target' in line]))

View File

@@ -6,17 +6,8 @@ project(CPMExampleCatch2)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME fibonacci
GITHUB_REPOSITORY TheLartians/Fibonacci
VERSION 2.0
)
CPMAddPackage(
NAME Catch2
GITHUB_REPOSITORY catchorg/Catch2
VERSION 2.13.4
)
CPMAddPackage("gh:cpm-cmake/testpack-fibonacci@2.0")
CPMAddPackage("gh:catchorg/Catch2@2.13.4")
# ---- Create binary ----

View File

@@ -6,12 +6,7 @@ project(CPMExampleCXXOpts)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME cxxopts
GITHUB_REPOSITORY jarro2783/cxxopts
VERSION 2.2.0
OPTIONS "CXXOPTS_BUILD_EXAMPLES Off" "CXXOPTS_BUILD_TESTS Off"
)
CPMAddPackage("gh:jarro2783/cxxopts@2.2.0")
# ---- Create binary ----

View File

@@ -3,8 +3,14 @@
int main(int argc, char** argv) {
cxxopts::Options options("MyProgram", "One line description of MyProgram");
options.add_options()("h,help", "Show help")("d,debug", "Enable debugging")(
"f,file", "File name", cxxopts::value<std::string>());
// clang-format off
options.add_options()
("h,help", "Show help")
("d,debug", "Enable debugging")
("f,file", "File name", cxxopts::value<std::string>()
);
// clang-format on
auto result = options.parse(argc, argv);

View File

@@ -6,17 +6,8 @@ project(CPMExampleDoctest)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME fibonacci
GITLAB_REPOSITORY TheLartians/Fibonacci
VERSION 2.0
)
CPMAddPackage(
NAME doctest
GITHUB_REPOSITORY onqtam/doctest
GIT_TAG 2.4.5
)
CPMAddPackage("gh:cpm-cmake/testpack-fibonacci@2.0")
CPMAddPackage("gh:onqtam/doctest#2.4.5")
# ---- Create binary ----

View File

@@ -6,11 +6,7 @@ project(CPMJSONExample)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME fmt
GIT_TAG 7.1.3
GITHUB_REPOSITORY fmtlib/fmt
)
CPMAddPackage("gh:fmtlib/fmt#7.1.3")
# ---- Executable ----

View File

@@ -6,11 +6,7 @@ project(CPMExampleGtest)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME fibonacci
GITLAB_REPOSITORY TheLartians/Fibonacci
VERSION 2.0
)
CPMAddPackage("gh:cpm-cmake/testpack-fibonacci@2.0")
CPMAddPackage(
NAME googletest

View File

@@ -5,22 +5,10 @@ project(CPMJSONExample)
# ---- Dependencies ----
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME nlohmann_json
VERSION 3.9.1
# not using the repo as it takes forever to clone
URL https://github.com/nlohmann/json/releases/download/v3.9.1/include.zip
URL_HASH SHA256=6bea5877b1541d353bd77bdfbdb2696333ae5ed8f9e8cc22df657192218cad91
)
if(nlohmann_json_ADDED)
add_library(nlohmann_json INTERFACE)
target_include_directories(nlohmann_json SYSTEM INTERFACE ${nlohmann_json_SOURCE_DIR}/include)
endif()
CPMAddPackage("gh:nlohmann/json@3.10.5")
# ---- Executable ----
add_executable(CPMJSONExample main.cpp)
target_compile_features(CPMJSONExample PRIVATE cxx_std_17)
target_link_libraries(CPMJSONExample nlohmann_json)
target_link_libraries(CPMJSONExample nlohmann_json::nlohmann_json)

View File

@@ -6,11 +6,7 @@ project(CPMlinenoiseExample)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME linenoise
GIT_TAG 1.0
GITHUB_REPOSITORY antirez/linenoise
)
CPMAddPackage("gh:antirez/linenoise#1.0")
if(linenoise_ADDED)
add_library(linenoise ${linenoise_SOURCE_DIR}/linenoise.c)

View File

@@ -6,18 +6,7 @@ project(CPMRangev3Example)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME range-v3
URL https://github.com/ericniebler/range-v3/archive/0.11.0.zip
VERSION 0.11.0
# the range-v3 CMakeLists screws with configuration options
DOWNLOAD_ONLY True
)
if(range-v3_ADDED)
add_library(range-v3 INTERFACE IMPORTED)
target_include_directories(range-v3 SYSTEM INTERFACE "${range-v3_SOURCE_DIR}/include")
endif()
CPMAddPackage("gh:ericniebler/range-v3#0.11.0")
# ---- Executable ----

View File

@@ -32,7 +32,7 @@ auto is_six = [](int i) { return i == 6; };
int main() {
std::vector<int> v{6, 2, 3, 4, 5, 6};
cout << std::boolalpha;
cout << "vector: " << ranges::view::all(v) << '\n';
cout << "vector: " << ranges::views::all(v) << '\n';
cout << "vector any_of is_six: " << ranges::any_of(v, is_six) << '\n';
cout << "vector all_of is_six: " << ranges::all_of(v, is_six) << '\n';

View File

@@ -6,11 +6,7 @@ project(CPMSpdlogExample)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME spdlog
GITHUB_REPOSITORY gabime/spdlog
VERSION 1.8.2
)
CPMAddPackage("gh:gabime/spdlog@1.8.2")
# ---- Executable ----

View File

@@ -6,13 +6,7 @@ project(CPMYamlExample)
include(../../cmake/CPM.cmake)
CPMAddPackage(
NAME yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
VERSION 0.6.3
GIT_TAG yaml-cpp-0.6.3
OPTIONS "YAML_CPP_BUILD_TESTS Off" "YAML_CPP_BUILD_CONTRIB Off" "YAML_CPP_BUILD_TOOLS Off"
)
CPMAddPackage("gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3")
# ---- Executable ----

2
test/integration/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Use this to have a local integration test which is for personal experiments
test_local.rb

View File

@@ -0,0 +1,44 @@
# CPM.cmake Integration Tests
The integration tests of CPM.cmake are written in Ruby. They use a custom integration test framework which extends the [Test::Unit](https://www.rubydoc.info/github/test-unit/test-unit/Test/Unit) library.
They require Ruby 2.7.0 or later.
## Running tests
To run all tests from the repo root execute:
```
$ ruby test/integration/runner.rb
```
The runner will run all tests and generate a report of the exeuction.
The current working directory doesn't matter. If you are in `<repo-root>/test/integration`, you can run simply `$ ruby runner.rb`.
You can execute with `--help` (`$ ruby runner.rb --help`) to see various configuration options of the runner like running individual tests or test cases, or ones that match a regex.
The tests themselves are situated in the Ruby scripts prefixed with `test_`. `<repo-root>/test/integration/test_*`. You can also run an individual test script. For example to only run the **basics** test case, you can execute `$ ruby test_basics.rb`
The tests generate CMake scripts and execute CMake and build toolchains. By default they do this in a directory they generate in your temp path (`/tmp/cpm-test/` on Linux). You can configure the working directory of the tests with an environment variable `CPM_INTEGRATION_TEST_DIR`. For example `$ CPM_INTEGRATION_TEST_DIR=~/mycpmtest; ruby runner.rb`
## Writing tests
Writing tests makes use of the custom integration test framework in `lib.rb`. It is a relatively small extension of Ruby's Test::Unit library.
### The Gist
* Tests cases are Ruby scripts in this directory. The file names must be prefixed with `test_`
* The script should `require_relative './lib'` to allow for individual execution (or else if will only be executable from the runner)
* A test case file should contain a single class which inherits from `IntegrationTest`. It *can* contain multiple classes, but that's bad practice as it makes individual execution harder and implies a dependency between the classes.
* There should be no dependency between the test scripts. Each should be executable individually and the order in which multiple ones are executed mustn't matter.
* The class should contain methods, also prefixed with `test_` which will be executed by the framework. In most cases there would be a single test method per class.
* In case there are multiple test methods, they will be executed in the order in which they are defined.
* The test methods should contain assertions which check for the expected state of things at varous points of the test's execution.
### More
* [A basic tutorial on writing integration tests.](tutorial.md)
* [A brief reference of the integration test framework](reference.md)
* Make sure you're familiar with the [idiosyncrasies](idiosyncrasies.md) of writing integration tests
* [Some tips and tricks](tips.md)

View File

@@ -0,0 +1,98 @@
# Notable Idiosyncrasies When Writing Integration Tests
As an integration test framework based on a unit test framework the one created for CPM.cmake suffers from several idiosyncrasies. Make sure you familiarize yourself with them before writing integration tests.
## No shared instance variables between methods
The runner will create an instance of the test class for each test method. This means that instance variables defined in a test method, *will not* be visible in another. For example:
```ruby
class MyTest < IntegrationTest
def test_something
@x = 123
assert_equal 123, @x # Pass. @x is 123
end
def test_something_else
assert_equal 123, @x # Fail! @x would be nil here
end
end
```
There are hacks around sharing Ruby state between methods, but we choose not to use them. If you want to initialize something for all test methods, use `setup`.
```ruby
class MyTest < IntegrationTest
def setup
@x = 123
end
def test_something
assert_equal 123, @x # Pass. @x is 123 thanks to setup
end
def test_something_else
assert_equal 123, @x # Pass. @x is 123 thanks to setup
end
end
```
## `IntegrationTest` makes use of `Test::Unit::TestCase#cleanup`
After each test method the `cleanup` method is called thanks to Test::Unit. If you require the use of `cleanup` in your own tests, make sure you call `super` to also run `IntegrationTest#cleanup`.
```ruby
class MyTest < IntegrationTest
def cleanup
super
my_cleanup
end
# ...
end
```
## It's better to have assertions in test methods as opposed to helper methods
Test::Unit will display a helpful message if an assertion has failed. It will also include the line of code in the test method which caused the failure. However if an assertion is not in the test method, it will display the line which calls the method in which it is. So, please try, to have most assertions in test methods (though we acknowledge that in certain cases this is not practical). For example, if you only require scopes, try using lambdas.
Instead of this:
```ruby
class MyTest < IntegrationTest
def test_something
do_a
do_b
do_c
end
def do_a
# ...
end
def do_b
# ...
assert false # will display failed line as "do_b"
end
def do_c
# ...
end
end
```
...write this:
```ruby
class MyTest < IntegrationTest
def test_something
do_a = -> {
# ...
}
do_b = -> {
# ...
assert false # will display failed line as "assert false"
}
do_c = -> {
# ...
}
do_a.()
do_b.()
do_c.()
end
end
```

200
test/integration/lib.rb Normal file
View File

@@ -0,0 +1,200 @@
require 'fileutils'
require 'open3'
require 'tmpdir'
require 'test/unit'
module TestLib
TMP_DIR = File.expand_path(ENV['CPM_INTEGRATION_TEST_DIR'] || File.join(Dir.tmpdir, 'cpm-test', Time.now.strftime('%Y_%m_%d-%H_%M_%S')))
CPM_PATH = File.expand_path('../../cmake/CPM.cmake', __dir__)
TEMPLATES_DIR = File.expand_path('templates', __dir__)
# Environment variables which are read by cpm
CPM_ENV = %w(
CPM_USE_LOCAL_PACKAGES
CPM_LOCAL_PACKAGES_ONLY
CPM_DOWNLOAD_ALL
CPM_DONT_UPDATE_MODULE_PATH
CPM_DONT_CREATE_PACKAGE_LOCK
CPM_INCLUDE_ALL_IN_PACKAGE_LOCK
CPM_USE_NAMED_CACHE_DIRECTORIES
CPM_SOURCE_CACHE
)
def self.clear_env
CPM_ENV.each { ENV[_1] = nil }
end
end
puts "Warning: test directory '#{TestLib::TMP_DIR}' already exists" if File.exist?(TestLib::TMP_DIR)
raise "Cannot find 'CPM.cmake' at '#{TestLib::CPM_PATH}'" if !File.file?(TestLib::CPM_PATH)
puts "Running CPM.cmake integration tests"
puts "Temp directory: '#{TestLib::TMP_DIR}'"
# Clean all CPM-related env vars
TestLib.clear_env
class Project
def initialize(src_dir, bin_dir)
@src_dir = src_dir
@bin_dir = bin_dir
end
attr :src_dir, :bin_dir
def create_file(target_path, text, args = {})
target_path = File.join(@src_dir, target_path)
# tweak args
args[:cpm_path] = TestLib::CPM_PATH if !args[:cpm_path]
args[:packages] = [args[:package]] if args[:package] # if args contain package, create the array
args[:packages] = args[:packages].join("\n") if args[:packages] # join all packages if any
File.write target_path, text % args
end
def create_file_from_template(target_path, source_path, args = {})
source_path = File.join(@src_dir, source_path)
raise "#{source_path} doesn't exist" if !File.file?(source_path)
src_text = File.read source_path
create_file target_path, src_text, args
end
# common function to create ./CMakeLists.txt from ./lists.in.cmake
def create_lists_from_default_template(args = {})
create_file_from_template 'CMakeLists.txt', 'lists.in.cmake', args
end
CommandResult = Struct.new :out, :err, :status
def configure(extra_args = '')
CommandResult.new *Open3.capture3("cmake -S #{@src_dir} -B #{@bin_dir} #{extra_args}")
end
def build(extra_args = '')
CommandResult.new *Open3.capture3("cmake --build #{@bin_dir} #{extra_args}")
end
class CMakeCache
class Entry
def initialize(val, type, advanced, desc)
@val = val
@type = type
@advanced = advanced
@desc = desc
end
attr :val, :type, :advanced, :desc
alias_method :advanced?, :advanced
def inspect
"(#{val.inspect} #{type}" + (advanced? ? ' ADVANCED)' : ')')
end
end
Package = Struct.new(:ver, :src_dir, :bin_dir)
def self.from_dir(dir)
entries = {}
cur_desc = ''
file = File.join(dir, 'CMakeCache.txt')
return nil if !File.file?(file)
File.readlines(file).each { |line|
line.strip!
next if line.empty?
next if line.start_with? '#' # comment
if line.start_with? '//'
cur_desc += line[2..]
else
m = /(.+?)(-ADVANCED)?:([A-Z]+)=(.*)/.match(line)
raise "Error parsing '#{line}' in #{file}" if !m
entries[m[1]] = Entry.new(m[4], m[3], !!m[2], cur_desc)
cur_desc = ''
end
}
CMakeCache.new entries
end
def initialize(entries)
@entries = entries
package_list = self['CPM_PACKAGES']
@packages = if package_list
# collect package data
@packages = package_list.split(';').map { |name|
[name, Package.new(
self["CPM_PACKAGE_#{name}_VERSION"],
self["CPM_PACKAGE_#{name}_SOURCE_DIR"],
self["CPM_PACKAGE_#{name}_BINARY_DIR"]
)]
}.to_h
else
{}
end
end
attr :entries, :packages
def [](key)
e = @entries[key]
return nil if !e
e.val
end
end
def read_cache
CMakeCache.from_dir @bin_dir
end
end
class IntegrationTest < Test::Unit::TestCase
self.test_order = :defined # run tests in order of defintion (as opposed to alphabetical)
def cleanup
# Clear cpm-related env vars which may have been set by the test
TestLib.clear_env
end
# extra assertions
def assert_success(res)
msg = build_message(nil, "command status was expected to be a success, but failed with code <?> and STDERR:\n\n#{res.err}", res.status.to_i)
assert_block(msg) { res.status.success? }
end
def assert_same_path(a, b)
msg = build_message(nil, "<?> expected but was\n<?>", a, b)
assert_block(msg) { File.identical? a, b }
end
# utils
class << self
def startup
@@test_dir = File.join(TestLib::TMP_DIR, self.name.
# to-underscore conversion from Rails
gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
)
end
end
def cur_test_dir
@@test_dir
end
def make_project(template_dir = nil)
test_name = local_name
test_name = test_name[5..] if test_name.start_with?('test_')
base = File.join(cur_test_dir, test_name)
src_dir = base + '-src'
FileUtils.mkdir_p src_dir
if template_dir
template_dir = File.join(TestLib::TEMPLATES_DIR, template_dir)
raise "#{template_dir} is not a directory" if !File.directory?(template_dir)
FileUtils.copy_entry template_dir, src_dir
end
Project.new src_dir, base + '-bin'
end
end

View File

@@ -0,0 +1,65 @@
# Integration Test Framework Refernce
## `TestLib`
A module for the framework. Provides global data and functionality. For ease of use the utility classes are *not* in this module.
Provides:
* `TMP_DIR` - the temporary directory for the current test run
* `CPM_PATH` - path to CPM.cmake. The thing that is being tested
* `TEMPLATES_DIR` - path to integration test templates
* `CPM_ENV` - an array of the names of all environment variables, which CPM.cmake may read
* `.clear_env` - a function to clear all aforementioned environment variables
## `Project`
A helper class to manage a CMake project.
Provides:
* `#initialize(src_dir, bin_dir)` - create a project with a given source and binary directory
* `#src_dir`, `#bin_dir` - get project directories
* `#create_file(target_path, text, args = {})` - create a file in the project's source directory with a given test. The `args` hash is used to interpolate markup in the text string.
* Will set `:cpm_path` in `args` to `TestLib::CPM_PATH` if not already present.
* If `:package` is present it will be added to the array `:packages`
* Will convert `:packages` form an array to a string
* `#create_file_from_template(target_path, source_path, args = {})` - create a file in the project source directory, based on another file in the project source directory. The contents of the file at `source_path` will be read and used in `create_file`
* `#create_lists_from_default_template(args = {})` - same as `create_file_from_template('CMakeLists.txt', 'lists.in.cmake', args)`
* `::CommandResult` - a struct of:
* `out` - the standard output from a command execution
* `err` - the standard error output from the execution
* `status` - the [`Process::Status`](https://ruby-doc.org/core-2.7.0/Process/Status.html) of the execution
* `#configure(extra_args = '') => CommandResult` - configure the project with optional extra args to CMake
* `#build(extra_args = '') => CommandResult` - build the project with optional extra args to CMake
* `::CMakeCache` - a helper class with the contents of a CMakeCache.txt. Provides:
* `::Entry` - a CMake cache entry of:
* `val` - the value as string
* `type` - the type as string
* `advanced?` - whether the entry is an advanced option
* `desc` - the description of the entry (can be an empty string)
* `::Package` - the CMake cache for a CPM.cmake package. A struct of:
* `ver` - the version as string
* `src_dir`, `bin_dir` - the source and binary directories of the package
* `.from_dir(dir)` - create an instance of `CMakeCache` from `<dir>/CMakeLists.txt`
* `#initialize(entries)` - create a cache from a hash of entries by name. Will populate packages.
* `#entries => {String => Entry}` - the entries of the cache
* `#packages => {String => Package}` - CPM.cmake packages by name found in the cache
* `#[](key) => String` - an entry value from an entry name. Created because the value is expected to be needed much more frequently than the entire entry data. To get a full entry use `cache.entries['name']`.
* `read_cache => CMakeCache` - reads the CMake cache in the binary directory of the project and returns it as a `CMakeCache` instance
## `IntegrationTest`
The class which must be a parent of all integration test case classes. It itself extends `Test::Unit::TestCase` with:
### Assertions
* `assert_success(res)` - assert that an instance of `Project::CommandResult` is a success
* `assert_same_path(a, b)` - assert that two strings represent the same path. For example on Windows `c:\foo` and `C:\Foo` do.
### Utils
* `cur_test_dir` - the directory of the current test case. A subdirectory of `TestLib::TMP_DIR`
* `make_project(template_dir = nil)` - create a project from a test method. Will create a the project's source and binary directories as subdirectories of `cur_test_dir`.
* Optionally work with a template directory, in which case it will copy the contents of the template directory (one from `templates`) in the project's source directory.

View File

@@ -0,0 +1,4 @@
require_relative './lib'
exit Test::Unit::AutoRunner::run(true, __dir__)

View File

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(no-deps)
include("%{cpm_path}")
add_executable(no-deps main.c)

View File

@@ -0,0 +1,6 @@
#include <stdio.h>
int main() {
puts("Hello");
return 0;
}

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(using-adder)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include("%{cpm_path}")
%{packages}
add_executable(using-adder using-adder.cpp)
target_link_libraries(using-adder adder)

View File

@@ -0,0 +1,8 @@
#include <adder/adder.hpp>
#include <cstdio>
int main() {
int sum = adder::add(5, 3);
std::printf("%d\n", sum);
return 0;
}

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(using-fibadder)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include("%{cpm_path}")
%{packages}
add_executable(using-fibadder using-fibadder.cpp)
target_link_libraries(using-fibadder fibadder)

View File

@@ -0,0 +1,8 @@
#include <cstdio>
#include <fibadder/fibadder.hpp>
int main() {
int sum = fibadder::fibadd(6, 7);
std::printf("%d\n", sum);
return 0;
}

View File

@@ -0,0 +1,56 @@
require_relative './lib'
# Tests of cpm caches and vars when no packages are used
class Basics < IntegrationTest
# Test cpm caches with no cpm-related env vars
def test_cpm_default
prj = make_project 'no-deps'
prj.create_lists_from_default_template
assert_success prj.configure
@cache = prj.read_cache
assert_empty @cache.packages
assert_same_path TestLib::CPM_PATH, check_and_get('CPM_FILE')
assert_same_path File.dirname(TestLib::CPM_PATH), check_and_get('CPM_DIRECTORY')
assert_equal 'OFF', check_and_get('CPM_DRY_RUN')
assert_equal 'CPM:', check_and_get('CPM_INDENT')
assert_equal '1.0.0-development-version', check_and_get('CPM_VERSION')
assert_equal 'OFF', check_and_get('CPM_SOURCE_CACHE', 'PATH')
assert_equal 'OFF', check_and_get('CPM_DOWNLOAD_ALL', 'BOOL')
assert_equal 'OFF', check_and_get('CPM_LOCAL_PACKAGES_ONLY', 'BOOL')
assert_equal 'OFF', check_and_get('CPM_USE_LOCAL_PACKAGES', 'BOOL')
assert_equal 'OFF', check_and_get('CPM_USE_NAMED_CACHE_DIRECTORIES', 'BOOL')
assert_equal 'OFF', check_and_get('CPM_DONT_CREATE_PACKAGE_LOCK', 'BOOL')
assert_equal 'OFF', check_and_get('CPM_INCLUDE_ALL_IN_PACKAGE_LOCK', 'BOOL')
assert_same_path File.join(prj.bin_dir, 'cpm-package-lock.cmake'), check_and_get('CPM_PACKAGE_LOCK_FILE')
assert_equal 'OFF', check_and_get('CPM_DONT_UPDATE_MODULE_PATH', 'BOOL')
assert_same_path File.join(prj.bin_dir, 'CPM_modules'), check_and_get('CPM_MODULE_PATH')
end
# Test when env CPM_SOURCE_CACHE is set
def test_env_cpm_source_cache
ENV['CPM_SOURCE_CACHE'] = cur_test_dir
prj = make_project 'no-deps'
prj.create_lists_from_default_template
assert_success prj.configure
@cache = prj.read_cache
assert_equal cur_test_dir, check_and_get('CPM_SOURCE_CACHE', 'PATH')
end
def check_and_get(key, type = 'INTERNAL')
e = @cache.entries[key]
assert_not_nil e, key
assert_equal type, e.type, key
e.val
end
end

View File

@@ -0,0 +1,43 @@
require_relative './lib'
# Tests FetchContent overriding with CPM
class FetchContentCompatibility < IntegrationTest
def setup
@cache_dir = File.join(cur_test_dir, 'cpmcache')
ENV['CPM_SOURCE_CACHE'] = @cache_dir
end
def test_add_dependency_cpm_and_fetchcontent
prj = make_project 'using-adder'
prj.create_lists_from_default_template package: <<~PACK
CPMAddPackage(
NAME testpack-adder
GITHUB_REPOSITORY cpm-cmake/testpack-adder
VERSION 1.0.0
OPTIONS "ADDER_BUILD_TESTS OFF"
)
# should have no effect, as we added the dependency using CPM
FetchContent_Declare(
testpack-adder
GIT_REPOSITORY https://github.com/cpm-cmake/testpack-adder
GIT_TAG v1.0.0
)
FetchContent_MakeAvailable(testpack-adder)
PACK
# configure with unpopulated cache
assert_success prj.configure
assert_success prj.build
# cache is populated
assert_true File.exist?(File.join(@cache_dir, "testpack-adder"))
# configure with populated cache
assert_success prj.configure
assert_success prj.build
end
end

View File

@@ -0,0 +1,91 @@
require_relative './lib'
class Simple < IntegrationTest
ADDER_PACKAGE_NAME = 'testpack-adder'
def test_update_single_package
prj = make_project 'using-adder'
adder_cache0 = nil
adder_ver_file = nil
create_with_commit_sha = -> {
prj.create_lists_from_default_template package:
'CPMAddPackage("gh:cpm-cmake/testpack-adder#cad1cd4b4cdf957c5b59e30bc9a1dd200dbfc716")'
assert_success prj.configure
cache = prj.read_cache
assert_equal 1, cache.packages.size
adder_cache = cache.packages[ADDER_PACKAGE_NAME]
assert_not_nil adder_cache
assert_equal '0', adder_cache.ver
assert File.directory? adder_cache.src_dir
assert File.directory? adder_cache.bin_dir
adder_ver_file = File.join(adder_cache.src_dir, 'version')
assert File.file? adder_ver_file
assert_equal 'initial', File.read(adder_ver_file).strip
# calculated adder values
assert_equal 'ON', cache['ADDER_BUILD_EXAMPLES']
assert_equal 'ON', cache['ADDER_BUILD_TESTS']
assert_equal adder_cache.src_dir, cache['adder_SOURCE_DIR']
assert_equal adder_cache.bin_dir, cache['adder_BINARY_DIR']
# store for future comparisons
adder_cache0 = adder_cache
}
update_to_version_1 = -> {
prj.create_lists_from_default_template package:
'CPMAddPackage("gh:cpm-cmake/testpack-adder@1.0.0")'
assert_success prj.configure
cache = prj.read_cache
assert_equal 1, cache.packages.size
adder_cache = cache.packages[ADDER_PACKAGE_NAME]
assert_not_nil adder_cache
assert_equal '1.0.0', adder_cache.ver
# dirs shouldn't have changed
assert_equal adder_cache0.src_dir, adder_cache.src_dir
assert_equal adder_cache0.bin_dir, adder_cache.bin_dir
assert_equal '1.0.0', File.read(adder_ver_file).strip
}
update_with_option_off_and_build = -> {
prj.create_lists_from_default_template package: <<~PACK
CPMAddPackage(
NAME testpack-adder
GITHUB_REPOSITORY cpm-cmake/testpack-adder
VERSION 1.0.0
OPTIONS "ADDER_BUILD_TESTS OFF"
)
PACK
assert_success prj.configure
assert_success prj.build
exe_dir = File.join(prj.bin_dir, 'bin')
assert File.directory? exe_dir
exes = Dir[exe_dir + '/**/*'].filter {
# on multi-configuration generators (like Visual Studio) the executables will be in bin/<Config>
# also filter-out other articacts like .pdb or .dsym
!File.directory?(_1) && File.stat(_1).executable?
}.map {
# remove .exe extension if any (there will be one on Windows)
File.basename(_1, '.exe')
}.sort
# we should end up with two executables
# * simple - the simple example from adder
# * using-adder - for this project
# ...and notably no test for adder, which must be disabled from the option override from above
assert_equal ['simple', 'using-adder'], exes
}
create_with_commit_sha.()
update_to_version_1.()
update_with_option_off_and_build.()
end
end

View File

@@ -0,0 +1,80 @@
require_relative './lib'
# Tests with source cache
class SourceCache < IntegrationTest
def setup
@cache_dir = File.join(cur_test_dir, 'cpmcache')
ENV['CPM_SOURCE_CACHE'] = @cache_dir
end
def test_add_remove_dependency
prj = make_project 'using-fibadder'
###################################
# create
prj.create_lists_from_default_template package: 'CPMAddPackage("gh:cpm-cmake/testpack-fibadder@1.0.0")'
assert_success prj.configure
@cache = prj.read_cache
# fibadder - adder
# \ fibonacci - Format
assert_equal 4, @cache.packages.size
check_package_cache 'testpack-fibadder', '1.0.0', '6a17d24c95c44a169ff8ba173f52876a2ba3d137'
check_package_cache 'testpack-adder', '1.0.0', '1a4c153849d8e0cf9a3a245e5f6ab6e4722d8995'
check_package_cache 'testpack-fibonacci', '2.0', '332c789cb09b8c2f92342dfb874c82bec643daf6'
check_package_cache 'Format.cmake', '1.0', 'c5897bd28c5032d45f7f669c8fb470790d2ae156'
###################################
# add one package with a newer version
prj.create_lists_from_default_template packages: [
'CPMAddPackage("gh:cpm-cmake/testpack-adder@1.0.1")',
'CPMAddPackage("gh:cpm-cmake/testpack-fibadder@1.0.0")',
]
assert_success prj.configure
@cache = prj.read_cache
assert_equal 4, @cache.packages.size
check_package_cache 'testpack-fibadder', '1.0.0', '6a17d24c95c44a169ff8ba173f52876a2ba3d137'
check_package_cache 'testpack-adder', '1.0.1', '84eb33c1b8db880083cefc2adf4dc3f04778cd44'
check_package_cache 'testpack-fibonacci', '2.0', '332c789cb09b8c2f92342dfb874c82bec643daf6'
check_package_cache 'Format.cmake', '1.0', 'c5897bd28c5032d45f7f669c8fb470790d2ae156'
end
def test_second_project
prj = make_project 'using-fibadder'
prj.create_lists_from_default_template package: 'CPMAddPackage("gh:cpm-cmake/testpack-fibadder@1.1.0")'
assert_success prj.configure
@cache = prj.read_cache
# fibadder - adder
# \ fibonacci - Format
assert_equal 4, @cache.packages.size
check_package_cache 'testpack-fibadder', '1.1.0', '603d79d88d7230cc749460a0f476df862aa70ead'
check_package_cache 'testpack-adder', '1.0.1', '84eb33c1b8db880083cefc2adf4dc3f04778cd44'
check_package_cache 'testpack-fibonacci', '2.0', '332c789cb09b8c2f92342dfb874c82bec643daf6'
check_package_cache 'Format.cmake', '1.0', 'c5897bd28c5032d45f7f669c8fb470790d2ae156'
end
def test_cache_dir_contents
num_subdirs = -> (name) { Dir["#{File.join(@cache_dir, name.downcase)}/*/"].size }
assert_equal 2, num_subdirs.('testpack-fibadder')
assert_equal 2, num_subdirs.('testpack-adder')
assert_equal 1, num_subdirs.('testpack-fibonacci')
assert_equal 1, num_subdirs.('Format.cmake')
end
def check_package_cache(name, ver, dir_sha1)
package = @cache.packages[name]
assert_not_nil package, name
assert_equal ver, package.ver
expected_parent_dir = File.join(@cache_dir, name.downcase)
assert package.src_dir.start_with?(expected_parent_dir), "#{package.src_dir} must be in #{expected_parent_dir}"
assert_equal dir_sha1, File.basename(package.src_dir)
end
end

35
test/integration/tips.md Normal file
View File

@@ -0,0 +1,35 @@
# Tips and Tricks
## Playing and experimenting
Create a file called `test_local.rb` in this directory to have an integration test which is for your personal experiments and just playing with the integration test framework. `test_local.rb` is gitignored.
## Speeding-up development
Running an integration test requires configuring directories with CMake which can be quite slow. To speed-up development of integration tests consider doing the following steps:
**Work with standalone tests**
Instead of starting the runner, run just your integration test (`$ ruby test_your_test.rb`). This won't burden the execution with the others.
**Export the environment variable `CPM_INTEGRATION_TEST_DIR` to some local directory**
By default the framework generates a new temporary directory for each test run. If you override the temp directory to a specific one, rerunning the tests will work with the binary directories from the previous run and will improve the performance considerably.
*NOTE HOWEVER* that in certain cases this may not be an option. Some tests might assert that certain artifacts in the temporary directory are missing but upon rerunning in an existing directory they will be there causing the test to fail.
*ALSO NOTE* that this may silently affect reruns based on CMake caches from previous runs. If your test fails in peculiar ways on reruns, try a clean run. Always do a clean run before declaring a test a success.
**Set `CPM_SOURCE_CACHE` even if the test doesn't require it**
This is not a option for tests which explicitly check that there is no source cache. However certain tests may be indiferent to this. For such cases in development, you can add a setup function in the lines of:
```ruby
def setup
ENV['CPM_SOURCE_CACHE'] = '/home/myself/.testcpmcache'
end
```
Then the packages from your test will be cached and not redownloaded every time which is a dramatic improvement in performance.
*NOTE HOWEVER* that this may introduce subtle bugs. Always test without this dev-only addition, before declaring a test a success.

View File

@@ -0,0 +1,69 @@
# Integration Test Tutorial
Let's create an integration test which checks that CPM.cmake can make a specific package available.
First we do some boilerplate.
```ruby
require_relative './lib'
class MyTest < IntegrationTest
# test that CPM.cmake can make https://github.com/cpm-cmake/testpack-adder/ available as a package
def test_make_adder_available
end
end
```
Now we have our test case class, and the single test method that we will require. Let's focus on the method's contents. The integration test framework provides us with a helper class, `Project`, which can be used for this scenario. A project has an assoiciated pair of source and binary directories in the temporary directory and it provides methods to work with them.
We start by creating the project:
```ruby
prj = make_project
```
`make_project` is method of IntegrationTest which generates a source and a binary directory for it based on the name of our test class and test method. The project doesn't contain anything yet, so let's create some source files:
```ruby
prj.create_file 'main.cpp', <<~SRC
#include <iostream>
#include <adder/adder.hpp>
int main() {
std::cout << adder::add(1, 2) << '\\n';
return 0;
}
SRC
prj.create_file 'CMakeLists.txt', <<~SRC
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(using-adder)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include("%{cpm_path}")
CPMAddPackage("gh:cpm-cmake/testpack-adder@1.0.0")
add_executable(using-adder main.cpp)
target_link_libraries(using-adder adder)
SRC
```
Note the line `include("%{cpm_path}")` when creating `CMakeLists.txt`. It contains a markup `%{cpm_path}`. `Project#create_file` will see such markups and substitute them with the appropriate values (in this case the path to CPM.cmake).
Now that we have the two files we need it's time we configure our project. We can use the opportunity to assert that the configure is successful as we expect it to be.
```ruby
assert_success prj.configure
```
Now we can read the generated `CMakeCache.txt` and assert that certain values we expect are inside. `Project` provides a method for that: `read_cache`. It will return an instance of `Project::CMakeCache` which contains the data from the cache and provides additional helper functionalities. One of them is `packages`, which is a hash of the CPM.cmake packages in the cache with their versions, binary, source directories. So let's get the cache and assert that there is only one CPM.cmake package inside ant it has the version we expect.
```ruby
cache = prj.read_cache
assert_equal 1, cache.packages.size
assert_equal '1.0.0', cache.packages['testpack-adder'].ver
```
Finally let's assert that the project can be built. This would mean that CPM.cmake has made the package available to our test project and that it has the appropriate include directories and link libraries to make an executable out of `main.cpp`.
```ruby
assert_success prj.build
```
You can see the entire code for this tutorial in [tutorial.rb](tutorial.rb) in this directory.

View File

@@ -0,0 +1,37 @@
# This file is intentionally not prefixed with test_
# It is a tutorial for making integration tests and is not to be run from the runner
require_relative './lib'
class Tutorial < IntegrationTest
# test that CPM.cmake can make https://github.com/cpm-cmake/testpack-adder/ available as a package
def test_make_adder_available
prj = make_project
prj.create_file 'main.cpp', <<~SRC
#include <iostream>
#include <adder/adder.hpp>
int main() {
std::cout << adder::add(1, 2) << '\\n';
return 0;
}
SRC
prj.create_file 'CMakeLists.txt', <<~SRC
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(using-adder)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include("%{cpm_path}")
CPMAddPackage("gh:cpm-cmake/testpack-adder@1.0.0")
add_executable(using-adder main.cpp)
target_link_libraries(using-adder adder)
SRC
assert_success prj.configure
cache = prj.read_cache
assert_equal 1, cache.packages.size
assert_equal '1.0.0', cache.packages['testpack-adder'].ver
assert_success prj.build
end
end

View File

@@ -6,7 +6,9 @@ include(${CMAKE_CURRENT_LIST_DIR}/../../cmake/CPM.cmake)
CPMAddPackage(
NAME Format.cmake
VERSION 1.6
VERSION 1.7.3
GITHUB_REPOSITORY TheLartians/Format.cmake
OPTIONS "FORMAT_CHECK_CMAKE ON"
# We exclude cmake files from integration tests as they contain invalid lines of code which are
# used by the integration test scripts
OPTIONS "CMAKE_FORMAT_EXCLUDE integration/templates"
)

View File

@@ -0,0 +1,2 @@
/CMakeLists.txt
/package-lock.cmake

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTest)
# ---- Dependencies ----
include(@CPM_PATH@/CPM.cmake)
CPMAddPackage(
NAME BrokenDependency
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/dependency
EXCLUDE_FROM_ALL @EXCLUDE_FROM_ALL@
)

View File

@@ -0,0 +1,3 @@
project(BrokenDependency)
add_custom_target(error ALL ${CMAKE_COMMAND} -E false)

View File

@@ -33,7 +33,7 @@ set(FIBONACCI_VERSION 1.0)
reset_test()
execute_process(
COMMAND ${CMAKE_COMMAND} "-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}"
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}"
"-DCPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" RESULT_VARIABLE ret
)
@@ -76,7 +76,7 @@ reset_test()
execute_process(
COMMAND ${CMAKE_COMMAND} -E env "CPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" ${CMAKE_COMMAND}
"-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
"-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
)
assert_equal(${ret} "0")
@@ -86,7 +86,7 @@ assert_exists("${CPM_SOURCE_CACHE_DIR}/fibonacci")
execute_process(
COMMAND ${CMAKE_COMMAND} -E env "CPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" ${CMAKE_COMMAND}
"-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}-2"
"-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}-2"
RESULT_VARIABLE ret
)
@@ -99,7 +99,7 @@ reset_test()
execute_process(
COMMAND
${CMAKE_COMMAND} -E env "CPM_SOURCE_CACHE=${CMAKE_CURRENT_BINARY_DIR}/junk" ${CMAKE_COMMAND}
"-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}"
"-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}"
"-DCPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}"
RESULT_VARIABLE ret
)
@@ -116,7 +116,7 @@ reset_test()
execute_process(
COMMAND ${CMAKE_COMMAND} -E env "CPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" ${CMAKE_COMMAND}
"-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
"-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
)
assert_equal(${ret} "0")
@@ -130,7 +130,7 @@ update_cmake_lists()
execute_process(
COMMAND ${CMAKE_COMMAND} -E env "CPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" ${CMAKE_COMMAND}
"-H${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
"-S${CMAKE_CURRENT_LIST_DIR}/remote_dependency" "-B${TEST_BUILD_DIR}" RESULT_VARIABLE ret
)
assert_equal(${ret} "0")

View File

@@ -0,0 +1,49 @@
include(${CPM_PATH}/CPM.cmake)
include(${CPM_PATH}/testing.cmake)
set(baseDir "${CMAKE_CURRENT_BINARY_DIR}/test_dirty_cache")
find_package(Git REQUIRED)
function(git_do dir)
execute_process(
COMMAND ${GIT_EXECUTABLE} -c user.name='User' -c user.email='user@email.org' ${ARGN}
RESULT_VARIABLE result
OUTPUT_VARIABLE status
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY "${dir}"
)
if(result)
message(FATAL_ERROR "git ${ARGN} fail: ${result} ${status}")
endif()
endfunction()
file(MAKE_DIRECTORY "${baseDir}")
file(WRITE "${baseDir}/draft.txt" "this is a test")
git_do("${baseDir}" init -b main)
git_do("${baseDir}" commit --allow-empty -m "empty repo")
message(STATUS "empty repo with file")
cpm_check_git_working_dir_is_clean(${baseDir} HEAD emptygit_test)
assert_falsy(emptygit_test)
git_do("${baseDir}" add draft.txt)
git_do("${baseDir}" commit -m "test change")
git_do("${baseDir}" tag v0.0.0)
message(STATUS "commit a change")
cpm_check_git_working_dir_is_clean(${baseDir} v0.0.0 onecommit_test)
assert_truthy(onecommit_test)
file(WRITE "${baseDir}/draft.txt" "a modification")
message(STATUS "dirty repo")
cpm_check_git_working_dir_is_clean(${baseDir} v0.0.0 nonemptygit_test)
assert_falsy(nonemptygit_test)
git_do("${baseDir}" add draft.txt)
git_do("${baseDir}" commit -m "another change")
message(STATUS "repo clean")
cpm_check_git_working_dir_is_clean(${baseDir} v0.0.0 twocommit_test)
assert_falsy(twocommit_test)
file(REMOVE_RECURSE "${baseDir}")

View File

@@ -0,0 +1,35 @@
include(CMakePackageConfigHelpers)
include(${CPM_PATH}/testing.cmake)
set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/exclude_from_all)
function(init_project EXCLUDE_FROM_ALL)
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/broken_dependency/CMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/broken_dependency/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
execute_process(
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/broken_dependency" "-B${TEST_BUILD_DIR}"
RESULT_VARIABLE ret
)
assert_equal(${ret} "0")
endfunction()
function(build_project expected_success)
execute_process(COMMAND ${CMAKE_COMMAND} "--build" "${TEST_BUILD_DIR}" RESULT_VARIABLE ret)
if(expected_success)
assert_equal(${ret} 0)
else()
assert_not_equal(${ret} 0)
endif()
endfunction()
init_project(FALSE)
build_project(FALSE)
init_project(TRUE)
build_project(TRUE)

View File

@@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
include(${CPM_PATH}/testing.cmake)
include(CMakePackageConfigHelpers)
set(CPM_SOURCE_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/CPM")
set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/fetchcontent_dependency)
function(clear_cache)
message(STATUS "clearing CPM cache")
file(REMOVE_RECURSE ${CPM_SOURCE_CACHE_DIR})
assert_not_exists("${CPM_SOURCE_CACHE_DIR}")
endfunction()
function(update_cmake_lists)
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/fetchcontent_dependency/CMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/fetchcontent_dependency/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
endfunction()
function(reset_test)
clear_cache()
file(REMOVE_RECURSE ${TEST_BUILD_DIR})
update_cmake_lists()
endfunction()
# Read CPM_SOURCE_CACHE from arguments
reset_test()
execute_process(
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/fetchcontent_dependency"
"-B${TEST_BUILD_DIR}" "-DCPM_SOURCE_CACHE=${CPM_SOURCE_CACHE_DIR}" RESULT_VARIABLE ret
)
assert_equal(${ret} "0")

View File

@@ -0,0 +1,2 @@
/CMakeLists.txt
/package-lock.cmake

View File

@@ -0,0 +1,38 @@
# ~~~
# ┌────────────────────────┐
# │ FetchContentDependency │
# └─────┬────────────┬─────┘
# │1. │3.
# │ │
# ┌────────▼────┐ ┌───▼─────────┐
# │ Dependency ├───► Fibonacci │
# └─────────────┘2. └─────────────┘
#
# 1. Add Project with CPMAddPackage
# 2. Dependency will add Fibonacci with FetchContent
# 3. Our project add Fibonacci with CPMAddPackage
# ~~~
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTest_FetchContentDependency)
# ---- Dependencies ----
include(@CPM_PATH@/CPM.cmake)
# 1 & 2 Dependency will add Fibonacci using FetchContent (1 & 2)
CPMAddPackage(NAME Dependency SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/dependency)
# 3 Add again Fibonacci that have already been populated with FetchContent_MakeAvailable
#
# * This test should highlight the fact that cpm_add_subdirectory is always called, even when
# cpm_fetch_package isn't populating the dependency
# * NO_CACHE YES highlight a bug introduced in 32b063eba5c754f833725ed4b9e5f352bc3ca959 where
# cpm_fetch_package was checking undefined ${lower_case_name}_POPULATED variable
CPMAddPackage(
NAME Fibonacci
GIT_REPOSITORY https://github.com/cpm-cmake/testpack-fibonacci.git
VERSION 2.0
NO_CACHE YES
)

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTest_Dependency)
# ---- Dependencies ----
include(FetchContent)
FetchContent_Declare(
Fibonacci
GIT_REPOSITORY https://github.com/cpm-cmake/testpack-fibonacci.git
GIT_TAG v2.0
)
FetchContent_MakeAvailable(Fibonacci)

View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTest)
# ---- Options ----
option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF)
# ---- Dependencies ----
include(@CPM_PATH@/CPM.cmake)
CPMAddPackage(
NAME Dependency
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/dependency
OPTIONS
"DEFINE_ALTERNATIVE_FUNCTION YES"
"LIST_ARGUMENT a\\\\;b\\\\;c"
EXCLUDE_FROM_ALL YES
)
# ---- Dependencies ----
alternative_dependency_function()
# ---- Check parameters ----
include(@CPM_PATH@/testing.cmake)
message("DEFINE_ALTERNATIVE_FUNCTION: ${DEFINE_ALTERNATIVE_FUNCTION}")
# this option is overridden by CPM.cmake
ASSERT_NOT_DEFINED(DEFINE_ALTERNATIVE_FUNCTION)
# this option is leaked by the dependency
ASSERT_EQUAL(${LEAKED_OPTION} "OFF")

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(CPMTest)
# ---- Dependencies ----
include(@CPM_PATH@/CPM.cmake)
CPMAddPackage(
NAME Dependency
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/dependency
SOURCE_SUBDIR inner
)
# ---- Call inner dependency method to validate correct addition of subdirectory ----
inner_dependency_function()

View File

@@ -1,3 +1,20 @@
function(dependency_function)
message("called external method")
endfunction()
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
option(DEFINE_ALTERNATIVE_FUNCTION "define the alternative function" OFF)
option(LEAKED_OPTION "this option will be leaked to the outer scope" OFF)
if(NOT DEFINE_ALTERNATIVE_FUNCTION)
function(dependency_function)
message("called external method")
endfunction()
else()
# check if list was passed correctly
if(NOT "${LIST_ARGUMENT}" STREQUAL "a;b;c")
message(FATAL_ERROR "list argument not properly passed to dependency: '${LIST_ARGUMENT}'")
endif()
function(alternative_dependency_function)
message("called alternative external method")
endfunction()
endif()

View File

@@ -0,0 +1,3 @@
function(inner_dependency_function)
message("called inner method")
endfunction()

View File

@@ -11,7 +11,7 @@ function(init_project_with_dependency TEST_DEPENDENCY_NAME)
)
execute_process(
COMMAND ${CMAKE_COMMAND} "-H${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
RESULT_VARIABLE ret
)

19
test/unit/options.cmake Normal file
View File

@@ -0,0 +1,19 @@
include(CMakePackageConfigHelpers)
include(${CPM_PATH}/testing.cmake)
set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/source_dir)
set(TEST_DEPENDENCY_NAME Dependency)
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/local_dependency/OptionsCMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/local_dependency/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
execute_process(
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
RESULT_VARIABLE ret
)
assert_equal(${ret} "0")

View File

@@ -24,7 +24,7 @@ function(configure_with_declare DECLARE_DEPENDENCY)
)
execute_process(
COMMAND ${CMAKE_COMMAND} -H${CMAKE_CURRENT_LIST_DIR}/local_dependency -B${TEST_BUILD_DIR}
COMMAND ${CMAKE_COMMAND} -S${CMAKE_CURRENT_LIST_DIR}/local_dependency -B${TEST_BUILD_DIR}
-DCPM_INCLUDE_ALL_IN_PACKAGE_LOCK=1 RESULT_VARIABLE ret
)

View File

@@ -12,7 +12,7 @@ configure_package_config_file(
)
execute_process(
COMMAND ${CMAKE_COMMAND} -H${CMAKE_CURRENT_LIST_DIR}/local_dependency -B${TEST_BUILD_DIR}
COMMAND ${CMAKE_COMMAND} -S${CMAKE_CURRENT_LIST_DIR}/local_dependency -B${TEST_BUILD_DIR}
-DCPM_Dependency_SOURCE=${CMAKE_CURRENT_LIST_DIR}/local_dependency/dependency
RESULT_VARIABLE ret
)

View File

@@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
include(${CPM_PATH}/CPM.cmake)
include(${CPM_PATH}/testing.cmake)
cpm_package_name_and_ver_from_url("https://example.com/coolpack-1.2.3.zip" name ver)
assert_equal("coolpack" ${name})
assert_equal("1.2.3" ${ver})
cpm_package_name_and_ver_from_url("https://example.com/cool-pack-v1.3.tar.gz" name ver)
assert_equal("cool-pack" ${name})
assert_equal("1.3" ${ver})
cpm_package_name_and_ver_from_url(
"https://subd.zip.com/download.php?Cool.Pack-v1.2.3rc0.tar" name ver
)
assert_equal("Cool.Pack" ${name})
assert_equal("1.2.3rc0" ${ver})
cpm_package_name_and_ver_from_url(
"http://evil-1.2.tar.gz.com/Plan9_1.2.3a.tar.bz2?download" name ver
)
assert_equal("Plan9" ${name})
assert_equal("1.2.3a" ${ver})
cpm_package_name_and_ver_from_url(
"http://evil-1.2.tar.gz.com/Plan_9-1.2.3a.tar.bz2?download" name ver
)
assert_equal("Plan_9" ${name})
assert_equal("1.2.3a" ${ver})
cpm_package_name_and_ver_from_url(
"http://evil-1.2.tar.gz.com/Plan-9_1.2.3a.tar.bz2?download" name ver
)
assert_equal("Plan-9" ${name})
assert_equal("1.2.3a" ${ver})
cpm_package_name_and_ver_from_url("https://sf.com/distrib/SFLib-0.999.4.tar.gz/download" name ver)
assert_equal("SFLib" ${name})
assert_equal("0.999.4" ${ver})
cpm_package_name_and_ver_from_url("https://example.com/coolpack/v5.6.5rc44.zip" name ver)
assert_not_defined(name)
assert_equal("5.6.5rc44" ${ver})
cpm_package_name_and_ver_from_url("evil-1.3.zip.com/coolpack/release999.000beta.ZIP" name ver)
assert_not_defined(name)
assert_equal("999.000beta" ${ver})
cpm_package_name_and_ver_from_url("https://example.com/Foo55.tar.gz" name ver)
assert_equal("Foo55" ${name})
assert_not_defined(ver)
cpm_package_name_and_ver_from_url("https://example.com/foo" name ver)
assert_not_defined(name)
assert_not_defined(ver)
cpm_package_name_and_ver_from_url("example.zip.com/foo" name ver)
assert_not_defined(name)
assert_not_defined(ver)

View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
include(${CPM_PATH}/CPM.cmake)
include(${CPM_PATH}/testing.cmake)
cpm_package_name_from_git_uri("https://github.com/cpm-cmake/CPM.cmake.git" name)
assert_equal("CPM.cmake" ${name})
cpm_package_name_from_git_uri("ssh://user@host.xz:123/path/to/pkg.git/" name)
assert_equal("pkg" ${name})
cpm_package_name_from_git_uri("git://host.xz/path/to/pkg.git" name)
assert_equal("pkg" ${name})
cpm_package_name_from_git_uri("git@host.xz:cool-pkg.git" name)
assert_equal("cool-pkg" ${name})
cpm_package_name_from_git_uri("file:///path/to/pkg33.git" name)
assert_equal("pkg33" ${name})
cpm_package_name_from_git_uri("../local-repo/.git" name)
assert_equal("local-repo" ${name})
cpm_package_name_from_git_uri("asdf" name)
assert_not_defined(name)
cpm_package_name_from_git_uri("/something.git/stuff" name)
assert_not_defined(name)

View File

@@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
include(${CPM_PATH}/CPM.cmake)
include(${CPM_PATH}/testing.cmake)
cpm_parse_add_package_single_arg("gh:cpm-cmake/CPM.cmake" args)
assert_equal("GITHUB_REPOSITORY;cpm-cmake/CPM.cmake" "${args}")
cpm_parse_add_package_single_arg("gh:cpm-cmake/CPM.cmake@1.2.3" args)
assert_equal("GITHUB_REPOSITORY;cpm-cmake/CPM.cmake;VERSION;1.2.3" "${args}")
cpm_parse_add_package_single_arg("gh:cpm-cmake/CPM.cmake#master" args)
assert_equal("GITHUB_REPOSITORY;cpm-cmake/CPM.cmake;GIT_TAG;master" "${args}")
cpm_parse_add_package_single_arg("gh:cpm-cmake/CPM.cmake@0.20.3#asdf" args)
assert_equal("GITHUB_REPOSITORY;cpm-cmake/CPM.cmake;VERSION;0.20.3;GIT_TAG;asdf" "${args}")
cpm_parse_add_package_single_arg("gh:a/b#c@d" args)
assert_equal("GITHUB_REPOSITORY;a/b;GIT_TAG;c;VERSION;d" "${args}")
cpm_parse_add_package_single_arg("gh:foo#c@d" args)
assert_equal("GITHUB_REPOSITORY;foo;GIT_TAG;c;VERSION;d" "${args}")
cpm_parse_add_package_single_arg("gh:Foo@5" args)
assert_equal("GITHUB_REPOSITORY;Foo;VERSION;5" "${args}")
cpm_parse_add_package_single_arg("gl:foo/bar" args)
assert_equal("GITLAB_REPOSITORY;foo/bar" "${args}")
cpm_parse_add_package_single_arg("gl:foo/Bar" args)
assert_equal("GITLAB_REPOSITORY;foo/Bar" "${args}")
cpm_parse_add_package_single_arg("bb:foo/bar" args)
assert_equal("BITBUCKET_REPOSITORY;foo/bar" "${args}")
cpm_parse_add_package_single_arg("bb:foo/Bar" args)
assert_equal("BITBUCKET_REPOSITORY;foo/Bar" "${args}")
cpm_parse_add_package_single_arg("https://github.com/cpm-cmake/CPM.cmake.git@0.30.5" args)
assert_equal("GIT_REPOSITORY;https://github.com/cpm-cmake/CPM.cmake.git;VERSION;0.30.5" "${args}")
cpm_parse_add_package_single_arg("git@host.xz:user/pkg.git@0.1.2" args)
assert_equal("GIT_REPOSITORY;git@host.xz:user/pkg.git;VERSION;0.1.2" "${args}")
cpm_parse_add_package_single_arg("git@host.xz:user/pkg.git@0.1.2#rc" args)
assert_equal("GIT_REPOSITORY;git@host.xz:user/pkg.git;VERSION;0.1.2;GIT_TAG;rc" "${args}")
cpm_parse_add_package_single_arg(
"ssh://user@host.xz:123/path/to/pkg.git#fragment@1.2.3#branch" args
)
assert_equal(
"GIT_REPOSITORY;ssh://user@host.xz:123/path/to/pkg.git#fragment;VERSION;1.2.3;GIT_TAG;branch"
"${args}"
)
cpm_parse_add_package_single_arg("https://example.org/foo.tar.gz" args)
assert_equal("URL;https://example.org/foo.tar.gz" "${args}")
cpm_parse_add_package_single_arg("https://example.org/foo.tar.gz#baadf00d@1.2.0" args)
assert_equal("URL;https://example.org/foo.tar.gz;URL_HASH;baadf00d;VERSION;1.2.0" "${args}")
cpm_parse_add_package_single_arg("https://example.org/foo.tar.gz#MD5=baadf00d" args)
assert_equal("URL;https://example.org/foo.tar.gz;URL_HASH;MD5=baadf00d" "${args}")
cpm_parse_add_package_single_arg("https://example.org/Foo.zip#SHA3_512=1337" args)
assert_equal("URL;https://example.org/Foo.zip;URL_HASH;SHA3_512=1337" "${args}")
cpm_parse_add_package_single_arg("ftp://user:pass@server/pathname.zip#fragment#0ddb411@0" args)
assert_equal(
"URL;ftp://user:pass@server/pathname.zip#fragment;URL_HASH;0ddb411;VERSION;0" "${args}"
)

View File

@@ -12,7 +12,7 @@ include(@CPM_PATH@/CPM.cmake)
CPMAddPackage(
NAME fibonacci
GIT_REPOSITORY https://github.com/TheLartians/Fibonacci.git
GIT_REPOSITORY https://github.com/cpm-cmake/testpack-fibonacci.git
VERSION @FIBONACCI_VERSION@
@FIBONACCI_PACKAGE_ARGS@
)

View File

@@ -12,7 +12,7 @@ configure_package_config_file(
)
execute_process(
COMMAND ${CMAKE_COMMAND} "-H${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
RESULT_VARIABLE ret
)

17
test/unit/subdir.cmake Normal file
View File

@@ -0,0 +1,17 @@
include(CMakePackageConfigHelpers)
include(${CPM_PATH}/testing.cmake)
set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/source_dir)
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/local_dependency/SubdirCMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/local_dependency/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
execute_process(
COMMAND ${CMAKE_COMMAND} "-S${CMAKE_CURRENT_LIST_DIR}/local_dependency" "-B${TEST_BUILD_DIR}"
RESULT_VARIABLE ret
)
assert_equal(${ret} "0")