1# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2# file Copyright.txt or https://cmake.org/licensing for details.
3
4# Author: Alex Turbov
5
6if(CMAKE_BINARY_DIR)
7  message(FATAL_ERROR "CPackNuGet.cmake may only be used by CPack internally.")
8endif()
9
10function(_cpack_nuget_debug)
11    if(CPACK_NUGET_PACKAGE_DEBUG)
12        message("CPackNuGet:Debug: " ${ARGN})
13    endif()
14endfunction()
15
16function(_cpack_nuget_debug_var NAME)
17    if(CPACK_NUGET_PACKAGE_DEBUG)
18        message("CPackNuGet:Debug: ${NAME}=`${${NAME}}`")
19    endif()
20endfunction()
21
22function(_cpack_nuget_variable_fallback OUTPUT_VAR_NAME NUGET_VAR_NAME)
23    if(ARGN)
24        list(JOIN ARGN "`, `" _va_args)
25        set(_va_args ", ARGN: `${_va_args}`")
26    endif()
27    _cpack_nuget_debug(
28        "_cpack_nuget_variable_fallback: "
29        "OUTPUT_VAR_NAME=`${OUTPUT_VAR_NAME}`, "
30        "NUGET_VAR_NAME=`${NUGET_VAR_NAME}`"
31        "${_va_args}"
32      )
33
34    set(_options USE_CDATA)
35    set(_one_value_args LIST_GLUE)
36    set(_multi_value_args FALLBACK_VARS)
37    cmake_parse_arguments(PARSE_ARGV 0 _args "${_options}" "${_one_value_args}" "${_multi_value_args}")
38
39    if(CPACK_NUGET_PACKAGE_COMPONENT)
40        string(
41            TOUPPER "${CPACK_NUGET_PACKAGE_COMPONENT}"
42            CPACK_NUGET_PACKAGE_COMPONENT_UPPER
43          )
44    endif()
45
46    if(CPACK_NUGET_PACKAGE_COMPONENT
47      AND CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}
48      )
49        set(
50            _result
51            "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}}"
52          )
53        _cpack_nuget_debug(
54            "  CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_PACKAGE_${NUGET_VAR_NAME}: "
55            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
56          )
57
58    elseif(CPACK_NUGET_PACKAGE_COMPONENT_UPPER
59      AND CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}
60      )
61        set(
62            _result
63            "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}}"
64          )
65        _cpack_nuget_debug(
66            "  CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_${NUGET_VAR_NAME}: "
67            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
68          )
69
70    elseif(CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME})
71        set(_result "${CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME}}")
72        _cpack_nuget_debug(
73            "  CPACK_NUGET_PACKAGE_${NUGET_VAR_NAME}: "
74            "OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
75          )
76
77    else()
78        foreach(_var IN LISTS _args_FALLBACK_VARS)
79            _cpack_nuget_debug("  Fallback: ${_var} ...")
80            if(${_var})
81                _cpack_nuget_debug("            ${_var}=`${${_var}}`")
82                set(_result "${${_var}}")
83                _cpack_nuget_debug(
84                    "  ${_var}: OUTPUT_VAR_NAME->${OUTPUT_VAR_NAME}=`${_result}`"
85                  )
86                break()
87            endif()
88        endforeach()
89    endif()
90
91    if(_result)
92        if(_args_USE_CDATA)
93            set(_value_before "<![CDATA[")
94            set(_value_after "]]>")
95        endif()
96
97        list(LENGTH _result _result_len)
98        if(_result_len GREATER 1 AND _args_LIST_GLUE)
99            list(JOIN _result "${_args_LIST_GLUE}" _result)
100        endif()
101
102        set(${OUTPUT_VAR_NAME} "${_value_before}${_result}${_value_after}" PARENT_SCOPE)
103    endif()
104
105endfunction()
106
107function(_cpack_nuget_variable_fallback_and_wrap_into_element ELEMENT NUGET_VAR_NAME)
108    set(_options)
109    set(_one_value_args)
110    set(_multi_value_args FALLBACK_VARS ATTRIBUTES)
111    cmake_parse_arguments(PARSE_ARGV 2 _args "${_options}" "${_one_value_args}" "${_multi_value_args}")
112
113    if(_args_ATTRIBUTES)
114        list(JOIN _args_ATTRIBUTES " " _attributes)
115        string(PREPEND _attributes " ")
116    endif()
117
118    _cpack_nuget_variable_fallback(_value ${NUGET_VAR_NAME} ${ARGN} USE_CDATA)
119
120    string(TOUPPER "${ELEMENT}" _ELEMENT_UP)
121    if(_value)
122        set(
123            _CPACK_NUGET_${_ELEMENT_UP}_TAG
124            "<${ELEMENT}${_attributes}>${_value}</${ELEMENT}>"
125            PARENT_SCOPE
126          )
127    elseif(_attributes)
128        set(
129            _CPACK_NUGET_${_ELEMENT_UP}_TAG
130            "<${ELEMENT}${_attributes} />"
131            PARENT_SCOPE
132          )
133    endif()
134endfunction()
135
136# Warn of obsolete nuspec fields, referencing CMake variables and suggested
137# replacement, if any
138function(_cpack_nuget_deprecation_warning NUGET_ELEMENT VARNAME REPLACEMENT)
139    if(${VARNAME})
140        if(REPLACEMENT)
141            message(DEPRECATION "nuspec element `${NUGET_ELEMENT}` is deprecated in NuGet; consider replacing `${VARNAME}` with `${REPLACEMENT}`")
142        else()
143            message(DEPRECATION "nuspec element `${NUGET_ELEMENT}` is deprecated in NuGet; consider removing `${VARNAME}`")
144        endif()
145    endif()
146endfunction()
147
148# Print some debug info
149_cpack_nuget_debug("---[CPack NuGet Input Variables]---")
150_cpack_nuget_debug_var(CPACK_PACKAGE_NAME)
151_cpack_nuget_debug_var(CPACK_PACKAGE_VERSION)
152_cpack_nuget_debug_var(CPACK_TOPLEVEL_TAG)
153_cpack_nuget_debug_var(CPACK_TOPLEVEL_DIRECTORY)
154_cpack_nuget_debug_var(CPACK_TEMPORARY_DIRECTORY)
155_cpack_nuget_debug_var(CPACK_NUGET_GROUPS)
156if(CPACK_NUGET_GROUPS)
157    foreach(_group IN LISTS CPACK_NUGET_GROUPS)
158        string(MAKE_C_IDENTIFIER "${_group}" _group_up)
159        string(TOUPPER "${_group_up}" _group_up)
160        _cpack_nuget_debug_var(CPACK_NUGET_${_group_up}_GROUP_COMPONENTS)
161    endforeach()
162endif()
163_cpack_nuget_debug_var(CPACK_NUGET_COMPONENTS)
164_cpack_nuget_debug_var(CPACK_NUGET_ALL_IN_ONE)
165_cpack_nuget_debug_var(CPACK_NUGET_ORDINAL_MONOLITIC)
166_cpack_nuget_debug("-----------------------------------")
167
168function(_cpack_nuget_render_spec)
169    # Make a variable w/ upper-cased component name
170    if(CPACK_NUGET_PACKAGE_COMPONENT)
171        string(TOUPPER "${CPACK_NUGET_PACKAGE_COMPONENT}" CPACK_NUGET_PACKAGE_COMPONENT_UPPER)
172    endif()
173
174    # Set mandatory variables (not wrapped into XML elements)
175    # https://docs.microsoft.com/en-us/nuget/reference/nuspec#required-metadata-elements
176    if(CPACK_NUGET_PACKAGE_COMPONENT)
177        if(CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_NAME)
178            set(
179                CPACK_NUGET_PACKAGE_NAME
180                "${CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_PACKAGE_NAME}"
181              )
182        elseif(NOT CPACK_NUGET_PACKAGE_COMPONENT STREQUAL "Unspecified")
183            set(
184                CPACK_NUGET_PACKAGE_NAME
185                "${CPACK_PACKAGE_NAME}.${CPACK_NUGET_PACKAGE_COMPONENT}"
186              )
187        else()
188            set(CPACK_NUGET_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
189        endif()
190    elseif(NOT CPACK_NUGET_PACKAGE_NAME)
191        set(CPACK_NUGET_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
192    endif()
193
194    # Warn about deprecated nuspec elements; warnings only display if
195    # variable is set
196    # Note that while nuspec's "summary" element is deprecated, there
197    # is no suggested replacement so (for now) no deprecation warning
198    # is shown for `CPACK_NUGET_*_DESCRIPTION_SUMMARY`
199    _cpack_nuget_deprecation_warning("licenseUrl" CPACK_NUGET_PACKAGE_LICENSEURL
200        "CPACK_NUGET_PACKAGE_LICENSE_FILE_NAME or CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION")
201    _cpack_nuget_deprecation_warning("licenseUrl" CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_LICENSEURL
202        "CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_LICENSE_FILE_NAME or CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_LICENSE_EXPRESSION")
203    _cpack_nuget_deprecation_warning("iconUrl" CPACK_NUGET_PACKAGE_ICONURL
204        "CPACK_NUGET_PACKAGE_ICON")
205    _cpack_nuget_deprecation_warning("iconUrl" CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_ICONURL
206        "CPACK_NUGET_${CPACK_NUGET_PACKAGE_COMPONENT}_ICON")
207
208    # Set nuspec fields
209    _cpack_nuget_variable_fallback(
210        CPACK_NUGET_PACKAGE_VERSION VERSION
211        FALLBACK_VARS
212            CPACK_PACKAGE_VERSION
213      )
214    _cpack_nuget_variable_fallback(
215        CPACK_NUGET_PACKAGE_DESCRIPTION DESCRIPTION
216        FALLBACK_VARS
217            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT}_DESCRIPTION
218            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DESCRIPTION
219            CPACK_COMPONENT_GROUP_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DESCRIPTION
220            CPACK_PACKAGE_DESCRIPTION
221        USE_CDATA
222      )
223    _cpack_nuget_variable_fallback(
224        CPACK_NUGET_PACKAGE_AUTHORS AUTHORS
225        FALLBACK_VARS
226            CPACK_PACKAGE_VENDOR
227        USE_CDATA
228        LIST_GLUE ","
229      )
230
231    # Set optional variables (wrapped into XML elements)
232    # https://docs.microsoft.com/en-us/nuget/reference/nuspec#optional-metadata-elements
233    _cpack_nuget_variable_fallback_and_wrap_into_element(
234        title
235        TITLE
236        FALLBACK_VARS
237            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT}_DISPLAY_NAME
238            CPACK_COMPONENT_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DISPLAY_NAME
239            CPACK_COMPONENT_GROUP_${CPACK_NUGET_PACKAGE_COMPONENT_UPPER}_DISPLAY_NAME
240      )
241    _cpack_nuget_variable_fallback_and_wrap_into_element(owners OWNERS LIST_GLUE ",")
242    _cpack_nuget_variable_fallback_and_wrap_into_element(
243        projectUrl
244        HOMEPAGE_URL
245        FALLBACK_VARS
246            CPACK_PACKAGE_HOMEPAGE_URL
247      )
248
249    # "licenseUrl" is deprecated in favor of "license"
250    _cpack_nuget_variable_fallback_and_wrap_into_element(licenseUrl LICENSEURL)
251
252    # "iconUrl" is deprecated in favor of "icon"
253    _cpack_nuget_variable_fallback_and_wrap_into_element(iconUrl ICONURL)
254
255    # "license" takes a "type" attribute of either "file" or "expression"
256    # "file" refers to a file path of a .txt or .md file relative to the installation root
257    # "expression" refers to simple or compound expression of license identifiers
258    # listed at https://spdx.org/licenses/
259    # Note that only one of CPACK_NUGET_PACKAGE_LICENSE_FILE_NAME and
260    # CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION may be specified. If both are specified,
261    # CPACK_NUGET_PACKAGE_LICENSE_FILE_NAME takes precedence and CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION is ignored.
262    if(CPACK_NUGET_PACKAGE_LICENSE_FILE_NAME)
263        _cpack_nuget_variable_fallback_and_wrap_into_element(
264            license LICENSE_FILE_NAME
265            ATTRIBUTES [[type="file"]]
266          )
267    elseif(CPACK_NUGET_PACKAGE_LICENSE_EXPRESSION)
268        _cpack_nuget_variable_fallback_and_wrap_into_element(
269            license LICENSE_EXPRESSION
270            ATTRIBUTES [[type="expression"]]
271          )
272    endif()
273
274    # "icon" refers to a file path relative to the installation root
275    _cpack_nuget_variable_fallback_and_wrap_into_element(icon ICON)
276    # "summary" is deprecated in favor of "description"
277    _cpack_nuget_variable_fallback_and_wrap_into_element(
278        summary DESCRIPTION_SUMMARY
279        FALLBACK_VARS
280            CPACK_PACKAGE_DESCRIPTION_SUMMARY
281      )
282    if(CPACK_NUGET_PACKAGE_REQUIRE_LICENSE_ACCEPTANCE)
283        set(
284            _CPACK_NUGET_REQUIRELICENSEACCEPTANCE_TAG
285            "<requireLicenseAcceptance>true</requireLicenseAcceptance>"
286          )
287    endif()
288    _cpack_nuget_variable_fallback_and_wrap_into_element(releaseNotes RELEASE_NOTES)
289    _cpack_nuget_variable_fallback_and_wrap_into_element(copyright COPYRIGHT)
290    # "language" is a locale identifier such as "en_CA"
291    _cpack_nuget_variable_fallback_and_wrap_into_element(language LANGUAGE)
292    _cpack_nuget_variable_fallback_and_wrap_into_element(tags TAGS LIST_GLUE " ")
293    # "repository" holds repository metadata consisting of four optional
294    # attributes: "type", "url", "branch", and "commit". While all fields are
295    # considered optional, they are not independent. Currently unsupported.
296
297    # Handle dependencies
298    _cpack_nuget_variable_fallback(_deps DEPENDENCIES)
299    set(_collected_deps)
300    foreach(_dep IN LISTS _deps)
301        _cpack_nuget_debug("  checking dependency `${_dep}`")
302
303        _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep}_VERSION)
304
305        if(NOT _ver)
306            string(TOUPPER "${_dep}" _dep_upper)
307            _cpack_nuget_variable_fallback(_ver DEPENDENCIES_${_dep_upper}_VERSION)
308        endif()
309
310        if(_ver)
311            _cpack_nuget_debug("  got `${_dep}` dependency version ${_ver}")
312            string(CONCAT _collected_deps "${_collected_deps}" "            <dependency id=\"${_dep}\" version=\"${_ver}\" />\n")
313        endif()
314    endforeach()
315
316    # Render deps into the variable
317    if(_collected_deps)
318        string(CONCAT _CPACK_NUGET_DEPENDENCIES_TAG "<dependencies>\n" "${_collected_deps}" "        </dependencies>")
319    endif()
320
321    # Render the spec file
322    # NOTE The spec filename doesn't matter. Being included into a package,
323    # NuGet will name it properly.
324    _cpack_nuget_debug("Rendering `${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec` file...")
325    configure_file(
326        "${CMAKE_ROOT}/Modules/Internal/CPack/CPack.NuGet.nuspec.in"
327        "${CPACK_TEMPORARY_DIRECTORY}/CPack.NuGet.nuspec"
328        @ONLY
329      )
330endfunction()
331
332function(_cpack_nuget_make_files_tag)
333    set(_files)
334    foreach(_comp IN LISTS ARGN)
335        string(APPEND _files "        <file src=\"${_comp}/**\" target=\".\" />\n")
336    endforeach()
337    set(_CPACK_NUGET_FILES_TAG "<files>\n${_files}    </files>" PARENT_SCOPE)
338endfunction()
339
340find_program(NUGET_EXECUTABLE nuget)
341_cpack_nuget_debug_var(NUGET_EXECUTABLE)
342if(NOT NUGET_EXECUTABLE)
343    message(FATAL_ERROR "NuGet executable not found")
344endif()
345
346# Add details for debug run
347if(CPACK_NUGET_PACKAGE_DEBUG)
348    list(APPEND CPACK_NUGET_PACK_ADDITIONAL_OPTIONS "-Verbosity" "detailed")
349endif()
350
351# Case one: ordinal all-in-one package
352if(CPACK_NUGET_ORDINAL_MONOLITIC)
353    # This variable `CPACK_NUGET_ALL_IN_ONE` set by C++ code:
354    # Meaning to pack all installed files into a single package
355    _cpack_nuget_debug("---[Making an ordinal monolitic package]---")
356    _cpack_nuget_render_spec()
357    execute_process(
358        COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
359        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
360        RESULT_VARIABLE _nuget_result
361      )
362    if(NOT _nuget_result EQUAL 0)
363        message(FATAL_ERROR "Nuget pack failed")
364    endif()
365
366elseif(CPACK_NUGET_ALL_IN_ONE)
367    # This variable `CPACK_NUGET_ALL_IN_ONE` set by C++ code:
368    # Meaning to pack all installed components into a single package
369    _cpack_nuget_debug("---[Making a monolitic package from installed components]---")
370
371    # Prepare the `files` element which include files from several components
372    _cpack_nuget_make_files_tag(${CPACK_NUGET_COMPONENTS})
373    _cpack_nuget_render_spec()
374    execute_process(
375        COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
376        WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
377        RESULT_VARIABLE _nuget_result
378      )
379    if(NOT _nuget_result EQUAL 0)
380        message(FATAL_ERROR "Nuget pack failed")
381    endif()
382
383else()
384    # Is there any grouped component?
385    if(CPACK_NUGET_GROUPS)
386        _cpack_nuget_debug("---[Making grouped component(s) package(s)]---")
387        foreach(_group IN LISTS CPACK_NUGET_GROUPS)
388            _cpack_nuget_debug("Starting to make the package for group `${_group}`")
389            string(MAKE_C_IDENTIFIER "${_group}" _group_up)
390            string(TOUPPER "${_group_up}" _group_up)
391
392            # Render a spec file which includes all components in the current group
393            unset(_CPACK_NUGET_FILES_TAG)
394            _cpack_nuget_make_files_tag(${CPACK_NUGET_${_group_up}_GROUP_COMPONENTS})
395            # Temporary set `CPACK_NUGET_PACKAGE_COMPONENT` to the group name
396            # to properly collect various per group settings
397            set(CPACK_NUGET_PACKAGE_COMPONENT ${_group})
398            _cpack_nuget_render_spec()
399            unset(CPACK_NUGET_PACKAGE_COMPONENT)
400            execute_process(
401                COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
402                WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
403                RESULT_VARIABLE _nuget_result
404              )
405            if(NOT _nuget_result EQUAL 0)
406                message(FATAL_ERROR "Nuget pack failed")
407            endif()
408        endforeach()
409    endif()
410    # Is there any single component package needed?
411    if(CPACK_NUGET_COMPONENTS)
412        _cpack_nuget_debug("---[Making single-component(s) package(s)]---")
413        foreach(_comp IN LISTS CPACK_NUGET_COMPONENTS)
414            _cpack_nuget_debug("Starting to make the package for component `${_comp}`")
415            # Render a spec file which includes only given component
416            unset(_CPACK_NUGET_FILES_TAG)
417            _cpack_nuget_make_files_tag(${_comp})
418            # Temporary set `CPACK_NUGET_PACKAGE_COMPONENT` to the current
419            # component name to properly collect various per group settings
420            set(CPACK_NUGET_PACKAGE_COMPONENT ${_comp})
421            _cpack_nuget_render_spec()
422            unset(CPACK_NUGET_PACKAGE_COMPONENT)
423            execute_process(
424                COMMAND "${NUGET_EXECUTABLE}" pack ${CPACK_NUGET_PACK_ADDITIONAL_OPTIONS}
425                WORKING_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}"
426                RESULT_VARIABLE _nuget_result
427              )
428            if(NOT _nuget_result EQUAL 0)
429                message(FATAL_ERROR "Nuget pack failed")
430            endif()
431        endforeach()
432    endif()
433endif()
434
435file(GLOB_RECURSE GEN_CPACK_OUTPUT_FILES "${CPACK_TEMPORARY_DIRECTORY}/*.nupkg")
436if(NOT GEN_CPACK_OUTPUT_FILES)
437    message(FATAL_ERROR "NuGet package was not generated at `${CPACK_TEMPORARY_DIRECTORY}`!")
438endif()
439
440_cpack_nuget_debug("Generated files: ${GEN_CPACK_OUTPUT_FILES}")
441