From 3f6cbe73830d25dc552c385fd0a6e87846b31cd8 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Mon, 22 Feb 2021 22:12:06 +0200 Subject: [PATCH] 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 * Update README.md Co-authored-by: Lars Melchior * Removed GitHub as the default package shorthand provider Co-authored-by: Lars Melchior --- README.md | 30 ++++---- cmake/CPM.cmake | 77 ++++++++++++++++++++ cmake/testing.cmake | 4 +- test/unit/parse_add_package_single_arg.cmake | 64 ++++++++++++++++ 4 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 test/unit/parse_add_package_single_arg.cmake diff --git a/README.md b/README.md index cdad0b3a..c8def483 100644 --- a/README.md +++ b/README.md @@ -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 `` is the name of the dependency. - `_SOURCE_DIR` is the path to the source of the dependency. @@ -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/Catch2@2.5.0) # link dependencies target_link_libraries(tests Catch2) @@ -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/Catch2@2.5.0) ``` ### [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/boost-cmake@1.67.0) ``` ### [cxxopts](https://github.com/jarro2783/cxxopts) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 29a0a53d..555c6465 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -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/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") + # 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 diff --git a/cmake/testing.cmake b/cmake/testing.cmake index dee389a6..7ae26f1e 100644 --- a/cmake/testing.cmake +++ b/cmake/testing.cmake @@ -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}'") @@ -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}'") diff --git a/test/unit/parse_add_package_single_arg.cmake b/test/unit/parse_add_package_single_arg.cmake new file mode 100644 index 00000000..8d452e02 --- /dev/null +++ b/test/unit/parse_add_package_single_arg.cmake @@ -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/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("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}" +) + +# 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#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("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}")