Skip to content

Commit

Permalink
Single-argument shorthand syntax for CPMAddPackage (#207)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update README.md

Co-authored-by: Lars Melchior <[email protected]>

* Removed GitHub as the default package shorthand provider

Co-authored-by: Lars Melchior <[email protected]>
  • Loading branch information
iboB and TheLartians authored Feb 22, 2021
1 parent 4aadac1 commit 3f6cbe7
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 16 deletions.
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ On the other hand, if `VERSION` hasn't been explicitly specified, CPM can automa
`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).

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, or branch name
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 coverted to `https://gitlab.com/user/name.git`. Otherwise the URI used verbatim as a git URL.

After calling `CPMAddPackage` or `CPMFindPackage`, 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.
Expand All @@ -70,10 +83,7 @@ add_executable(tests tests.cpp)
# add dependencies
include(cmake/CPM.cmake)
CPMAddPackage(
GITHUB_REPOSITORY catchorg/Catch2
VERSION 2.5.0
)
CPMAddPackage(gh:catchorg/[email protected])
# link dependencies
target_link_libraries(tests Catch2)
Expand Down Expand Up @@ -244,21 +254,13 @@ See the [wiki](https://github.com/cpm-cmake/CPM.cmake/wiki/More-Snippets) for mo
### [Catch2](https://github.com/catchorg/Catch2)

```cmake
CPMAddPackage(
NAME Catch2
GITHUB_REPOSITORY catchorg/Catch2
VERSION 2.5.0
)
CPMAddPackage(gh:catchorg/[email protected])
```

### [Boost (via boost-cmake)](https://github.com/Orphis/boost-cmake)

```CMake
CPMAddPackage(
NAME boost-cmake
GITHUB_REPOSITORY Orphis/boost-cmake
VERSION 1.67.0
)
CPMAddPackage(gh:Orphis/[email protected])
```

### [cxxopts](https://github.com/jarro2783/cxxopts)
Expand Down
77 changes: 77 additions & 0 deletions cmake/CPM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,85 @@ 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/[email protected] 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")
# 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()
# This error here is temporary. We can't provide URLs from here until we support inferring the
# package name from an url. When this is supported, remove this error as well as commented out
# tests in test/unit/parse_add_package_single_arg.cmake
message(FATAL_ERROR "CPM: Unsupported package type of '${arg}'")

# 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 occurence. 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()

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

# The shorthand syntax implies EXCLUDE_FROM_ALL
set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES")
endif()

set(oneValueArgs
NAME
Expand Down
4 changes: 2 additions & 2 deletions cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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}'")
Expand All @@ -15,7 +15,7 @@ function(ASSERT_NOT_EQUAL)
message(FATAL_ERROR "assertion failed: invalid argument count: ${ARGC}")
endif()

if(${ARGV0} STREQUAL ${ARGV1})
if("${ARGV0}" STREQUAL "${ARGV1}")
message(FATAL_ERROR "assertion failed: '${ARGV0}' == '${ARGV1}'")
else()
message(STATUS "test passed: '${ARGV0}' != '${ARGV1}'")
Expand Down
64 changes: 64 additions & 0 deletions test/unit/parse_add_package_single_arg.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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/[email protected]" 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/[email protected]#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("https://github.com/cpm-cmake/[email protected]" args)
assert_equal("GIT_REPOSITORY;https://github.com/cpm-cmake/CPM.cmake.git;VERSION;0.30.5" "${args}")

cpm_parse_add_package_single_arg("[email protected]:user/[email protected]" args)
assert_equal("GIT_REPOSITORY;[email protected]:user/pkg.git;VERSION;0.1.2" "${args}")

cpm_parse_add_package_single_arg("[email protected]:user/[email protected]#rc" args)
assert_equal("GIT_REPOSITORY;[email protected]:user/pkg.git;VERSION;0.1.2;GIT_TAG;rc" "${args}")

cpm_parse_add_package_single_arg(
"ssh://[email protected]:123/path/to/pkg.git#[email protected]#branch" args
)
assert_equal(
"GIT_REPOSITORY;ssh://[email protected]:123/path/to/pkg.git#fragment;VERSION;1.2.3;GIT_TAG;branch"
"${args}"
)

# The following test cases are to be used in the future, once single-argument archives are supported

# 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#[email protected]" args)

# assert_equal("URL;https://example.org/foo.tar.gz;URL_HASH;baadf00d;VERSION;1.2.0" "${args}")

# cpm_parse_add_package_single_arg("ftp://user:password@server/pathname.zip#fragment#0ddb411@0"
# args)

# assert_equal("URL;ftp://user:password@server/pathname.zip#fragment;URL_HASH;0ddb411;VERSION;0"
# "${args}")

0 comments on commit 3f6cbe7

Please sign in to comment.