diff --git a/README.md b/README.md index ab28dbc..4d7d4e1 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,17 @@ 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. 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` 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. diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 555c646..9e81789 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -134,7 +134,7 @@ endif() include(FetchContent) include(CMakeParseArguments) -# Infer package name from git repository uri (path or url) +# 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} @@ -146,6 +146,52 @@ function(cpm_package_name_from_git_uri URI RESULT) 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 - (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) set(CPM_INDENT @@ -274,11 +320,6 @@ function(cpm_parse_add_package_single_arg arg outArgs) 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") @@ -349,7 +390,7 @@ function(CPMAddPackage) EXCLUDE_FROM_ALL ) - set(multiValueArgs OPTIONS) + set(multiValueArgs URL OPTIONS) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") @@ -397,6 +438,24 @@ 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) diff --git a/test/unit/package_name_and_ver_from_url.cmake b/test/unit/package_name_and_ver_from_url.cmake new file mode 100644 index 0000000..beb1901 --- /dev/null +++ b/test/unit/package_name_and_ver_from_url.cmake @@ -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) diff --git a/test/unit/parse_add_package_single_arg.cmake b/test/unit/parse_add_package_single_arg.cmake index 8d452e0..62c8b52 100644 --- a/test/unit/parse_add_package_single_arg.cmake +++ b/test/unit/parse_add_package_single_arg.cmake @@ -47,18 +47,19 @@ assert_equal( "${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" 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}") -# assert_equal("URL;https://example.org/foo.tar.gz" "${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.tar.gz#baadf00d@1.2.0" 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}") -# 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}") +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}" +)