CPack/NuGet: Build dependencies in groups
This allows particular frameworks to be targeted by particular dependencies, and for empty dependency groups to satisfy NuGet's framework-matching requirements.
This commit is contained in:
parent
140766867b
commit
0f4d3664a9
@ -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
|
||||
|
7
Help/release/dev/cpack-nuget-dependency-groups.rst
Normal file
7
Help/release/dev/cpack-nuget-dependency-groups.rst
Normal file
@ -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.
|
@ -316,27 +316,21 @@ function(_cpack_nuget_render_spec)
|
||||
endif()
|
||||
|
||||
# Handle dependencies
|
||||
_cpack_nuget_variable_fallback(_deps DEPENDENCIES)
|
||||
set(_collected_deps)
|
||||
foreach(_dep IN LISTS _deps)
|
||||
_cpack_nuget_debug(" checking dependency `${_dep}`")
|
||||
# Primary deps (not specific to any framework)
|
||||
_cpack_nuget_render_deps_group("" rendered_group)
|
||||
string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG "${rendered_group}")
|
||||
|
||||
_cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep}_VERSION)
|
||||
|
||||
if(NOT _ver)
|
||||
string(TOUPPER "${_dep}" _dep_upper)
|
||||
_cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_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")
|
||||
endif()
|
||||
# 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()
|
||||
|
||||
# Render deps into the variable
|
||||
if(_collected_deps)
|
||||
string(CONCAT _CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n" "${_collected_deps}" " </dependencies>")
|
||||
# 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
|
||||
@ -350,6 +344,106 @@ function(_cpack_nuget_render_spec)
|
||||
)
|
||||
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${_tfm}_${_dep}_VERSION)
|
||||
|
||||
if(NOT _ver)
|
||||
string(TOUPPER "${_dep}" _dep_upper)
|
||||
_cpack_nuget_variable_fallback(_ver DEPENDENCIES${_tfm}_${_dep_upper}_VERSION)
|
||||
endif()
|
||||
|
||||
if(_ver)
|
||||
_cpack_nuget_debug(" got `${_dep}` dependency version ${_ver}")
|
||||
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)
|
||||
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()
|
||||
|
||||
# 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)
|
||||
set(_files)
|
||||
foreach(_comp IN LISTS ARGN)
|
||||
|
@ -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
|
||||
|
3
Tests/RunCMake/CPack_NuGet/.gitattributes
vendored
Normal file
3
Tests/RunCMake/CPack_NuGet/.gitattributes
vendored
Normal file
@ -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
|
23
Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake
Normal file
23
Tests/RunCMake/CPack_NuGet/NuGetLib-cpack-NuGet-check.cmake
Normal file
@ -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()
|
7
Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake
Normal file
7
Tests/RunCMake/CPack_NuGet/RunCMakeTest.cmake
Normal file
@ -0,0 +1,7 @@
|
||||
include(RunCPack)
|
||||
|
||||
set(env_PATH "$ENV{PATH}")
|
||||
|
||||
set(RunCPack_GENERATORS NuGet)
|
||||
|
||||
run_cpack(NuGetLib)
|
38
Tests/RunCMake/CPack_NuGet/expected.nuspec
Normal file
38
Tests/RunCMake/CPack_NuGet/expected.nuspec
Normal file
@ -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>
|
34
Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt
Normal file
34
Tests/RunCMake/RunCPack/NuGetLib/CMakeLists.txt
Normal file
@ -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)
|
0
Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll
Normal file
0
Tests/RunCMake/RunCPack/NuGetLib/alsonotalib.dll
Normal file
Loading…
Reference in New Issue
Block a user