From f739752ad6f2d661148faa3691189db9251d34b6 Mon Sep 17 00:00:00 2001 From: Alex Turbov Date: Mon, 7 May 2018 23:47:28 +0300 Subject: 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. --- Help/manual/cmake-modules.7.rst | 1 + Help/module/CPackNuGet.rst | 1 + Help/release/dev/cpack-nuget.rst | 7 + Modules/CPack.NuGet.nuspec.in | 24 + Modules/CPack.cmake | 11 +- Modules/CPackNuGet.cmake | 556 +++++++++++++++++++++ Source/CMakeLists.txt | 1 + Source/CPack/cmCPackGeneratorFactory.cxx | 5 + Source/CPack/cmCPackNuGetGenerator.cxx | 140 ++++++ Source/CPack/cmCPackNuGetGenerator.h | 37 ++ Tests/CMakeLists.txt | 22 +- Tests/CPackComponentsForAll/CMakeLists.txt | 12 + .../MyLibCPackConfig-AllInOne.cmake.in | 4 + .../MyLibCPackConfig-IgnoreGroup.cmake.in | 4 + .../MyLibCPackConfig-OnePackPerGroup.cmake.in | 4 + .../RunCPackVerifyResult.cmake | 12 + 16 files changed, 835 insertions(+), 6 deletions(-) create mode 100644 Help/module/CPackNuGet.rst create mode 100644 Help/release/dev/cpack-nuget.rst create mode 100644 Modules/CPack.NuGet.nuspec.in create mode 100644 Modules/CPackNuGet.cmake create mode 100644 Source/CPack/cmCPackNuGetGenerator.cxx create mode 100644 Source/CPack/cmCPackNuGetGenerator.h diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst index 070c6ed..3bfaa54 100644 --- a/Help/manual/cmake-modules.7.rst +++ b/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 diff --git a/Help/module/CPackNuGet.rst b/Help/module/CPackNuGet.rst new file mode 100644 index 0000000..a4cbb59 --- /dev/null +++ b/Help/module/CPackNuGet.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../Modules/CPackNuGet.cmake diff --git a/Help/release/dev/cpack-nuget.rst b/Help/release/dev/cpack-nuget.rst new file mode 100644 index 0000000..03cb101 --- /dev/null +++ b/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 diff --git a/Modules/CPack.NuGet.nuspec.in b/Modules/CPack.NuGet.nuspec.in new file mode 100644 index 0000000..b7beb5d --- /dev/null +++ b/Modules/CPack.NuGet.nuspec.in @@ -0,0 +1,24 @@ + + + + + @CPACK_NUGET_PACKAGE_NAME@ + @CPACK_NUGET_PACKAGE_VERSION@ + @CPACK_NUGET_PACKAGE_DESCRIPTION@ + @CPACK_NUGET_PACKAGE_AUTHORS@ + + + @_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@ + + @_CPACK_NUGET_FILES_TAG@ + diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake index 05fb78a..8070869 100644 --- a/Modules/CPack.cmake +++ b/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 diff --git a/Modules/CPackNuGet.cmake b/Modules/CPackNuGet.cmake new file mode 100644 index 0000000..29d4296 --- /dev/null +++ b/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`), ```` 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__PACKAGE_NAME + + The NUGET package name. + + * Mandatory : YES + * Default : :variable:`CPACK_PACKAGE_NAME` + +.. variable:: CPACK_NUGET_PACKAGE_VERSION + CPACK_NUGET__PACKAGE_VERSION + + The NuGet package version. + + * Mandatory : YES + * Default : :variable:`CPACK_PACKAGE_VERSION` + +.. variable:: CPACK_NUGET_PACKAGE_DESCRIPTION + CPACK_NUGET__PACKAGE_DESCRIPTION + + A long description of the package for UI display. + + * Mandatory : YES + * Default : + - :variable:`CPACK_COMPONENT__DESCRIPTION`, + - ``CPACK_COMPONENT_GROUP__DESCRIPTION``, + - :variable:`CPACK_PACKAGE_DESCRIPTION` + + .. variable:: CPACK_NUGET_PACKAGE_AUTHORS + CPACK_NUGET__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__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__DISPLAY_NAME`, + - ``CPACK_COMPONENT_GROUP__DISPLAY_NAME`` + +.. variable:: CPACK_NUGET_PACKAGE_OWNERS + CPACK_NUGET__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__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__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__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__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__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__PACKAGE_COPYRIGHT + + Copyright details for the package. + + * Mandatory : NO + * Default : - + +.. variable:: CPACK_NUGET_PACKAGE_TAGS + CPACK_NUGET__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__PACKAGE_DEPENDENCIES + + A list of package dependencies. + + * Mandatory : NO + * Default : - + +.. variable:: CPACK_NUGET_PACKAGE_DEPENDENCIES__VERSION + CPACK_NUGET__PACKAGE_DEPENDENCIES__VERSION + + A `version specification`_ for the particular dependency, where + ```` 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 "") + 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}" + 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 + "true" + ) + 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 "") + endif() + endforeach() + + # Render deps into the variable + if(_collected_deps) + set(_CPACK_NUGET_DEPENDENCIES_TAG "\n") + foreach(_line IN LISTS _collected_deps) + string( + APPEND _CPACK_NUGET_DEPENDENCIES_TAG + " ${_line}\n" + ) + endforeach() + string(APPEND _CPACK_NUGET_DEPENDENCIES_TAG " ") + 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 " \n") + endforeach() + set(_CPACK_NUGET_FILES_TAG "\n${_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}") diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index d9e07e5..316d50b 100644 --- a/Source/CMakeLists.txt +++ b/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 diff --git a/Source/CPack/cmCPackGeneratorFactory.cxx b/Source/CPack/cmCPackGeneratorFactory.cxx index 47e7527..a395a8f 100644 --- a/Source/CPack/cmCPackGeneratorFactory.cxx +++ b/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", diff --git a/Source/CPack/cmCPackNuGetGenerator.cxx b/Source/CPack/cmCPackNuGetGenerator.cxx new file mode 100644 index 0000000..2ebfb3d --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +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 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 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 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 components; + components.reserve(this->Components.size()); + std::transform(begin(this->Components), end(this->Components), + std::back_inserter(components), + [](std::pair 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)); +} diff --git a/Source/CPack/cmCPackNuGetGenerator.h b/Source/CPack/cmCPackNuGetGenerator.h new file mode 100644 index 0000000..a59db2d --- /dev/null +++ b/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 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 6e3c3c4..fe8f2cc 100644 --- a/Tests/CMakeLists.txt +++ b/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} diff --git a/Tests/CPackComponentsForAll/CMakeLists.txt b/Tests/CPackComponentsForAll/CMakeLists.txt index 3440843..f2e4fcd 100644 --- a/Tests/CPackComponentsForAll/CMakeLists.txt +++ b/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 ...) diff --git a/Tests/CPackComponentsForAll/MyLibCPackConfig-AllInOne.cmake.in b/Tests/CPackComponentsForAll/MyLibCPackConfig-AllInOne.cmake.in index 0bfbf14..1b9e658 100644 --- a/Tests/CPackComponentsForAll/MyLibCPackConfig-AllInOne.cmake.in +++ b/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 # diff --git a/Tests/CPackComponentsForAll/MyLibCPackConfig-IgnoreGroup.cmake.in b/Tests/CPackComponentsForAll/MyLibCPackConfig-IgnoreGroup.cmake.in index 0ffe44d..a6f6ea9 100644 --- a/Tests/CPackComponentsForAll/MyLibCPackConfig-IgnoreGroup.cmake.in +++ b/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 # diff --git a/Tests/CPackComponentsForAll/MyLibCPackConfig-OnePackPerGroup.cmake.in b/Tests/CPackComponentsForAll/MyLibCPackConfig-OnePackPerGroup.cmake.in index ac65dc9..d41225d 100644 --- a/Tests/CPackComponentsForAll/MyLibCPackConfig-OnePackPerGroup.cmake.in +++ b/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 # diff --git a/Tests/CPackComponentsForAll/RunCPackVerifyResult.cmake b/Tests/CPackComponentsForAll/RunCPackVerifyResult.cmake index 2bfb5b0..253d128 100644 --- a/Tests/CPackComponentsForAll/RunCPackVerifyResult.cmake +++ b/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") -- cgit v0.12