Browse Source

CPack: Add NuGet support

Create a CPack generator that uses `nuget.exe` to create packages:

    https://docs.microsoft.com/en-us/nuget/what-is-nuget

NuGet packages could be easily produced from a `*.nuspec` file (running
`nuget pack` in the directory w/ the spec file).  The spec filename does
not affect the result `*.nupkg` name -- only `id` and `version` elements
of the spec are used (by NuGet).

Some implementation details:

* Minimize C++ code -- use CMake script do to the job. It just let the
  base class (`cmCPackGenerator`) to preinstall everything to a temp
  directory, render the spec file and run `nuget pack` in it, harvesting
  `*.nupkg` files...;

* Ignore package name (and use default paths) prepared by the base class
  (only `CPACK_TEMPORARY_DIRECTORY` is important) -- final package
  filename is a responsibility of NuGet, so after generation just scan the
  temp directory for the result `*.nupkg` file(s) and update
  `packageFileNames` data-member of the generator;

* The generator supports _all-in-one_ (default), _one-group-per-package_
  and _one-component-per-package_ modes.
Alex Turbov 7 years ago
parent
commit
f739752ad6

+ 1 - 0
Help/manual/cmake-modules.7.rst

@@ -64,6 +64,7 @@ All Modules
    /module/CPackIFW
    /module/CPackIFWConfigureFile
    /module/CPackNSIS
+   /module/CPackNuGet
    /module/CPackPackageMaker
    /module/CPackProductBuild
    /module/CPackRPM

+ 1 - 0
Help/module/CPackNuGet.rst

@@ -0,0 +1 @@
+.. cmake-module:: ../../Modules/CPackNuGet.cmake

+ 7 - 0
Help/release/dev/cpack-nuget.rst

@@ -0,0 +1,7 @@
+cpack-nuget
+-----------
+
+* :manual:`cpack(1)` gained basic support for `NuGet`_.
+  See the :module:`CPackNuGet` module.
+
+.. _NuGet: https://docs.microsoft.com/en-us/nuget/what-is-nuget

+ 24 - 0
Modules/CPack.NuGet.nuspec.in

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
+    <metadata>
+        <!-- Required elements-->
+        <id>@CPACK_NUGET_PACKAGE_NAME@</id>
+        <version>@CPACK_NUGET_PACKAGE_VERSION@</version>
+        <description>@CPACK_NUGET_PACKAGE_DESCRIPTION@</description>
+        <authors>@CPACK_NUGET_PACKAGE_AUTHORS@</authors>
+
+        <!-- Optional elements -->
+        @_CPACK_NUGET_TITLE_TAG@
+        @_CPACK_NUGET_OWNERS_TAG@
+        @_CPACK_NUGET_PROJECTURL_TAG@
+        @_CPACK_NUGET_LICENSEURL_TAG@
+        @_CPACK_NUGET_ICONURL_TAG@
+        @_CPACK_NUGET_REQUIRELICENSEACCEPTANCE_TAG@
+        @_CPACK_NUGET_SUMMARY_TAG@
+        @_CPACK_NUGET_RELEASENOTES_TAG@
+        @_CPACK_NUGET_COPYRIGHT_TAG@
+        @_CPACK_NUGET_TAGS_TAG@
+        @_CPACK_NUGET_DEPENDENCIES_TAG@
+    </metadata>
+    @_CPACK_NUGET_FILES_TAG@
+</package>

+ 7 - 4
Modules/CPack.cmake

@@ -544,10 +544,11 @@ if(NOT CPACK_GENERATOR)
       option(CPACK_BINARY_TXZ  "Enable to build TXZ packages"     OFF)
     endif()
   else()
-    option(CPACK_BINARY_7Z   "Enable to build 7-Zip packages" OFF)
-    option(CPACK_BINARY_NSIS "Enable to build NSIS packages" ON)
-    option(CPACK_BINARY_WIX  "Enable to build WiX packages" OFF)
-    option(CPACK_BINARY_ZIP  "Enable to build ZIP packages" OFF)
+    option(CPACK_BINARY_7Z    "Enable to build 7-Zip packages" OFF)
+    option(CPACK_BINARY_NSIS  "Enable to build NSIS packages" ON)
+    option(CPACK_BINARY_NUGET "Enable to build NuGet packages" OFF)
+    option(CPACK_BINARY_WIX   "Enable to build WiX packages" OFF)
+    option(CPACK_BINARY_ZIP   "Enable to build ZIP packages" OFF)
   endif()
   option(CPACK_BINARY_IFW "Enable to build IFW packages" OFF)
 
