diff options
author | Brad King <brad.king@kitware.com> | 2024-06-04 12:59:18 (GMT) |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2024-06-04 12:59:28 (GMT) |
commit | 57c6dd277e38375d9decd1ac4331615775bb2fb1 (patch) | |
tree | 1da7ac24f83dd1f2a8fc82063d3d329a3b3ec5bb | |
parent | d913a22f26b9e527f748d39334a9c435a3146714 (diff) | |
parent | 0f4d3664a94c96a131e1a4aec0698ed7f5f92521 (diff) | |
download | CMake-57c6dd277e38375d9decd1ac4331615775bb2fb1.zip CMake-57c6dd277e38375d9decd1ac4331615775bb2fb1.tar.gz CMake-57c6dd277e38375d9decd1ac4331615775bb2fb1.tar.bz2 |
Merge topic 'cpack-nuget-dependency-groups'
0f4d3664a9 CPack/NuGet: Build dependencies in groups
Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !9544
-rw-r--r-- | Help/cpack_gen/nuget.rst | 77 | ||||
-rw-r--r-- | Help/release/dev/cpack-nuget-dependency-groups.rst | 7 | ||||
-rw-r--r-- | Modules/Internal/CPack/CPackNuGet.cmake | 122 | ||||
-rw-r--r-- | Tests/RunCMake/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Tests/RunCMake/CPack_NuGet/.gitattributes | 3 | ||||
-rw-r--r-- | Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake | 23 | ||||
-rw-r--r-- | Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake | 7 | ||||
-rw-r--r-- | Tests/RunCMake/CPack_NuGet/expected.nuspec | 38 | ||||
-rw-r--r-- | Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt | 34 | ||||
-rw-r--r-- | Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll | 0 | ||||
-rw-r--r-- | Tests/RunCMake/RunCPack/NuGetLib/notactuallyalib.dll | 0 |
11 files changed, 298 insertions, 17 deletions
diff --git a/Help/cpack_gen/nuget.rst b/Help/cpack_gen/nuget.rst index 8ee2816..ad3ec9f 100644 --- a/Help/cpack_gen/nuget.rst +++ b/Help/cpack_gen/nuget.rst @@ -233,7 +233,7 @@ List of CPack NuGet generator specific variables: .. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES CPACK_NUGET_<compName>_PACKAGE_DEPENDENCIES - A list of package dependencies. + A list of default (not framework-specific) package dependencies. :Mandatory: No :Default: None @@ -242,8 +242,44 @@ List of CPack NuGet generator specific variables: CPACK_NUGET_<compName>_PACKAGE_DEPENDENCIES_<dependency>_VERSION A `version specification`_ for the particular dependency, where - ``<dependency>`` is an item of the dependency list (see above) - transformed with :command:`string(MAKE_C_IDENTIFIER)` command. + ``<dependency>`` is an item of the dependency list (see above). + + :Mandatory: No + :Default: None + +.. variable:: CPACK_NUGET_PACKAGE_TFMS + CPACK_NUGET_<compName>_PACKAGE_TFMS + + .. versionadded:: 3.30 + + A list of Target Framework Monikers (TFMs) for the package, e.g., "net47;netcoreapp21". + For each of these TFMs a `dependency group`_ will be generated in the dependencies block of the NuGet + package. Framework-specific dependencies can be added to these groups with the TFM + dependency lists (see below). + + This variable is particularly useful for fixing warnings `NU5128`_. + + :Mandatory: No + :Default: None + +.. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES_<tfm> + CPACK_NUGET_<compName>_PACKAGE_DEPENDENCIES_<tfm> + + .. versionadded:: 3.30 + + A list of package dependencies that apply specifically to the ``<tfm>`` framework, where ``<tfm>`` + is an item from the TFMs list (see above). + + :Mandatory: No + :Default: None + +.. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES_<tfm>_<dependency>_VERSION + CPACK_NUGET_<compName>_PACKAGE_DEPENDENCIES_<tfm>_<dependency>_VERSION + + .. versionadded:: 3.30 + + A `version specification`_ for the particular framework-specific dependency, where + ``<dependency>`` is an item of the ``<tfm>``-specific dependency list (see above). :Mandatory: No :Default: None @@ -256,9 +292,44 @@ List of CPack NuGet generator specific variables: :Default: ``OFF`` +Example usage +^^^^^^^^^^^^^ + +.. code-block:: cmake + + set(CPACK_GENERATOR NuGet) + # Set up package metadata + set(CPACK_PACKAGE_NAME SamplePackage) + set(CPACK_PACKAGE_VERSION "1.0.0") # Why doesn't this pick up the version from the project? + set(CPACK_PACKAGE_VENDOR "Example Inc") + set(CPACK_NUGET_PACKAGE_OWNERS "Example Inc") + set(CPACK_PACKAGE_DESCRIPTION "A .NET wrapper around the foobar library for frobbling bratchens") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A .NET wrapper around the foobar library for frobbling bratchens") + set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.example.com") + set(CPACK_NUGET_PACKAGE_REPOSITORY_URL "https://github.com/example/libfoobar.git") + set(CPACK_NUGET_PACKAGE_REPOSITORY_TYPE git) + set(CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION "MIT") + # Set up dependencies + set(CPACK_NUGET_PACKAGE_TFMS "net4;net6.0") + set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4 "Foo;Bar") + # NB: If a version number is omitted, the dependency will not be created + set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4_Foo_VERSION "1.23") + set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4_Bar_VERSION "4.3.2") + # NB: General dependencies (not framework-specific) go in this variable + set(CPACK_NUGET_PACKAGE_DEPENDENCIES "Baz") + set(CPACK_NUGET_PACKAGE_DEPENDENCIES_Baz_VERSION "9.8.6") + # NB: Since "net6.0" was listed but no dependencies have been specified, an empty group + # will be added to the nuspec file for this framework. This can be used to address `NU5128`_. + + include(CPack) + + + .. _nuget.org: https://www.nuget.org .. _version specification: https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges .. _SPDX license identifier: https://spdx.org/licenses .. _SPDX specification: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions +.. _dependency group: https://learn.microsoft.com/en-us/nuget/reference/nuspec#dependency-groups +.. _NU5128: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu5128 .. NuGet spec docs https://docs.microsoft.com/en-us/nuget/reference/nuspec diff --git a/Help/release/dev/cpack-nuget-dependency-groups.rst b/Help/release/dev/cpack-nuget-dependency-groups.rst new file mode 100644 index 0000000..37944f7 --- /dev/null +++ b/Help/release/dev/cpack-nuget-dependency-groups.rst @@ -0,0 +1,7 @@ +cpack-nuget-dependency-groups +----------------------------- + +* The :cpack_gen:`CPack NuGet Generator` can now generate dependency groups + for framework-specific dependencies. The :variable:`CPACK_NUGET_PACKAGE_TFMS` + was added to specify a list of framework TFMs for which groups should be + generated. diff --git a/Modules/Internal/CPack/CPackNuGet.cmake b/Modules/Internal/CPack/CPackNuGet.cmake index 67f318a..bfd3ce5 100644 --- a/Modules/Internal/CPack/CPackNuGet.cmake +++ b/Modules/Internal/CPack/CPackNuGet.cmake @@ -316,38 +316,132 @@ function(_cpack_nuget_render_spec) endif() # Handle dependencies - _cpack_nuget_variable_fallback(_deps DEPENDENCIES) + # Primary deps (not specific to any framework) + _cpack_nuget_render_deps_group("" rendered_group) + string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG "${rendered_group}") + + # Framework-specific deps + _cpack_nuget_variable_fallback(_tfms TFMS) + foreach(tfm IN LISTS _tfms) + _cpack_nuget_render_deps_group("${tfm}" rendered_group) + string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG "${rendered_group}") + endforeach() + + # If there are any dependencies to include, wrap them with the appropriate tag + if(_CPACK_NUGET_DEPENDENCIES_TAG) + string(PREPEND _CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n") + string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG " </dependencies>") + endif() + + # Render the spec file + # NOTE The spec filename doesn't matter. Being included into a package, + # NuGet will name it properly. + _cpack_nuget_debug("Rendering `${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec` file...") + configure_file( + "${CMAKE_ROOT}/Modules/Internal/CPack/CPack.NuGet.nuspec.in" + "${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec" + @ONLY + ) +endfunction() + +# Call this function once for each TWFM (e.g., 'net48') to generate the dependencies for +# that framework. It can also be called with an empty TWFM for the "general" set of +# dependencies, which are not specific to a framework. +function(_cpack_nuget_render_deps_group TFM OUTPUT_VAR_NAME) + _cpack_nuget_debug(" rendering deps for ${TFM}") + if(TFM) + set(_tfm "_${TFM}") + else() + set(_tfm "") + endif() + _cpack_nuget_variable_fallback(_deps DEPENDENCIES${_tfm}) set(_collected_deps) foreach(_dep IN LISTS _deps) + set(_ver) # Ensure we don't accidentally use the version from the previous dep in the list _cpack_nuget_debug(" checking dependency `${_dep}`") - _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep}_VERSION) + _cpack_nuget_variable_fallback(_ver DEPENDENCIES${_tfm}_${_dep}_VERSION) if(NOT _ver) string(TOUPPER "${_dep}" _dep_upper) - _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep_upper}_VERSION) + _cpack_nuget_variable_fallback(_ver DEPENDENCIES${_tfm}_${_dep_upper}_VERSION) endif() if(_ver) _cpack_nuget_debug(" got `${_dep}` dependency version ${_ver}") - string(CONCAT _collected_deps "${_collected_deps}" " <dependency id=\"${_dep}\" version=\"${_ver}\" />\n") + string(APPEND _collected_deps " <dependency id=\"${_dep}\" version=\"${_ver}\" />\n") endif() endforeach() # Render deps into the variable + if(TFM) + _cpack_nuget_convert_tfm_to_frameworkname("${TFM}" framework_name) + _cpack_nuget_debug(" converted ${TFM} to ${framework_name}") + endif() + set(rendered_group) if(_collected_deps) - string(CONCAT _CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n" "${_collected_deps}" " </dependencies>") + if(TFM) + _cpack_nuget_debug(" rendering group for framework ${framework_name}") + string(CONCAT rendered_group " <group targetFramework=\"${framework_name}\">\n" "${_collected_deps}" " </group>\n") + else() + _cpack_nuget_debug(" rendering primary group") + string(CONCAT rendered_group " <group>\n" "${_collected_deps}" " </group>\n") + endif() + elseif(TFM) + _cpack_nuget_debug(" no deps for ${TFM}, rendering empty group") + # Insert an empty group for a framework that doesn't have any specific dependencies listed, as the existence + # of this group can be used by NuGet to see that the framework is supported. + string(CONCAT rendered_group " <group targetFramework=\"${framework_name}\" />\n") endif() + set(${OUTPUT_VAR_NAME} "${rendered_group}" PARENT_SCOPE) +endfunction() - # Render the spec file - # NOTE The spec filename doesn't matter. Being included into a package, - # NuGet will name it properly. - _cpack_nuget_debug("Rendering `${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec` file...") - configure_file( - "${CMAKE_ROOT}/Modules/Internal/CPack/CPack.NuGet.nuspec.in" - "${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec" - @ONLY - ) +# Tries to look up a Framework Name (e.g., '.NETFramework4.8') from a Target Framework Moniker (TFM) (e.g., 'net48') +function(_cpack_nuget_convert_tfm_to_frameworkname TFM OUTPUT_VAR_NAME) + # There are a few patterns to handle: + # 1a. net4 -> .NETFramework4 + # 1b. net5.0 -> net5.0 From version 5 onwards, the name just looks the same as the moniker, and both need to have a dot + # 2. netstandard13 -> .NETStandard1.3 + # 3. netcoreapp21 -> .NETCoreApp2.1 + # 4. dotnet50 -> .NETPlatform5.0 + if(TFM MATCHES "^net([1-4](.[\.0-9])?)$") # CMAKE_MATCH_1 holds the version part + _cpack_nuget_get_dotted_version("${CMAKE_MATCH_1}" dotted_version) + set(framework_name ".NETFramework${dotted_version}") + elseif(TFM MATCHES "^net[1-9](\.[0-9]+)+$") + set(framework_name "${TFM}") + elseif(TFM MATCHES "^netstandard([0-9]+(\.[0-9]+)*)$") + _cpack_nuget_get_dotted_version("${CMAKE_MATCH_1}" dotted_version) + set(framework_name ".NETStandard${dotted_version}") + elseif(TFM MATCHES "^netcoreapp([0-9]+(\.[0-9]+)*)$") + _cpack_nuget_get_dotted_version("${CMAKE_MATCH_1}" dotted_version) + set(framework_name ".NETCoreApp${dotted_version}") + elseif(TFM MATCHES "^dotnet([0-9]+(\.[0-9]+)*)$") + _cpack_nuget_get_dotted_version("${CMAKE_MATCH_1}" dotted_version) + set(framework_name ".NETPlatform${dotted_version}") + else() + message(FATAL_ERROR "Target Framework Moniker '${TFM}' not recognized") + endif() + set(${OUTPUT_VAR_NAME} ${framework_name} PARENT_SCOPE) +endfunction() + +function(_cpack_nuget_get_dotted_version VERSION OUTPUT_VAR_NAME) + if(VERSION MATCHES "\.") + # The version already has dots in it, just reuse the numbers given + set(dotted_version "${VERSION}") + else() + # No dots in the version, treat each digit as a version part + string(LENGTH "${VERSION}" length) + math(EXPR last_index "${length} - 1") + string(SUBSTRING "${VERSION}" 0 1 digit) + set(dotted_version "${digit}") + foreach(i RANGE 1 ${last_index}) + string(SUBSTRING "${VERSION}" ${i} 1 digit) + string(APPEND dotted_version ".${digit}") + endforeach() + endif() + # This would be a good place to remove any superfluous ".0"s from the end of the version string, but + # for now it should be fine to just expect the caller not to supply them in the first place. + set(${OUTPUT_VAR_NAME} "${dotted_version}" PARENT_SCOPE) endfunction() function(_cpack_nuget_make_files_tag) diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 94382fc..e205d9f 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -1141,6 +1141,10 @@ if(CMake_TEST_CPACK_WIX3 OR CMake_TEST_CPACK_WIX4) ) endif() +if(CMake_TEST_CPACK_NUGET) + add_RunCMake_test(CPack_NuGet) +endif() + # add a test to make sure symbols are exported from a shared library # for MSVC compilers CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS property is used add_RunCMake_test(AutoExportDll diff --git a/Tests/RunCMake/CPack_NuGet/.gitattributes b/Tests/RunCMake/CPack_NuGet/.gitattributes new file mode 100644 index 0000000..4a8bbd0 --- /dev/null +++ b/Tests/RunCMake/CPack_NuGet/.gitattributes @@ -0,0 +1,3 @@ +# Do not check whitespace in the nuspec file for comparison. It needs to +# look identical to the file generated in the test. +expected.nuspec -whitespace diff --git a/Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake b/Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake new file mode 100644 index 0000000..e83f007 --- /dev/null +++ b/Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake @@ -0,0 +1,23 @@ +file(GLOB generated_nuspec "${RunCMake_TEST_BINARY_DIR}/_CPack_Packages/*/NuGet/GeneratorTest-1.2.3-*/CPack.NuGet.nuspec") +if(NOT generated_nuspec) + set(RunCMake_TEST_FAILED "No nuspec file generated under ${RunCMake_TEST_BINARY_DIR}") +else() + # Read in the generated nuspec file content + file(READ "${generated_nuspec}" actual_nuspec) + # Read in the expected file content + file(READ "${CMAKE_CURRENT_LIST_DIR}/expected.nuspec" expected_nuspec) + + # Compare the file contents + string(COMPARE EQUAL "${actual_nuspec}" "${expected_nuspec}" nuspec_matches) + + if(NOT nuspec_matches) + set(RunCMake_TEST_FAILED "generated nuspec file incorrect") + set(failure_msg "") + # This would be nicer with a `diff` output, but it needs to be portable + string(APPEND failure_msg "\nExpected file:\n") + string(APPEND failure_msg "${expected_nuspec}") + string(APPEND failure_msg "Actual file:\n") + string(APPEND failure_msg "${actual_nuspec}") + set(RunCMake_TEST_FAILURE_MESSAGE "${failure_msg}") + endif() +endif() diff --git a/Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake b/Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake new file mode 100644 index 0000000..af8ad31 --- /dev/null +++ b/Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake @@ -0,0 +1,7 @@ +include(RunCPack) + +set(env_PATH "$ENV{PATH}") + +set(RunCPack_GENERATORS NuGet) + +run_cpack(NuGetLib) diff --git a/Tests/RunCMake/CPack_NuGet/expected.nuspec b/Tests/RunCMake/CPack_NuGet/expected.nuspec new file mode 100644 index 0000000..d5c6c45 --- /dev/null +++ b/Tests/RunCMake/CPack_NuGet/expected.nuspec @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> + <metadata> + <!-- Required elements--> + <id>GeneratorTest</id> + <version>1.2.3</version> + <description><![CDATA[A NuGet package for testing CMake's CPack NuGet generator]]></description> + <authors><![CDATA[ACME Inc]]></authors> + + <!-- Optional elements --> + + <owners><![CDATA[ACME Inc]]></owners> + <projectUrl><![CDATA[https://www.example.com]]></projectUrl> + + <license type="expression"><![CDATA[MIT]]></license> + + + + + <summary><![CDATA[A test NuGet package]]></summary> + + + + + <repository type="git" url="https://github.com/example/nugetlib.git" /> + <dependencies> + <group> + <dependency id="Baz" version="9.8.6" /> + </group> + <group targetFramework=".NETFramework4"> + <dependency id="Foo" version="1.23" /> + <dependency id="Bar" version="4.3.2" /> + </group> + <group targetFramework="net6.0" /> + </dependencies> + </metadata> + +</package> diff --git a/Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt b/Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt new file mode 100644 index 0000000..55ab638 --- /dev/null +++ b/Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt @@ -0,0 +1,34 @@ +# Support for framework-specific dependencies were introduced in 3.30 +# TODO: update this version req to 3.30 +cmake_minimum_required(VERSION 3.29.20240531) +project(CPackNugetGenerator) + +install(FILES notactuallyalib.dll DESTINATION lib/net4) +install(FILES alsonotalib.dll DESTINATION lib/net6.0) + +# Create NuGet package +set(CPACK_GENERATOR NuGet) +set(CPACK_PACKAGE_NAME GeneratorTest) +set(CPACK_PACKAGE_VERSION "1.2.3") +set(CPACK_PACKAGE_VENDOR "ACME Inc") +set(CPACK_NUGET_PACKAGE_OWNERS "ACME Inc") +set(CPACK_PACKAGE_DESCRIPTION "A NuGet package for testing CMake's CPack NuGet generator") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A test NuGet package") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.example.com") +set(CPACK_NUGET_PACKAGE_REPOSITORY_URL "https://github.com/example/nugetlib.git") +set(CPACK_NUGET_PACKAGE_REPOSITORY_TYPE git) +set(CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION "MIT") + +# Set up dependencies +set(CPACK_NUGET_PACKAGE_TFMS "net4;net6.0") +set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4 "Foo;Bar") +# NB: If a version number is omitted, the dependency will not be created +set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4_Foo_VERSION "1.23") +set(CPACK_NUGET_PACKAGE_DEPENDENCIES_net4_Bar_VERSION "4.3.2") +# NB: General dependencies (not framework-specific) go in this variable +set(CPACK_NUGET_PACKAGE_DEPENDENCIES "Baz") +set(CPACK_NUGET_PACKAGE_DEPENDENCIES_Baz_VERSION "9.8.6") +# NB: Since "net6.0" was listed but no dependencies have been specified, an empty group +# will be added to the nuspec file for this framework. This can be used to address `NU5128`_. + +include(CPack) diff --git a/Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll b/Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll diff --git a/Tests/RunCMake/RunCPack/NuGetLib/notactuallyalib.dll b/Tests/RunCMake/RunCPack/NuGetLib/notactuallyalib.dll new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Tests/RunCMake/RunCPack/NuGetLib/notactuallyalib.dll |