@@ -559,6 +560,7 @@ if(NOT CPACK_GENERATOR)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_FREEBSD      FREEBSD)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_IFW          IFW)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NSIS         NSIS)
+  cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NUGET        NuGet)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_OSXX11       OSXX11)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_PACKAGEMAKER PackageMaker)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_PRODUCTBUILD productbuild)
@@ -611,6 +613,7 @@ mark_as_advanced(
   CPACK_BINARY_FREEBSD
   CPACK_BINARY_IFW
   CPACK_BINARY_NSIS
+  CPACK_BINARY_NUGET
   CPACK_BINARY_OSXX11
   CPACK_BINARY_PACKAGEMAKER
   CPACK_BINARY_PRODUCTBUILD

+ 556 - 0
Modules/CPackNuGet.cmake

@@ -0,0 +1,556 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+CPackNuGet
+----------
+
+When build a NuGet pacakge there is no direct way to control an output
+filename due a lack of the corresponding CLI option of NuGet, so there
+is no ``CPACK_NUGET_PACKAGE_FILENAME`` variable. To form the output filename
+NuGet uses the package name and the version according to its built-in rules.
+
+Also, be aware that including a top level directory
+(``CPACK_INCLUDE_TOPLEVEL_DIRECTORY``) is ignored by this generator.
+
+
+Variables specific to CPack NuGet generator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+CPackNuGet may be used to create NuGet packages using :module:`CPack`.
+CPackNuGet is a :module:`CPack` generator thus it uses the ``CPACK_XXX``
+variables used by :module:`CPack`.
+
+CPackNuGet has specific features which are controlled by the specifics
+:code:`CPACK_NUGET_XXX` variables. In the "one per group" mode
+(see :variable:`CPACK_COMPONENTS_GROUPING`), ``<compName>`` placeholder
+in the variables below would contain a group name (uppercased and turned into
+a "C" identifier).
+
+List of CPackNuGet specific variables:
+
+.. variable:: CPACK_NUGET_COMPONENT_INSTALL
+
+ Enable component packaging for CPackNuGet
+
+ * Mandatory : NO
+ * Default   : OFF
+
+.. variable:: CPACK_NUGET_PACKAGE_NAME
+              CPACK_NUGET_<compName>_PACKAGE_NAME
+
+ The NUGET package name.
+
+ * Mandatory : YES
+ * Default   : :variable:`CPACK_PACKAGE_NAME`
+
+.. variable:: CPACK_NUGET_PACKAGE_VERSION
+              CPACK_NUGET_<compName>_PACKAGE_VERSION
+
+ The NuGet package version.
+
+ * Mandatory : YES
+ * Default   : :variable:`CPACK_PACKAGE_VERSION`
+
+.. variable:: CPACK_NUGET_PACKAGE_DESCRIPTION
+              CPACK_NUGET_<compName>_PACKAGE_DESCRIPTION
+
+ A long description of the package for UI display.
+
+ * Mandatory : YES
+ * Default   :
+    - :variable:`CPACK_COMPONENT_<compName>_DESCRIPTION`,
+    - ``CPACK_COMPONENT_GROUP_<groupName>_DESCRIPTION``,
+    - :variable:`CPACK_PACKAGE_DESCRIPTION`
+
+ .. variable:: CPACK_NUGET_PACKAGE_AUTHORS
+               CPACK_NUGET_<compName>_PACKAGE_AUTHORS
+
+ A comma-separated list of packages authors, matching the profile names
+ on nuget.org_. These are displayed in the NuGet Gallery on
+ nuget.org_ and are used to cross-reference packages by the same
+ authors.
+
+ * Mandatory : YES
+ * Default   : :variable:`CPACK_PACKAGE_VENDOR`
+
+.. variable:: CPACK_NUGET_PACKAGE_TITLE
+              CPACK_NUGET_<compName>_PACKAGE_TITLE
+
+ A human-friendly title of the package, typically used in UI displays
+ as on nuget.org_ and the Package Manager in Visual Studio. If not
+ specified, the package ID is used.
+
+ * Mandatory : NO
+ * Default   :
+    - :variable:`CPACK_COMPONENT_<compName>_DISPLAY_NAME`,
+    - ``CPACK_COMPONENT_GROUP_<groupName>_DISPLAY_NAME``
+
+.. variable:: CPACK_NUGET_PACKAGE_OWNERS
+              CPACK_NUGET_<compName>_PACKAGE_OWNERS
+
+ A comma-separated list of the package creators using profile names
+ on nuget.org_. This is often the same list as in authors,
+ and is ignored when uploading the package to nuget.org_.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_HOMEPAGE_URL
+              CPACK_NUGET_<compName>_PACKAGE_HOMEPAGE_URL
+
+ A URL for the package's home page, often shown in UI displays as well
+ as nuget.org_.
+
+ * Mandatory : NO
+ * Default   : :variable:`CPACK_PACKAGE_HOMEPAGE_URL`
+
+.. variable:: CPACK_NUGET_PACKAGE_LICENSEURL
+              CPACK_NUGET_<compName>_PACKAGE_LICENSEURL
+
+ A URL for the package's license, often shown in UI displays as well
+ as nuget.org_.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_ICONURL
+              CPACK_NUGET_<compName>_PACKAGE_ICONURL
+
+ A URL for a 64x64 image with transparency background to use as the
+ icon for the package in UI display.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_DESCRIPTION_SUMMARY
+              CPACK_NUGET_<compName>_PACKAGE_DESCRIPTION_SUMMARY
+
+ A short description of the package for UI display. If omitted, a
+ truncated version of description is used.
+
+ * Mandatory : NO
+ * Default   : :variable:`CPACK_PACKAGE_DESCRIPTION_SUMMARY`
+
+.. variable:: CPACK_NUGET_PACKAGE_RELEASE_NOTES
+              CPACK_NUGET_<compName>_PACKAGE_RELEASE_NOTES
+
+ A description of the changes made in this release of the package,
+ often used in UI like the Updates tab of the Visual Studio Package
+ Manager in place of the package description.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_COPYRIGHT
+              CPACK_NUGET_<compName>_PACKAGE_COPYRIGHT
+
+ Copyright details for the package.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_TAGS
+              CPACK_NUGET_<compName>_PACKAGE_TAGS
+
+ A space-delimited list of tags and keywords that describe the
+ package and aid discoverability of packages through search and
+ filtering.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES
+              CPACK_NUGET_<compName>_PACKAGE_DEPENDENCIES
+
+ A list of package dependencies.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES_<dependency>_VERSION
+              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 ``MAKE_C_IDENTIFIER`` function of :command:`string`
+ command.
+
+ * Mandatory : NO
+ * Default   : -
+
+.. variable:: CPACK_NUGET_PACKAGE_DEBUG
+
+ Enable debug messages while executing ``CPackNuGet.cmake``.
+
+ * Mandatory : NO
+ * Default   : OFF
+
+
+.. _nuget.org: http://nuget.org
+.. _version specification: https://docs.microsoft.com/en-us/nuget/reference/package-versioning#version-ranges-and-wildcards
+
+.. NuGet spec docs https://docs.microsoft.com/en-us/nuget/reference/nuspec
+
+#]=======================================================================]
+
+# Author: Alex Turbov
+
+if(CMAKE_BINARY_DIR)
+  message(FATAL_ERROR "CPackNuGet.cmake may only be used by CPack internally.")
+endif()
+
+function(_cpack_nuget_debug)
+    if(CPACK_NUGET_PACKAGE_DEBUG)
+        message("CPackNuGet:Debug: " ${ARGN})
+    endif()
+endfunction()
+
+function(_cpack_nuget_debug_var NAME)
+    if(CPACK_NUGET_PACKAGE_DEBUG)
+        message("CPackNuGet:Debug: ${NAME}=`${${NAME}}`")
+    endif()
+endfunction()
+
+function(_cpack_nuget_variable_fallback OUTPUT_VAR_NAME NUGET_VAR_NAME)
+    if(ARGN)
+        list(JOIN ARGN "`, `" _va_args)
+        set(_va_args ", ARGN: `${_va_args}`")
+    endif()
+    _cpack_nuget_debug(
+        "_cpack_nuget_variable_fallback: "
+        "OUTPUT_VAR_NAME=`${OUTPUT_VAR_NAME}`, "
+        "NUGET_VAR_NAME=`${NUGET_VAR_NAME}`"
+        "${_va_args}"
+      )
+
+    set(_options USE_CDATA)
+    set(_one_value_args LIST_GLUE)
+    set(_multi_value_args FALLBACK_VARS)
+    cmake_parse_arguments(PARSE_ARGV 0 _args "${_options}" "${_one_value_args}" "${_multi_value_args}")
+
+    if(CPACK_NUGET_PACKAGE_COMPONENT)
+        string(
+            TOUPPER "${CPACK_NUGET_PACKAGE_COMPONENT}"
+            CPACK_NUGET_PACKAGE_COMPONENT_UPPER
+          )
+    endif()
+
+    if(CPACK_NUGET_PACKAGE_COMPONENT
+      AND CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}
+      )
+        set(
+            _result
+            "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}}"
+          )
+        _cpack_nuget_debug(
+            "  CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}: "
+            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
+          )
+
+    elseif(CPACK_NUGET_PACKAGE_COMPONENT_UPPER
+      AND CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}
+      )
+        set(
+            _result
+            "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}}"
+          )
+        _cpack_nuget_debug(
+            "  CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}: "
+            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
+          )
+
+    elseif(CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME})
+        set(_result "${CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME}}")
+        _cpack_nuget_debug(
+            "  CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME}: "
+            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
+          )
+
+    else()
+        foreach(_var IN LISTS _args_FALLBACK_VARS)
+            _cpack_nuget_debug("  Fallback: ${_var} ...")
+            if(${_var})
+                _cpack_nuget_debug("            ${_var}=`${${_var}}`")
+                set(_result "${${_var}}")
+                _cpack_nuget_debug(
+                    "  ${_var}: OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
+                  )
+                break()
+            endif()
+        endforeach()
+    endif()
+
+    if(_result)
+        if(_args_USE_CDATA)
+            set(_value_before "<![CDATA[")
+            set(_value_after "]]>")
+        endif()
+
+        list(LENGTH _result _result_len)
+        if(_result_len GREATER 1 AND _args_LIST_GLUE)
+            list(JOIN _result "${_args_LIST_GLUE}" _result)
+        endif()
+
+        set(${OUTPUT_VAR_NAME} "${_value_before}${_result}${_value_after}" PARENT_SCOPE)
+    endif()
+
+endfunction()
+
+function(_cpack_nuget_variable_fallback_and_wrap_into_element ELEMENT NUGET_VAR_NAME)
+    set(_options)
+    set(_one_value_args)
+    set(_multi_value_args FALLBACK_VARS)
+    cmake_parse_arguments(PARSE_ARGV 0 _args "${_options}" "${_one_value_args}" "${_multi_value_args}")
+
+    _cpack_nuget_variable_fallback(_value ${NUGET_VAR_NAME} ${ARGN} USE_CDATA)
+
+    if(_value)
+        string(TOUPPER "${ELEMENT}" _ELEMENT_UP)
+        set(
+            _CPACK_NUGET_${_ELEMENT_UP}_TAG
+            "<${ELEMENT}>${_value}</${ELEMENT}>"
+            PARENT_SCOPE
+          )
+    endif()
+endfunction()
+
+# Print some debug info
+_cpack_nuget_debug("---[CPack NuGet Input Variables]---")
+_cpack_nuget_debug_var(CPACK_PACKAGE_NAME)
+_cpack_nuget_debug_var(CPACK_PACKAGE_VERSION)
+_cpack_nuget_debug_var(CPACK_TOPLEVEL_TAG)
+_cpack_nuget_debug_var(CPACK_TOPLEVEL_DIRECTORY)
+_cpack_nuget_debug_var(CPACK_TEMPORARY_DIRECTORY)
+_cpack_nuget_debug_var(CPACK_NUGET_GROUPS)
+if(CPACK_NUGET_GROUPS)
+    foreach(_group IN LISTS CPACK_NUGET_GROUPS)
+        string(MAKE_C_IDENTIFIER "${_group}" _group_up)
+        string(TOUPPER "${_group_up}" _group_up)
+        _cpack_nuget_debug_var(CPACK_NUGET_${_group_up}_GROUP_COMPONENTS)
+    endforeach()
+endif()
+_cpack_nuget_debug_var(CPACK_NUGET_COMPONENTS)
+_cpack_nuget_debug_var(CPACK_NUGET_ALL_IN_ONE)
+_cpack_nuget_debug_var(CPACK_NUGET_ORDINAL_MONOLITIC)
+_cpack_nuget_debug("-----------------------------------")
+
+function(_cpack_nuget_render_spec)
+    # Make a variable w/ upper-cased component name
+    if(CPACK_NUGET_PACKAGE_COMPONENT)
+        string(TOUPPER "${CPACK_NUGET_PACKAGE_COMPONENT}" CPACK_NUGET_PACKAGE_COMPONENT_UPPER)
+    endif()
+
+    # Set mandatory variables (not wrapped into XML elements)
+    # https://docs.microsoft.com/en-us/nuget/reference/nuspec#required-metadata-elements
+    if(CPACK_NUGET_PACKAGE_COMPONENT)
+        if(CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_NAME)
+            set(
+                CPACK_NUGET_PACKAGE_NAME
+                "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_NAME}"
+              )
+        elseif(NOT CPACK_NUGET_PACKAGE_COMPONENT STREQUAL "Unspecified")
+            set(
+                CPACK_NUGET_PACKAGE_NAME
+                "${CPACK_PACKAGE_NAME}.${CPACK_NUGET_PACKAGE_COMPONENT}"
+              )
+        else()
+            set(CPACK_NUGET_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
+        endif()
+    elseif(NOT CPACK_NUGET_PACKAGE_NAME)
+        set(CPACK_NUGET_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
+    endif()
+
+    _cpack_nuget_variable_fallback(
+        CPACK_NUGET_PACKAGE_VERSION VERSION
+        FALLBACK_VARS
+            CPACK_PACKAGE_VERSION
+      )
+    _cpack_nuget_variable_fallback(
+        CPACK_NUGET_PACKAGE_DESCRIPTION DESCRIPTION
+        FALLBACK_VARS
+            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT}_DESCRIPTION
+            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DESCRIPTION
+            CPACK_COMPONENT_GROUP_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DESCRIPTION
+            CPACK_PACKAGE_DESCRIPTION
+        USE_CDATA
+      )
+    _cpack_nuget_variable_fallback(
+        CPACK_NUGET_PACKAGE_AUTHORS AUTHORS
+        FALLBACK_VARS
+            CPACK_PACKAGE_VENDOR
+        USE_CDATA
+        LIST_GLUE ","
+      )
+
+    # Set optional variables (wrapped into XML elements)
+    # https://docs.microsoft.com/en-us/nuget/reference/nuspec#optional-metadata-elements
+    _cpack_nuget_variable_fallback_and_wrap_into_element(
+        title
+        TITLE
+        FALLBACK_VARS
+            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT}_DISPLAY_NAME
+            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DISPLAY_NAME
+            CPACK_COMPONENT_GROUP_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DISPLAY_NAME
+      )
+    _cpack_nuget_variable_fallback_and_wrap_into_element(owners OWNERS LIST_GLUE ",")
+    _cpack_nuget_variable_fallback_and_wrap_into_element(
+        projectUrl
+        HOMEPAGE_URL
+        FALLBACK_VARS
+            CPACK_PACKAGE_HOMEPAGE_URL
+      )
+    _cpack_nuget_variable_fallback_and_wrap_into_element(licenseUrl LICENSEURL)
+    _cpack_nuget_variable_fallback_and_wrap_into_element(iconUrl ICONURL)
+    _cpack_nuget_variable_fallback_and_wrap_into_element(
+        summary DESCRIPTION_SUMMARY
+        FALLBACK_VARS
+            CPACK_PACKAGE_DESCRIPTION_SUMMARY
+      )
+    if(CPACK_NUGET_PACKAGE_REQUIRE_LICENSE_ACCEPTANCE)
+        set(
+            _CPACK_NUGET_REQUIRELICENSEACCEPTANCE_TAG
+            "<requireLicenseAcceptance>true</requireLicenseAcceptance>"
+          )
+    endif()
+    _cpack_nuget_variable_fallback_and_wrap_into_element(releaseNotes RELEASE_NOTES)
+    _cpack_nuget_variable_fallback_and_wrap_into_element(copyright COPYRIGHT)
+    _cpack_nuget_variable_fallback_and_wrap_into_element(tags TAGS LIST_GLUE " ")
+
+    # Handle dependencies
+    _cpack_nuget_variable_fallback(_deps DEPENDENCIES)
+    set(_collected_deps)
+    foreach(_dep IN LISTS _deps)
+        _cpack_nuget_debug("  checking dependency `${_dep}`")
+
+        string(MAKE_C_IDENTIFIER "${_dep}" _dep_id)
+
+        _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep_id}_VERSION)
+
+        if(NOT _ver)
+            string(TOUPPER "${_dep_id}" _dep_id)
+            _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep_id}_VERSION)
+        endif()
+
+        if(_ver)
+            _cpack_nuget_debug("  got `${_dep}` dependency version ${_ver}")
+            list(APPEND _collected_deps "<dependency id=\"${_dep}\" version=\"${_ver}\" />")
+        endif()
+    endforeach()
+
+    # Render deps into the variable
+    if(_collected_deps)
+        set(_CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n")
+        foreach(_line IN LISTS _collected_deps)
+            string(
+                APPEND _CPACK_NUGET_DEPENDENCIES_TAG
+                "            ${_line}\n"
+              )
+        endforeach()
+        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_CURRENT_LIST_DIR}/CPack.NuGet.nuspec.in"
+        "${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec"
+        @ONLY
+      )
+endfunction()
+
+function(_cpack_nuget_make_files_tag)
+    set(_files)
+    foreach(_comp IN LISTS ARGN)
+        string(APPEND _files "        <file src=\"${_comp}\\**\" target=\".\" />\n")
+    endforeach()
+    set(_CPACK_NUGET_FILES_TAG "<files>\n${_files}    </files>" PARENT_SCOPE)
+endfunction()
+
+find_program(NUGET_EXECUTABLE NuGet)
+_cpack_nuget_debug_var(NUGET_EXECUTABLE)
+if(NOT NUGET_EXECUTABLE)
+    message(FATAL_ERROR "NuGet executable not found")
+endif()
+
+# Add details for debug run
+if(CPACK_NUGET_PACKAGE_DEBUG)
+    list(APPEND CPACK_NUGET_PACK_ADDITIONAL_OPTIONS "-Verbosity" "detailed")
+endif()
+
+# Case one: ordinal all-in-one package
+if(CPACK_NUGET_ORDINAL_MONOLITIC)
+    # This variable `CPACK_NUGET_ALL_IN_ONE` set by C++ code:
+    # Meaning to pack all installed files into a single package
+    _cpack_nuget_debug("---[Making an ordinal monolitic package]---")
+    _cpack_nuget_render_spec()
+    execute_process(
+        COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+      )
+
+elseif(CPACK_NUGET_ALL_IN_ONE)
+    # This variable `CPACK_NUGET_ALL_IN_ONE` set by C++ code:
+    # Meaning to pack all installed components into a single package
+    _cpack_nuget_debug("---[Making a monolitic package from installed components]---")
+
+    # Prepare the `files` element which include files from several components
+    _cpack_nuget_make_files_tag(${CPACK_NUGET_COMPONENTS})
+    _cpack_nuget_render_spec()
+    execute_process(
+        COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
+        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+      )
+
+else()
+    # Is there any grouped component?
+    if(CPACK_NUGET_GROUPS)
+        _cpack_nuget_debug("---[Making grouped component(s) package(s)]---")
+        foreach(_group IN LISTS CPACK_NUGET_GROUPS)
+            _cpack_nuget_debug("Starting to make the pacakge for group `${_group}`")
+            string(MAKE_C_IDENTIFIER "${_group}" _group_up)
+            string(TOUPPER "${_group_up}" _group_up)
+
+            # Render a spec file which includes all components in the current group
+            unset(_CPACK_NUGET_FILES_TAG)
+            _cpack_nuget_make_files_tag(${CPACK_NUGET_${_group_up}_GROUP_COMPONENTS})
+            # Temporary set `CPACK_NUGET_PACKAGE_COMPONENT` to the group name
+            # to properly collect various per group settings
+            set(CPACK_NUGET_PACKAGE_COMPONENT ${_group})
+            _cpack_nuget_render_spec()
+            unset(CPACK_NUGET_PACKAGE_COMPONENT)
+            execute_process(
+                COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
+                WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+              )
+        endforeach()
+    endif()
+    # Is there any single component package needed?
+    if(CPACK_NUGET_COMPONENTS)
+        _cpack_nuget_debug("---[Making single-component(s) package(s)]---")
+        foreach(_comp IN LISTS CPACK_NUGET_COMPONENTS)
+            _cpack_nuget_debug("Starting to make the pacakge for component `${_comp}`")
+            # Render a spec file which includes only given component
+            unset(_CPACK_NUGET_FILES_TAG)
+            _cpack_nuget_make_files_tag(${_comp})
+            # Temporary set `CPACK_NUGET_PACKAGE_COMPONENT` to the the current
+            # component name to properly collect various per group settings
+            set(CPACK_NUGET_PACKAGE_COMPONENT ${_comp})
+            _cpack_nuget_render_spec()
+            unset(CPACK_NUGET_PACKAGE_COMPONENT)
+            execute_process(
+                COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
+                WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
+              )
+        endforeach()
+    endif()
+endif()
+
+file(GLOB_RECURSE GEN_CPACK_OUTPUT_FILES "${CPACK_TEMPORARY_DIRECTORY}/*.nupkg")
+if(NOT GEN_CPACK_OUTPUT_FILES)
+    message(FATAL_ERROR "NuGet package was not generated at `${CPACK_TEMPORARY_DIRECTORY}`!")
+endif()
+
+_cpack_nuget_debug("Generated files: ${GEN_CPACK_OUTPUT_FILES}")

+ 1 - 0
Source/CMakeLists.txt

@@ -886,6 +886,7 @@ set(CPACK_SRCS
   CPack/cmCPackGenerator.cxx
   CPack/cmCPackLog.cxx
   CPack/cmCPackNSISGenerator.cxx
+  CPack/cmCPackNuGetGenerator.cxx
   CPack/cmCPackSTGZGenerator.cxx
   CPack/cmCPackTGZGenerator.cxx
   CPack/cmCPackTXZGenerator.cxx

+ 5 - 0
Source/CPack/cmCPackGeneratorFactory.cxx

@@ -15,6 +15,7 @@
 #include "cmCPackGenerator.h"
 #include "cmCPackLog.h"
 #include "cmCPackNSISGenerator.h"
+#include "cmCPackNuGetGenerator.h"
 #include "cmCPackSTGZGenerator.h"
 #include "cmCPackTGZGenerator.h"
 #include "cmCPackTXZGenerator.h"
@@ -105,6 +106,10 @@ cmCPackGeneratorFactory::cmCPackGeneratorFactory()
     this->RegisterGenerator("DEB", "Debian packages",
                             cmCPackDebGenerator::CreateGenerator);
   }
+  if (cmCPackNuGetGenerator::CanGenerate()) {
+    this->RegisterGenerator("NuGet", "NuGet packages",
+                            cmCPackNuGetGenerator::CreateGenerator);
+  }
 #ifdef __APPLE__
   if (cmCPackDragNDropGenerator::CanGenerate()) {
     this->RegisterGenerator("DragNDrop", "Mac OSX Drag And Drop",

+ 140 - 0
Source/CPack/cmCPackNuGetGenerator.cxx

@@ -0,0 +1,140 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCPackNuGetGenerator.h"
+
+#include "cmAlgorithms.h"
+#include "cmCPackComponentGroup.h"
+#include "cmCPackLog.h"
+#include "cmSystemTools.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+bool cmCPackNuGetGenerator::SupportsComponentInstallation() const
+{
+  return IsOn("CPACK_NUGET_COMPONENT_INSTALL");
+}
+
+int cmCPackNuGetGenerator::PackageFiles()
+{
+  cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
+
+  /* Reset package file name list it will be populated after the
+   * `CPackNuGet.cmake` run */
+  packageFileNames.clear();
+
+  /* Are we in the component packaging case */
+  if (WantsComponentInstallation()) {
+    if (componentPackageMethod == ONE_PACKAGE) {
+      // CASE 1 : COMPONENT ALL-IN-ONE package
+      // Meaning that all per-component pre-installed files
+      // goes into the single package.
+      this->SetOption("CPACK_NUGET_ALL_IN_ONE", "TRUE");
+      SetupGroupComponentVariables(true);
+    } else {
+      // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
+      // There will be 1 package for each component group
+      // however one may require to ignore component group and
+      // in this case you'll get 1 package for each component.
+      SetupGroupComponentVariables(componentPackageMethod ==
+                                   ONE_PACKAGE_PER_COMPONENT);
+    }
+  } else {
+    // CASE 3 : NON COMPONENT package.
+    this->SetOption("CPACK_NUGET_ORDINAL_MONOLITIC", "TRUE");
+  }
+
+  auto retval = this->ReadListFile("CPackNuGet.cmake");
+  if (retval) {
+    AddGeneratedPackageNames();
+  } else {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Error while execution CPackNuGet.cmake" << std::endl);
+  }
+
+  return retval;
+}
+
+void cmCPackNuGetGenerator::SetupGroupComponentVariables(bool ignoreGroup)
+{
+  // The default behavior is to have one package by component group
+  // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
+  if (!ignoreGroup) {
+    std::vector<std::string> groups;
+    for (auto const& compG : this->ComponentGroups) {
+      cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+                    "Packaging component group: " << compG.first << std::endl);
+      groups.push_back(compG.first);
+      auto compGUp =
+        cmSystemTools::UpperCase(cmSystemTools::MakeCidentifier(compG.first));
+
+      // Collect components for this group
+      std::vector<std::string> components;
+      std::transform(begin(compG.second.Components),
+                     end(compG.second.Components),
+                     std::back_inserter(components),
+                     [](cmCPackComponent const* comp) { return comp->Name; });
+      this->SetOption("CPACK_NUGET_" + compGUp + "_GROUP_COMPONENTS",
+                      cmJoin(components, ";").c_str());
+    }
+    if (!groups.empty()) {
+      this->SetOption("CPACK_NUGET_GROUPS", cmJoin(groups, ";").c_str());
+    }
+
+    // Handle Orphan components (components not belonging to any groups)
+    std::vector<std::string> components;
+    for (auto const& comp : this->Components) {
+      // Does the component belong to a group?
+      if (comp.second.Group == nullptr) {
+        cmCPackLogger(
+          cmCPackLog::LOG_VERBOSE, "Component <"
+            << comp.second.Name
+            << "> does not belong to any group, package it separately."
+            << std::endl);
+        components.push_back(comp.first);
+      }
+    }
+    if (!components.empty()) {
+      this->SetOption("CPACK_NUGET_COMPONENTS",
+                      cmJoin(components, ";").c_str());
+    }
+
+  } else {
+    std::vector<std::string> components;
+    components.reserve(this->Components.size());
+    std::transform(begin(this->Components), end(this->Components),
+                   std::back_inserter(components),
+                   [](std::pair<std::string, cmCPackComponent> const& comp) {
+                     return comp.first;
+                   });
+    this->SetOption("CPACK_NUGET_COMPONENTS", cmJoin(components, ";").c_str());
+  }
+}
+
+void cmCPackNuGetGenerator::AddGeneratedPackageNames()
+{
+  const char* const files_list = this->GetOption("GEN_CPACK_OUTPUT_FILES");
+  if (!files_list) {
+    cmCPackLogger(
+      cmCPackLog::LOG_ERROR,
+      "Error while execution CPackNuGet.cmake: No NuGet package has generated"
+        << std::endl);
+    return;
+  }
+  // add the generated packages to package file names list
+  std::string fileNames{ files_list };
+  const char sep = ';';
+  std::string::size_type pos1 = 0;
+  std::string::size_type pos2 = fileNames.find(sep, pos1 + 1);
+  while (pos2 != std::string::npos) {
+    packageFileNames.push_back(fileNames.substr(pos1, pos2 - pos1));
+    pos1 = pos2 + 1;
+    pos2 = fileNames.find(sep, pos1 + 1);
+  }
+  packageFileNames.push_back(fileNames.substr(pos1, pos2 - pos1));
+}

+ 37 - 0
Source/CPack/cmCPackNuGetGenerator.h

@@ -0,0 +1,37 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmCPackNuGetGenerator_h
+#define cmCPackNuGetGenerator_h
+
+#include "cmCPackGenerator.h"
+
+/** \class cmCPackNuGetGenerator
+ * \brief A generator for RPM packages
+ */
+class cmCPackNuGetGenerator : public cmCPackGenerator
+{
+public:
+  cmCPackTypeMacro(cmCPackNuGetGenerator, cmCPackGenerator);
+
+  // NOTE In fact, it is possible to have NuGet not only for Windows...
+  // https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools
+  static bool CanGenerate() { return true; }
+
+protected:
+  bool SupportsComponentInstallation() const override;
+  int PackageFiles() override;
+
+  const char* GetOutputExtension() override { return ".nupkg"; }
+  bool SupportsAbsoluteDestination() const override { return false; }
+  /**
+   * The method used to prepare variables when component
+   * install is used.
+   */
+  void SetupGroupComponentVariables(bool ignoreGroup);
+  /**
+   * Populate \c packageFileNames vector of built packages.
+   */
+  void AddGeneratedPackageNames();
+};
+
+#endif

+ 20 - 2
Tests/CMakeLists.txt

@@ -154,6 +154,15 @@ if(BUILD_TESTING)
     set(CPACK_BINARY_DEB OFF)
   endif()
 
+  # Look for NuGet to use for tests.
+  find_program(NUGET_EXECUTABLE NAMES NuGet nuget)
+
+  if(NUGET_EXECUTABLE)
+    set(CPACK_BINARY_NUGET ON)
+  else()
+    set(CPACK_BINARY_NUGET OFF)
+  endif()
+
   #---------------------------------------------------------------------------
   # Add tests below here.
 
@@ -1032,6 +1041,12 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
     if(CPACK_BINARY_DEB)
       list(APPEND ACTIVE_CPACK_GENERATORS DEB)
     endif()
+    # Check whether if NuGet command is found
+    # before adding NuGet tests
+    if(CPACK_BINARY_NUGET)
+      list(APPEND ACTIVE_CPACK_GENERATORS NUGET)
+      set(CPACK_GENERATOR_STRING_NUGET NuGet)
+    endif()
 
     # ACTIVE_CPACK_GENERATORS variable
     # now contains the list of 'active generators'
@@ -1051,7 +1066,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
     list(APPEND CWAYLST "IgnoreGroup")
     list(APPEND CWAYLST "AllInOne")
     foreach(CPackGen IN LISTS ACTIVE_CPACK_GENERATORS)
-      set(CPackRun_CPackGen  "-DCPackGen=${CPackGen}")
+      if(NOT DEFINED CPACK_GENERATOR_STRING_${CPackGen})
+        set(CPACK_GENERATOR_STRING_${CPackGen} ${CPackGen})
+      endif()
+      set(CPackRun_CPackGen  "-DCPackGen=${CPACK_GENERATOR_STRING_${CPackGen}}")
       foreach(CPackComponentWay ${CWAYLST})
         set(CPackRun_CPackComponentWay "-DCPackComponentWay=${CPackComponentWay}")
         add_test(CPackComponentsForAll-${CPackGen}-${CPackComponentWay}
@@ -1062,7 +1080,7 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
           ${build_generator_args}
           --build-project CPackComponentsForAll
           --build-options ${build_options}
-             -DCPACK_GENERATOR:STRING=${CPackGen}
+             -DCPACK_GENERATOR:STRING=${CPACK_GENERATOR_STRING_${CPackGen}}
              -DCPACK_BINARY_${CPackGen}:BOOL=ON
              ${CPackRun_CPackComponentWay}
              ${CPackComponentsForAll_BUILD_OPTIONS}

+ 12 - 0
Tests/CPackComponentsForAll/CMakeLists.txt

@@ -168,6 +168,18 @@ set(CPACK_RPM_RELOCATION_PATHS "${CMAKE_INSTALL_INCLUDEDIR}"
 # set CPACK_DEBIAN_FILE_NAME to use default package name format
 set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
 
+# set some tags for NuGet packages
+# 1. all in one pacakge
+set(CPACK_NUGET_PACKAGE_TAGS "nuget" "unit" "test" "all-in-one")
+# 2. per component packages
+set(CPACK_NUGET_APPLICATIONS_PACKAGE_TAGS "nuget" "unit" "test" "applications")
+set(CPACK_NUGET_LIBRARIES_PACKAGE_TAGS "nuget" "unit" "test" "libraries")
+set(CPACK_NUGET_HEADERS_PACKAGE_TAGS "nuget" "unit" "test" "headers")
+set(CPACK_NUGET_UNSPECIFIED_PACKAGE_TAGS "nuget" "unit" "test" "uNsP3c1FiEd")
+# 3. per group packages
+set(CPACK_NUGET_RUNTIME_PACKAGE_TAGS "nuget" "unit" "test" "run-time")
+set(CPACK_NUGET_DEVELOPMENT_PACKAGE_TAGS "nuget" "unit" "test" "development")
+
 # We may use the CPack specific config file in order
 # to tailor CPack behavior on a CPack generator specific way
 # (Behavior would be different for RPM or TGZ or DEB ...)

+ 4 - 0
Tests/CPackComponentsForAll/MyLibCPackConfig-AllInOne.cmake.in

@@ -13,6 +13,10 @@ if(CPACK_GENERATOR MATCHES "DEB")
    set(CPACK_DEB_COMPONENT_INSTALL "ON")
 endif()
 
+if(CPACK_GENERATOR MATCHES "NuGet")
+   set(CPACK_NUGET_COMPONENT_INSTALL "ON")
+endif()
+
 #
 # Choose grouping way
 #

+ 4 - 0
Tests/CPackComponentsForAll/MyLibCPackConfig-IgnoreGroup.cmake.in

@@ -52,6 +52,10 @@ if(CPACK_GENERATOR MATCHES "DEB")
    set(CPACK_DEB_COMPONENT_INSTALL "ON")
 endif()
 
+if(CPACK_GENERATOR MATCHES "NuGet")
+   set(CPACK_NUGET_COMPONENT_INSTALL "ON")
+endif()
+
 #
 # Choose grouping way
 #

+ 4 - 0
Tests/CPackComponentsForAll/MyLibCPackConfig-OnePackPerGroup.cmake.in

@@ -18,6 +18,10 @@ if(CPACK_GENERATOR MATCHES "DragNDrop")
    set(CPACK_COMPONENTS_GROUPING "ONE_PER_GROUP")
 endif()
 
+if(CPACK_GENERATOR MATCHES "NuGet")
+   set(CPACK_NUGET_COMPONENT_INSTALL "ON")
+endif()
+
 #
 # Choose grouping way
 #

+ 12 - 0
Tests/CPackComponentsForAll/RunCPackVerifyResult.cmake

@@ -70,6 +70,18 @@ elseif(CPackGen MATCHES "DEB")
     elseif(${CPackComponentWay} STREQUAL "AllInOne")
         set(expected_count 1)
     endif()
+elseif(CPackGen MATCHES "NuGet")
+    set(config_verbose -D "CPACK_NUGET_PACKAGE_DEBUG=1")
+    set(expected_file_mask "${CPackComponentsForAll_BINARY_DIR}/MyLib*1.0.2.nupkg")
+    if(${CPackComponentWay} STREQUAL "default")
+        set(expected_count 1)
+    elseif(${CPackComponentWay} STREQUAL "OnePackPerGroup")
+        set(expected_count 3)
+    elseif(${CPackComponentWay} STREQUAL "IgnoreGroup")
+        set(expected_count 4)
+    elseif(${CPackComponentWay} STREQUAL "AllInOne")
+        set(expected_count 1)
+    endif()
 endif()
 
 if(CPackGen MATCHES "DragNDrop")