浏览代码

CPack: Add Inno Setup generator

Jannik Alber 3 年之前
父节点
当前提交
1d6db66179

+ 1 - 0
.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake

@@ -1,4 +1,5 @@
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_CPACK_INNOSETUP "ON" CACHE STRING "")
   set(CMake_TEST_ISPC "ON" CACHE STRING "")
   set(CMake_TEST_ISPC "ON" CACHE STRING "")
 endif()
 endif()
 set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
 set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")

+ 420 - 0
Help/cpack_gen/innosetup.rst

@@ -0,0 +1,420 @@
+CPack Inno Setup Generator
+--------------------------
+
+.. versionadded:: 3.27
+
+Inno Setup is a free installer for Windows programs by Jordan Russell and
+Martijn Laan (https://jrsoftware.org/isinfo.php).
+
+This documentation explains Inno Setup generator specific options.
+
+The generator provides a lot of options like components. Unfortunately, not
+all features (e.g. component dependencies) are currently supported by
+Inno Setup and they're ignored by the generator for now.
+
+CPack requires Inno Setup 6 or greater and only works on Windows.
+
+Variables specific to CPack Inno Setup generator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can use the following variables to change the behavior of the CPack
+``INNOSETUP`` generator:
+
+
+General
+"""""""
+
+None of the following variables is required to be set for the Inno Setup
+generator to work. If a variable is marked as mandatory below but not set,
+its default value is taken.
+
+The variables can also contain Inno Setup constants like ``{app}``. Please
+refer to the documentation of Inno Setup for more information.
+
+If you're asked to provide the path to any file, you can always give an
+absolute path or in most cases the relative path from the top-level directory
+where all files being installed by an :command:`install` instruction reside.
+
+CPack tries to escape quotes and other special characters for you. However,
+using special characters could cause problems.
+
+The following variable simplifies the usage of Inno Setup in CMake:
+
+.. variable:: CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT
+
+ Inno Setup only uses ``yes`` or ``no`` as boolean formats meanwhile CMake
+ uses a lot of alternative formats like ``ON`` or ``OFF``. Having this option
+ turned on enables an automatic conversion.
+
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  set(CMAKE_INNOSETUP_SETUP_AllowNoIcons OFF)
+
+ If this option is turned on, the following line will be created in the output
+ script: ``AllowNoIcons=no``.
+ Else, the following erroneous line will be created: ``AllowNoIcons=OFF``
+
+ The conversion is enabled in every Inno Setup specific variable.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+
+Setup Specific Variables
+""""""""""""""""""""""""
+
+.. variable:: CPACK_INNOSETUP_ARCHITECTURE
+
+ One of ``x86``, ``x64``, ``arm64`` or ``ia64``. This variable specifies the
+ target architecture of the installer. This also affects the Program Files
+ folder or registry keys being used.
+
+ CPack tries to determine the correct value with a try compile (see
+ :variable:`CMAKE_SIZEOF_VOID_P`), but this option can be manually specified
+ too (especially when using ``ia64`` or cross-platform compilation).
+
+ :Mandatory: Yes
+ :Default: Either ``x86`` or ``x64`` depending on the results of the try-compile
+
+.. variable:: CPACK_INNOSETUP_INSTALL_ROOT
+
+ If you don't want the installer to create the installation directory under
+ Program Files, you've to specify the installation root here.
+
+ The full directory of the installation will be:
+ ``${CPACK_INNOSETUP_INSTALL_ROOT}/${CPACK_PACKAGE_INSTALL_DIRECTORY}``.
+
+ :Mandatory: Yes
+ :Default: ``{autopf}``
+
+.. variable:: CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY
+
+ If turned on, the installer allows the user to change the installation
+ directory providing an extra wizard page.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+.. variable:: CPACK_INNOSETUP_PROGRAM_MENU_FOLDER
+
+ The initial name of the start menu folder being created.
+
+ If this variable is set to ``.``, then no separate folder is created,
+ application shortcuts will appear in the top-level start menu folder.
+
+ :Mandatory: Yes
+ :Default: The value of :variable:`CPACK_PACKAGE_NAME`
+
+.. variable:: CPACK_INNOSETUP_LANGUAGES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of languages you want
+ Inno Setup to include.
+
+ Currently available: ``armenian``, ``brazilianPortuguese``, ``bulgarian``,
+ ``catalan``, ``corsican``, ``czech``, ``danish``, ``dutch``, ``english``,
+ ``finnish``, ``french``, ``german``, ``hebrew``, ``icelandic``, ``italian``,
+ ``japanese``, ``norwegian``, ``polish``, ``portuguese``, ``russian``,
+ ``slovak``, ``slovenian``, ``spanish``, ``turkish`` and ``ukrainian``.
+ This list might differ depending on the version of Inno Setup.
+
+ :Mandatory: Yes
+ :Default: ``english``
+
+.. variable:: CPACK_INNOSETUP_IGNORE_LICENSE_PAGE
+
+ If you don't specify a license file using
+ :variable:`CPACK_RESOURCE_FILE_LICENSE`, CPack uses a file for demonstration
+ purposes. If you want the installer to ignore license files at all, you can
+ enable this option.
+
+ :Mandatory: Yes
+ :Default: ``OFF``
+
+.. variable:: CPACK_INNOSETUP_IGNORE_README_PAGE
+
+ If you don't specify a readme file using
+ :variable:`CPACK_RESOURCE_FILE_README`, CPack uses a file for demonstration
+ purposes. If you want the installer to ignore readme files at all, you can
+ enable this option. Make sure the option is disabled when using
+ a custom readme file.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+.. variable:: CPACK_INNOSETUP_PASSWORD
+
+ Enables password protection and file encryption with the given password.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_USE_MODERN_WIZARD
+
+ Enables the modern look and feel provided by Inno Setup. If this option is
+ turned off, the classic style is used instead. Images and icon files are
+ also affected.
+
+ :Mandatory: Yes
+ :Default: ``OFF`` because of compatibility reasons
+
+.. variable:: CPACK_INNOSETUP_ICON_FILE
+
+ The path to a custom installer ``.ico`` file.
+
+ Use :variable:`CPACK_PACKAGE_ICON` to customize the bitmap file being shown
+ in the wizard.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_SETUP_<directive>
+
+ This group allows adapting any of the ``[Setup]`` section directives provided
+ by Inno Setup where ``directive`` is its name.
+
+ Here are some examples:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_SETUP_WizardSmallImageFile "my_bitmap.bmp")
+  set(CPACK_INNOSETUP_SETUP_AllowNoIcons OFF) # This requires CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT to be on
+
+ All of these variables have higher priority than the others.
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_SETUP_Password "admin")
+  set(CPACK_INNOSETUP_PASSWORD "secret")
+
+ The password will be ``admin`` at the end because ``CPACK_INNOSETUP_PASSWORD``
+ has less priority than ``CPACK_INNOSETUP_SETUP_Password``.
+
+ :Mandatory: No
+
+
+File Specific Variables
+"""""""""""""""""""""""
+
+Although all files being installed by an :command:`install` instruction are
+automatically processed and added to the installer, there are some variables
+to customize the installation process.
+
+Before using executables (only ``.exe`` or ``.com``) in shortcuts
+(e.g. :variable:`CPACK_CREATE_DESKTOP_LINKS`) or ``[Run]`` entries, you've to
+add the raw file name (without path and extension) to
+:variable:`CPACK_PACKAGE_EXECUTABLES` and create a start menu shortcut
+for them.
+
+If you have two files with the same raw name (e.g. ``a/executable.exe`` and
+``b/executable.com``), an entry in the section is created twice. This will
+result in undefined behavior and is not recommended.
+
+.. variable:: CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS
+
+ This variable should contain a
+ :ref:`semicolon-separated list <CMake Language Lists>` of pairs ``path``,
+ ``instruction`` and can be used to customize the install command being
+ automatically created for each file or directory.
+
+ CPack creates the following Inno Setup instruction for every file...
+
+ .. code-block::
+
+  Source: "absolute\path\to\my_file.txt"; DestDir: "{app}"; Flags: ignoreversion
+
+ ...and the following line for every directory:
+
+ .. code-block::
+
+  Name: "{app}\my_folder"
+
+ You might want to change the destination directory or the flags of
+ ``my_file.txt``. Since we can also provide a relative path, the line you'd
+ like to have, is the following:
+
+ .. code-block::
+
+  Source: "my_file.txt"; DestDir: "{userdocs}"; Flags: ignoreversion uninsneveruninstall
+
+ You would do this by using ``my_file.txt`` as ``path`` and
+ ``Source: "my_file.txt"; DestDir: "{userdocs}"; Flags: ignoreversion uninsneveruninstall``
+ as ``instruction``.
+
+ You've to take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+ So the CMake command would be:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS "my_file.txt;Source: \\\"my_file.txt\\\"\\; DestDir: \\\"{userdocs}\\\"\\; Flags: ignoreversion uninsneveruninstall")
+
+ To improve readability, you should go around the escaping problem by using
+ :variable:`CPACK_VERBATIM_VARIABLES` or by placing the instruction into a
+ separate CPack project config file.
+
+ If you customize the install instruction of a specific file, you lose the
+ connection to its component. To go around, manually add
+ ``Components: <component>``. You also need to add its shortcuts and ``[Run]``
+ entries by yourself in a custom section, since the executable won't be found
+ anymore by :variable:`CPACK_PACKAGE_EXECUTABLES`.
+
+ Here's another example (Note: You've to go around the escaping problem for
+ the example to work):
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS
+      "component1/my_folder" "Name: \"{userdocs}\\my_folder\"\; Components: component1"
+      "component2/my_folder2/my_file.txt" "Source: \"component2\\my_folder2\\my_file.txt\"\; DestDir: \"{app}\\my_folder2\\my_file.txt\"\; Flags: ignoreversion uninsneveruninstall\; Components: component2")
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_MENU_LINKS
+
+ This variable should contain a
+ :ref:`semicolon-separated list <CMake Language Lists>` of pairs ``link``,
+ ``link name`` and can be used to add shortcuts into the start menu folder
+ beside those of the executables (see :variable:`CPACK_PACKAGE_EXECUTABLES`).
+ While ``link name`` is the label, ``link`` can be a URL or a path relative to
+ the installation directory.
+
+ Here's an example:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_MENU_LINKS
+      "doc/cmake-@CMake_VERSION_MAJOR@.@CMake_VERSION_MINOR@/cmake.html"
+      "CMake Help" "https://cmake.org" "CMake Web Site")
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_CREATE_UNINSTALL_LINK
+
+ If this option is turned on, a shortcut to the application's uninstaller is
+ automatically added to the start menu folder.
+
+ :Mandatory: Yes
+ :Default: ``OFF``
+
+.. variable:: CPACK_INNOSETUP_RUN_EXECUTABLES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of executables being
+ specified in :variable:`CPACK_PACKAGE_EXECUTABLES` which the user can run
+ when the installer finishes.
+
+ They're internally added to the ``[Run]`` section.
+
+ :Mandatory: No
+
+
+Components Specific Variables
+"""""""""""""""""""""""""""""
+
+The generator supports components and also downloaded components. However,
+there are some features of components that aren't supported yet (especially
+component dependencies). These variables are ignored for now.
+
+CPack will change a component's name in Inno Setup if it has a parent group
+for technical reasons. Consider using ``group\component`` as component name in
+Inno Setup scripts if you have the component ``component`` and its parent
+group ``group``.
+
+Here are some additional variables for components:
+
+.. variable::  CPACK_INNOSETUP_<compName>_INSTALL_DIRECTORY
+
+ If you don't want the component ``compName`` to be installed under ``{app}``,
+ you've to specify its installation directory here.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_VERIFY_DOWNLOADS
+
+ This option only affects downloaded components.
+
+ If this option is turned on, the hashes of the downloaded archives are
+ calculated during compile and
+ download time. The installer will only proceed if they match.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+
+Compilation and Scripting Specific Variables
+""""""""""""""""""""""""""""""""""""""""""""
+
+.. variable:: CPACK_INNOSETUP_EXECUTABLE
+
+ The filename of the Inno Setup Script Compiler command.
+
+ :Mandatory: Yes
+ :Default: ``ISCC``
+
+.. variable:: CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of extra
+ command-line options for the Inno Setup Script Compiler command.
+
+ For example: ``/Qp;/Smysigntool=$p``
+
+ Take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_DEFINE_<macro>
+
+ This group allows to add custom define directives as command-line options to
+ the Inno Setup Preprocessor command. Each entry emulates a
+ ``#define public <macro>`` directive. Its macro is accessible from anywhere
+ (``public``), so it can also be used in extra script files.
+
+ Macro names must not contain any special characters. Refer to the Inno Setup
+ Preprocessor documentation for the detailed rules.
+
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  # The following line emulates: #define public MyMacro "Hello, World!"
+  set(CPACK_INNOSETUP_DEFINE_MyMacro "Hello, World!")
+
+ At this point, you can use ``MyMacro`` anywhere. For example in the following
+ extra script:
+
+ .. code-block::
+
+  AppComments={#emit "'My Macro' has the value: " + MyMacro}
+
+ Take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_EXTRA_SCRIPTS
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of paths to
+ additional ``.iss`` script files to be processed.
+
+ They're internally included at the top of the output script file using a
+ ``#include`` directive.
+
+ You can add any section in your file to extend the installer (e.g. adding
+ additional tasks or registry keys). Prefer using
+ :variable:`CPACK_INNOSETUP_SETUP_<directive>` when extending the
+ ``[Setup]`` section.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_CODE_FILES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of paths to
+ additional Pascal files to be processed.
+
+ This variable is actually the same as
+ :variable:`CPACK_INNOSETUP_EXTRA_SCRIPTS`, except you don't have to
+ add ``[Code]`` at the top of your file. Never change the current section in
+ a code file. This will result in undefined behavior! Treat them as normal
+ Pascal scripts instead.
+
+ Code files are included at the very bottom of the output script.
+
+ :Mandatory: No

+ 1 - 0
Help/manual/cpack-generators.7.rst

@@ -20,6 +20,7 @@ Generators
    /cpack_gen/dmg
    /cpack_gen/dmg
    /cpack_gen/external
    /cpack_gen/external
    /cpack_gen/freebsd
    /cpack_gen/freebsd
+   /cpack_gen/innosetup
    /cpack_gen/ifw
    /cpack_gen/ifw
    /cpack_gen/nsis
    /cpack_gen/nsis
    /cpack_gen/nuget
    /cpack_gen/nuget

+ 12 - 0
Help/release/dev/cpack-innosetup.rst

@@ -0,0 +1,12 @@
+cpack-innosetup
+---------------
+
+* The :cpack_gen:`CPack Inno Setup Generator` was added to package using
+  Inno Setup.
+
+  The new generator adds:
+
+  * A lot of options to customize the Inno Setup installer (e.g. custom
+    installation rules)
+  * Start menu and desktop shortcuts
+  * Components (and also downloaded components)

+ 17 - 7
Modules/CPack.cmake

@@ -262,8 +262,8 @@ installers.  The most commonly-used variables are:
   Lists each of the executables and associated text label to be used to
   Lists each of the executables and associated text label to be used to
   create Start Menu shortcuts.  For example, setting this to the list
   create Start Menu shortcuts.  For example, setting this to the list
   ``ccmake;CMake`` will create a shortcut named "CMake" that will execute the
   ``ccmake;CMake`` will create a shortcut named "CMake" that will execute the
-  installed executable :program:`ccmake`.  Not all CPack generators use it (at least
-  NSIS, and WIX do).
+  installed executable :program:`ccmake`. Not all CPack generators use it (at least
+  NSIS, Inno Setup and WIX do).
 
 
 .. variable:: CPACK_STRIP_FILES
 .. variable:: CPACK_STRIP_FILES
 
 
@@ -738,14 +738,16 @@ if(NOT CPACK_GENERATOR)
         )
         )
     endif()
     endif()
   else()
   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_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)
+    option(CPACK_BINARY_7Z        "Enable to build 7-Zip packages" OFF)
+    option(CPACK_BINARY_NSIS      "Enable to build NSIS packages" ON)
+    option(CPACK_BINARY_INNOSETUP "Enable to build Inno Setup packages" OFF)
+    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)
     mark_as_advanced(
     mark_as_advanced(
       CPACK_BINARY_7Z
       CPACK_BINARY_7Z
       CPACK_BINARY_NSIS
       CPACK_BINARY_NSIS
+      CPACK_BINARY_INNOSETUP
       CPACK_BINARY_NUGET
       CPACK_BINARY_NUGET
       CPACK_BINARY_WIX
       CPACK_BINARY_WIX
       CPACK_BINARY_ZIP
       CPACK_BINARY_ZIP
@@ -762,6 +764,7 @@ if(NOT CPACK_GENERATOR)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_FREEBSD      FREEBSD)
   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_IFW          IFW)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NSIS         NSIS)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NSIS         NSIS)
+  cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_INNOSETUP    INNOSETUP)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NUGET        NuGet)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NUGET        NuGet)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_PRODUCTBUILD productbuild)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_PRODUCTBUILD productbuild)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_RPM          RPM)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_RPM          RPM)
@@ -869,6 +872,13 @@ if(NOT DEFINED CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE
   unset(_CPack_CMP0133)
   unset(_CPack_CMP0133)
 endif()
 endif()
 
 
+# Inno Setup specific variables
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  _cpack_set_default(CPACK_INNOSETUP_ARCHITECTURE "x86")
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
+  _cpack_set_default(CPACK_INNOSETUP_ARCHITECTURE "x64")
+endif()
+
 # WiX specific variables
 # WiX specific variables
 _cpack_set_default(CPACK_WIX_SIZEOF_VOID_P "${CMAKE_SIZEOF_VOID_P}")
 _cpack_set_default(CPACK_WIX_SIZEOF_VOID_P "${CMAKE_SIZEOF_VOID_P}")
 
 

+ 88 - 0
Modules/Internal/CPack/ISComponents.pas

@@ -0,0 +1,88 @@
+{ Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. }
+
+function CPackGetCustomInstallationMessage(Param: String): String;
+begin
+  Result := SetupMessage(msgCustomInstallation);
+end;
+
+{ Downloaded components }
+#ifdef CPackDownloadCount
+const
+  NO_PROGRESS_BOX = 4;
+  RESPOND_YES_TO_ALL = 16;
+var
+  CPackDownloadPage: TDownloadWizardPage;
+  CPackShell: Variant;
+
+<event('InitializeWizard')>
+procedure CPackInitializeWizard();
+begin
+  CPackDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+  CPackShell := CreateOleObject('Shell.Application');
+end;
+
+<event('NextButtonClick')>
+function CPackNextButtonClick(CurPageID: Integer): Boolean;
+begin
+  if CurPageID = wpReady then
+  begin
+    CPackDownloadPage.Clear;
+    CPackDownloadPage.Show;
+
+#sub AddDownload
+  if WizardIsComponentSelected('{#CPackDownloadComponents[i]}') then
+    #emit "CPackDownloadPage.Add('" + CPackDownloadUrls[i] + "', '" + CPackDownloadArchives[i] + ".zip', '" + CPackDownloadHashes[i] + "');"
+#endsub
+#define i
+#for {i = 0; i < CPackDownloadCount; i++} AddDownload
+#undef i
+
+    try
+      try
+        CPackDownloadPage.Download;
+        Result := True;
+      except
+        if not CPackDownloadPage.AbortedByUser then
+          SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
+
+        Result := False;
+      end;
+    finally
+      CPackDownloadPage.Hide;
+    end;
+  end else
+    Result := True;
+end;
+
+procedure CPackExtractFile(ArchiveName, FileName: String);
+var
+  ZipFileName: String;
+  ZipFile: Variant;
+  Item: Variant;
+  TargetFolderName: String;
+  TargetFolder: Variant;
+begin
+  TargetFolderName := RemoveBackslashUnlessRoot(ExpandConstant('{tmp}\' + ArchiveName + '\' + ExtractFileDir(FileName)));
+  ZipFileName := ExpandConstant('{tmp}\' + ArchiveName + '.zip');
+
+  if not DirExists(TargetFolderName) then
+    if not ForceDirectories(TargetFolderName) then
+      RaiseException(Format('Target path "%s" cannot be created', [TargetFolderName]));
+
+  ZipFile := CPackShell.NameSpace(ZipFileName);
+  if VarIsClear(ZipFile) then
+    RaiseException(Format('Cannot open ZIP file "%s" or does not exist', [ZipFileName]));
+
+  Item := ZipFile.ParseName(FileName);
+  if VarIsClear(Item) then
+    RaiseException(Format('Cannot find "%s" in "%s" ZIP file', [FileName, ZipFileName]));
+
+  TargetFolder := CPackShell.NameSpace(TargetFolderName);
+  if VarIsClear(TargetFolder) then
+    RaiseException(Format('Target path "%s" does not exist', [TargetFolderName]));
+
+  TargetFolder.CopyHere(Item, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
+end;
+
+#endif

+ 34 - 0
Modules/Internal/CPack/ISScript.template.in

@@ -0,0 +1,34 @@
+; Script generated by the CPack Inno Setup generator.
+; All changes made in this file will be lost when CPack is run again.
+
+@CPACK_INNOSETUP_INCLUDES_INTERNAL@
+
+[Setup]
+@CPACK_INNOSETUP_SETUP_INTERNAL@
+
+[Languages]
+@CPACK_INNOSETUP_LANGUAGES_INTERNAL@
+
+[Dirs]
+@CPACK_INNOSETUP_DIRS_INTERNAL@
+
+[Files]
+@CPACK_INNOSETUP_FILES_INTERNAL@
+
+[Types]
+@CPACK_INNOSETUP_TYPES_INTERNAL@
+
+[Components]
+@CPACK_INNOSETUP_COMPONENTS_INTERNAL@
+
+[Tasks]
+@CPACK_INNOSETUP_TASKS_INTERNAL@
+
+[Icons]
+@CPACK_INNOSETUP_ICONS_INTERNAL@
+
+[Run]
+@CPACK_INNOSETUP_RUN_INTERNAL@
+
+[Code]
+@CPACK_INNOSETUP_CODE_INTERNAL@

+ 1 - 0
Source/CMakeLists.txt

@@ -1018,6 +1018,7 @@ add_library(
   CPack/cmCPackGeneratorFactory.cxx
   CPack/cmCPackGeneratorFactory.cxx
   CPack/cmCPackGenerator.cxx
   CPack/cmCPackGenerator.cxx
   CPack/cmCPackLog.cxx
   CPack/cmCPackLog.cxx
+  CPack/cmCPackInnoSetupGenerator.cxx
   CPack/cmCPackNSISGenerator.cxx
   CPack/cmCPackNSISGenerator.cxx
   CPack/cmCPackNuGetGenerator.cxx
   CPack/cmCPackNuGetGenerator.cxx
   CPack/cmCPackSTGZGenerator.cxx
   CPack/cmCPackSTGZGenerator.cxx

+ 5 - 0
Source/CPack/cmCPackGeneratorFactory.cxx

@@ -13,6 +13,7 @@
 #include "cmCPackDebGenerator.h"
 #include "cmCPackDebGenerator.h"
 #include "cmCPackExternalGenerator.h"
 #include "cmCPackExternalGenerator.h"
 #include "cmCPackGenerator.h"
 #include "cmCPackGenerator.h"
+#include "cmCPackInnoSetupGenerator.h"
 #include "cmCPackLog.h"
 #include "cmCPackLog.h"
 #include "cmCPackNSISGenerator.h"
 #include "cmCPackNSISGenerator.h"
 #include "cmCPackNuGetGenerator.h"
 #include "cmCPackNuGetGenerator.h"
@@ -60,6 +61,10 @@ cmCPackGeneratorFactory::cmCPackGeneratorFactory()
     this->RegisterGenerator("STGZ", "Self extracting Tar GZip compression",
     this->RegisterGenerator("STGZ", "Self extracting Tar GZip compression",
                             cmCPackSTGZGenerator::CreateGenerator);
                             cmCPackSTGZGenerator::CreateGenerator);
   }
   }
+  if (cmCPackInnoSetupGenerator::CanGenerate()) {
+    this->RegisterGenerator("INNOSETUP", "Inno Setup packages",
+                            cmCPackInnoSetupGenerator::CreateGenerator);
+  }
   if (cmCPackNSISGenerator::CanGenerate()) {
   if (cmCPackNSISGenerator::CanGenerate()) {
     this->RegisterGenerator("NSIS", "Null Soft Installer",
     this->RegisterGenerator("NSIS", "Null Soft Installer",
                             cmCPackNSISGenerator::CreateGenerator);
                             cmCPackNSISGenerator::CreateGenerator);

+ 1159 - 0
Source/CPack/cmCPackInnoSetupGenerator.cxx

@@ -0,0 +1,1159 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmCPackInnoSetupGenerator.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstdlib>
+#include <ostream>
+#include <stack>
+#include <utility>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmCPackComponentGroup.h"
+#include "cmCPackLog.h"
+#include "cmDuration.h"
+#include "cmGeneratedFileStream.h"
+#include "cmList.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+cmCPackInnoSetupGenerator::cmCPackInnoSetupGenerator() = default;
+cmCPackInnoSetupGenerator::~cmCPackInnoSetupGenerator() = default;
+
+bool cmCPackInnoSetupGenerator::CanGenerate()
+{
+  // Inno Setup is only available for Windows
+#ifdef _WIN32
+  return true;
+#else
+  return false;
+#endif
+}
+
+int cmCPackInnoSetupGenerator::InitializeInternal()
+{
+  if (cmIsOn(GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Inno Setup Generator cannot work with "
+                  "CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
+                  "This option will be reset to 0 (for this generator only)."
+                    << std::endl);
+    SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
+  }
+
+  std::vector<std::string> path;
+
+#ifdef _WIN32
+  path.push_back("C:\\Program Files (x86)\\Inno Setup 5");
+  path.push_back("C:\\Program Files (x86)\\Inno Setup 6");
+#endif
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_EXECUTABLE", "ISCC");
+  const std::string& isccPath = cmSystemTools::FindProgram(
+    GetOption("CPACK_INNOSETUP_EXECUTABLE"), path, false);
+
+  if (isccPath.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Cannot find Inno Setup compiler ISCC: "
+                  "likely it is not installed, or not in your PATH"
+                    << std::endl);
+
+    return 0;
+  }
+
+  const std::string isccCmd = cmStrCat(QuotePath(isccPath), "/?");
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+                "Test Inno Setup version: " << isccCmd << std::endl);
+  std::string output;
+  cmSystemTools::RunSingleCommand(isccCmd, &output, &output, nullptr, nullptr,
+                                  this->GeneratorVerbose, cmDuration::zero());
+  cmsys::RegularExpression vRex("Inno Setup ([0-9]+)");
+  if (!vRex.find(output)) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem checking Inno Setup version with command: "
+                    << isccCmd << std::endl
+                    << "Have you downloaded Inno Setup from "
+                       "https://jrsoftware.org/isinfo.php?"
+                    << std::endl);
+    return 0;
+  }
+
+  const int isccVersion = atoi(vRex.match(1).c_str());
+  const int minIsccVersion = 6;
+  cmCPackLogger(cmCPackLog::LOG_DEBUG,
+                "Inno Setup Version: " << isccVersion << std::endl);
+
+  if (isccVersion < minIsccVersion) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "CPack requires Inno Setup Version 6 or greater. "
+                  "Inno Setup found on the system was: "
+                    << isccVersion << std::endl);
+    return 0;
+  }
+
+  SetOption("CPACK_INSTALLER_PROGRAM", isccPath);
+
+  return this->Superclass::InitializeInternal();
+}
+
+int cmCPackInnoSetupGenerator::PackageFiles()
+{
+  // Includes
+  if (IsSet("CPACK_INNOSETUP_EXTRA_SCRIPTS")) {
+    const cmList extraScripts(GetOption("CPACK_INNOSETUP_EXTRA_SCRIPTS"));
+
+    for (const std::string& i : extraScripts) {
+      includeDirectives.push_back(cmStrCat(
+        "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
+    }
+  }
+
+  // [Languages] section
+  SetOptionIfNotSet("CPACK_INNOSETUP_LANGUAGES", "english");
+  const cmList languages(GetOption("CPACK_INNOSETUP_LANGUAGES"));
+  for (std::string i : languages) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(i);
+
+    if (cmSystemTools::LowerCase(i) == "english") {
+      params["MessagesFile"] = "\"compiler:Default.isl\"";
+    } else {
+      i[0] = static_cast<char>(std::toupper(i[0]));
+      params["MessagesFile"] = cmStrCat("\"compiler:Languages\\", i, ".isl\"");
+    }
+
+    languageInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  if (!Components.empty() && !ProcessComponents()) {
+    return false;
+  }
+
+  if (!(ProcessSetupSection() && ProcessFiles())) {
+    return false;
+  }
+
+  // [Code] section
+  if (IsSet("CPACK_INNOSETUP_CODE_FILES")) {
+    const cmList codeFiles(GetOption("CPACK_INNOSETUP_CODE_FILES"));
+
+    for (const std::string& i : codeFiles) {
+      codeIncludes.push_back(cmStrCat(
+        "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
+    }
+  }
+
+  return ConfigureISScript() && Compile();
+}
+
+bool cmCPackInnoSetupGenerator::ProcessSetupSection()
+{
+  if (!RequireOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY")) {
+    return false;
+  }
+  setupDirectives["AppId"] = GetOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY");
+
+  if (!RequireOption("CPACK_PACKAGE_NAME")) {
+    return false;
+  }
+  setupDirectives["AppName"] = GetOption("CPACK_PACKAGE_NAME");
+  setupDirectives["UninstallDisplayName"] = GetOption("CPACK_PACKAGE_NAME");
+
+  if (!RequireOption("CPACK_PACKAGE_VERSION")) {
+    return false;
+  }
+  setupDirectives["AppVersion"] = GetOption("CPACK_PACKAGE_VERSION");
+
+  if (!RequireOption("CPACK_PACKAGE_VENDOR")) {
+    return false;
+  }
+  setupDirectives["AppPublisher"] = GetOption("CPACK_PACKAGE_VENDOR");
+
+  if (IsSet("CPACK_PACKAGE_HOMEPAGE_URL")) {
+    setupDirectives["AppPublisherURL"] =
+      GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+    setupDirectives["AppSupportURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+    setupDirectives["AppUpdatesURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE", "OFF");
+  if (IsSet("CPACK_RESOURCE_FILE_LICENSE") &&
+      !GetOption("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE").IsOn()) {
+    setupDirectives["LicenseFile"] = cmSystemTools::ConvertToWindowsOutputPath(
+      GetOption("CPACK_RESOURCE_FILE_LICENSE"));
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_README_PAGE", "ON");
+  if (IsSet("CPACK_RESOURCE_FILE_README") &&
+      !GetOption("CPACK_INNOSETUP_IGNORE_README_PAGE").IsOn()) {
+    setupDirectives["InfoBeforeFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_RESOURCE_FILE_README"));
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_USE_MODERN_WIZARD", "OFF");
+  if (GetOption("CPACK_INNOSETUP_USE_MODERN_WIZARD").IsOn()) {
+    setupDirectives["WizardStyle"] = "modern";
+  } else {
+    setupDirectives["WizardStyle"] = "classic";
+    setupDirectives["WizardSmallImageFile"] =
+      "compiler:WizClassicSmallImage.bmp";
+    setupDirectives["WizardImageFile"] = "compiler:WizClassicImage.bmp";
+    setupDirectives["SetupIconFile"] = "compiler:SetupClassicIcon.ico";
+  }
+
+  if (IsSet("CPACK_INNOSETUP_ICON_FILE")) {
+    setupDirectives["SetupIconFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_INNOSETUP_ICON_FILE"));
+  }
+
+  if (IsSet("CPACK_PACKAGE_ICON")) {
+    setupDirectives["WizardSmallImageFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_PACKAGE_ICON"));
+  }
+
+  if (!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY")) {
+    return false;
+  }
+  SetOptionIfNotSet("CPACK_INNOSETUP_INSTALL_ROOT", "{autopf}");
+  setupDirectives["DefaultDirName"] =
+    cmSystemTools::ConvertToWindowsOutputPath(
+      cmStrCat(GetOption("CPACK_INNOSETUP_INSTALL_ROOT"), '\\',
+               GetOption("CPACK_PACKAGE_INSTALL_DIRECTORY")));
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY", "ON");
+  if (GetOption("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY").IsOff()) {
+    setupDirectives["DisableDirPage"] = "yes";
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER",
+                    GetOption("CPACK_PACKAGE_NAME"));
+  if (GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER") == ".") {
+    setupDirectives["DisableProgramGroupPage"] = "yes";
+    toplevelProgramFolder = true;
+  } else {
+    setupDirectives["DefaultGroupName"] =
+      GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER");
+    toplevelProgramFolder = false;
+  }
+
+  if (IsSet("CPACK_INNOSETUP_PASSWORD")) {
+    setupDirectives["Password"] = GetOption("CPACK_INNOSETUP_PASSWORD");
+    setupDirectives["Encryption"] = "yes";
+  }
+
+  /*
+   * These directives can only be modified using the
+   * CPACK_INNOSETUP_SETUP_<directive> variables
+   */
+  setupDirectives["ShowLanguageDialog"] = "auto";
+  setupDirectives["AllowNoIcons"] = "yes";
+  setupDirectives["Compression"] = "lzma";
+  setupDirectives["SolidCompression"] = "yes";
+
+  // Output file and directory
+  if (!RequireOption("CPACK_PACKAGE_FILE_NAME")) {
+    return false;
+  }
+  setupDirectives["OutputBaseFilename"] = GetOption("CPACK_PACKAGE_FILE_NAME");
+
+  if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY")) {
+    return false;
+  }
+  setupDirectives["OutputDir"] = cmSystemTools::ConvertToWindowsOutputPath(
+    GetOption("CPACK_TOPLEVEL_DIRECTORY"));
+
+  setupDirectives["SourceDir"] =
+    cmSystemTools::ConvertToWindowsOutputPath(toplevel);
+
+  // Target architecture
+  if (!RequireOption("CPACK_INNOSETUP_ARCHITECTURE")) {
+    return false;
+  }
+
+  const std::string& architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE");
+  if (architecture != "x86" && architecture != "x64" &&
+      architecture != "arm64" && architecture != "ia64") {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "CPACK_INNOSETUP_ARCHITECTURE must be either x86, x64, "
+                  "arm64 or ia64"
+                    << std::endl);
+    return false;
+  }
+
+  // The following directives must not be set to target x86
+  if (architecture != "x86") {
+    setupDirectives["ArchitecturesAllowed"] = architecture;
+    setupDirectives["ArchitecturesInstallIn64BitMode"] = architecture;
+  }
+
+  /*
+   * Handle custom directives (they have higher priority than other variables,
+   * so they have to be processed after all other variables)
+   */
+  for (const std::string& i : GetOptions()) {
+    if (cmHasPrefix(i, "CPACK_INNOSETUP_SETUP_")) {
+      const std::string& directive =
+        i.substr(cmStrLen("CPACK_INNOSETUP_SETUP_"));
+      setupDirectives[directive] = GetOption(i);
+    }
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ProcessFiles()
+{
+  std::map<std::string, std::string> customFileInstructions;
+  if (IsSet("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS")) {
+    const cmList instructions(
+      GetOption("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS"));
+    if (instructions.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS should "
+                    "contain pairs of <path> and <instruction>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = instructions.begin(); it != instructions.end(); ++it) {
+      const std::string& key =
+        QuotePath(cmSystemTools::CollapseFullPath(*it, toplevel));
+      customFileInstructions[key] = *(++it);
+    }
+  }
+
+  const std::string& iconsPrefix =
+    toplevelProgramFolder ? "{autoprograms}\\" : "{group}\\";
+
+  std::map<std::string, std::string> icons;
+  if (IsSet("CPACK_PACKAGE_EXECUTABLES")) {
+    const cmList executables(GetOption("CPACK_PACKAGE_EXECUTABLES"));
+    if (executables.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_PACKAGE_EXECUTABLES should should contain pairs of "
+                    "<executable> and <text label>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = executables.begin(); it != executables.end(); ++it) {
+      const std::string& key = *it;
+      icons[key] = *(++it);
+    }
+  }
+
+  std::vector<std::string> desktopIcons;
+  if (IsSet("CPACK_CREATE_DESKTOP_LINKS")) {
+    cmExpandList(GetOption("CPACK_CREATE_DESKTOP_LINKS"), desktopIcons);
+  }
+
+  std::vector<std::string> runExecutables;
+  if (IsSet("CPACK_INNOSETUP_RUN_EXECUTABLES")) {
+    cmExpandList(GetOption("CPACK_INNOSETUP_RUN_EXECUTABLES"), runExecutables);
+  }
+
+  for (const std::string& i : files) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    std::string toplevelDirectory;
+    std::string outputDir;
+    cmCPackComponent* component = nullptr;
+    std::string componentParam;
+    if (!Components.empty()) {
+      const std::string& fileName = cmSystemTools::RelativePath(toplevel, i);
+      const std::string::size_type pos = fileName.find('/');
+
+      // Use the custom component install directory if we have one
+      if (pos != std::string::npos) {
+        const std::string& componentName = fileName.substr(0, pos);
+        component = &Components[componentName];
+
+        toplevelDirectory =
+          cmSystemTools::CollapseFullPath(componentName, toplevel);
+        outputDir = CustomComponentInstallDirectory(component);
+        componentParam =
+          CreateRecursiveComponentPath(component->Group, component->Name);
+
+        if (component->IsHidden && component->IsDisabledByDefault) {
+          continue;
+        }
+
+        if (component->IsHidden) {
+          componentParam.clear();
+        }
+      } else {
+        // Don't install component directories
+        continue;
+      }
+    } else {
+      toplevelDirectory = toplevel;
+      outputDir = "{app}";
+    }
+
+    if (!componentParam.empty()) {
+      params["Components"] = componentParam;
+    }
+
+    if (cmSystemTools::FileIsDirectory(i)) {
+      // Custom instructions replace the automatic generated instructions
+      if (customFileInstructions.count(QuotePath(i))) {
+        dirInstructions.push_back(customFileInstructions[QuotePath(i)]);
+      } else {
+        std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
+          cmStrCat(outputDir, '\\',
+                   cmSystemTools::RelativePath(toplevelDirectory, i)));
+        cmStripSuffixIfExists(destDir, '\\');
+
+        params["Name"] = QuotePath(destDir);
+
+        dirInstructions.push_back(ISKeyValueLine(params));
+      }
+    } else {
+      // Custom instructions replace the automatic generated instructions
+      if (customFileInstructions.count(QuotePath(i))) {
+        fileInstructions.push_back(customFileInstructions[QuotePath(i)]);
+      } else {
+        std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
+          cmStrCat(outputDir, '\\',
+                   cmSystemTools::GetParentDirectory(
+                     cmSystemTools::RelativePath(toplevelDirectory, i))));
+        cmStripSuffixIfExists(destDir, '\\');
+
+        params["DestDir"] = QuotePath(destDir);
+
+        if (component != nullptr && component->IsDownloaded) {
+          const std::string& archiveName =
+            cmSystemTools::GetFilenameWithoutLastExtension(
+              component->ArchiveFile);
+          const std::string& relativePath =
+            cmSystemTools::RelativePath(toplevelDirectory, i);
+
+          params["Source"] =
+            QuotePath(cmStrCat("{tmp}\\", archiveName, '\\', relativePath));
+          params["ExternalSize"] =
+            std::to_string(cmSystemTools::FileLength(i));
+          params["Flags"] = "external ignoreversion";
+          params["BeforeInstall"] =
+            cmStrCat("CPackExtractFile('", archiveName, "', '",
+                     cmRemoveQuotes(cmSystemTools::ConvertToWindowsOutputPath(
+                       relativePath)),
+                     "')");
+        } else {
+          params["Source"] = QuotePath(i);
+          params["Flags"] = "ignoreversion";
+        }
+
+        fileInstructions.push_back(ISKeyValueLine(params));
+
+        // Icon
+        const std::string& name =
+          cmSystemTools::GetFilenameWithoutLastExtension(i);
+        const std::string& extension =
+          cmSystemTools::GetFilenameLastExtension(i);
+        if ((extension == ".exe" || extension == ".com") && // only .exe, .com
+            icons.count(name)) {
+          cmCPackInnoSetupKeyValuePairs iconParams;
+
+          iconParams["Name"] = QuotePath(cmStrCat(iconsPrefix, icons[name]));
+          iconParams["Filename"] =
+            QuotePath(cmStrCat(destDir, '\\', name, extension));
+
+          if (!componentParam.empty()) {
+            iconParams["Components"] = componentParam;
+          }
+
+          iconInstructions.push_back(ISKeyValueLine(iconParams));
+
+          // Desktop icon
+          if (std::find(desktopIcons.begin(), desktopIcons.end(), name) !=
+              desktopIcons.end()) {
+            iconParams["Name"] =
+              QuotePath(cmStrCat("{autodesktop}\\", icons[name]));
+            iconParams["Tasks"] = "desktopicon";
+
+            if (!componentParam.empty() &&
+                std::find(desktopIconComponents.begin(),
+                          desktopIconComponents.end(),
+                          componentParam) == desktopIconComponents.end()) {
+              desktopIconComponents.push_back(componentParam);
+            }
+            iconInstructions.push_back(ISKeyValueLine(iconParams));
+          }
+
+          // [Run] section
+          if (std::find(runExecutables.begin(), runExecutables.end(), name) !=
+              runExecutables.end()) {
+            cmCPackInnoSetupKeyValuePairs runParams;
+
+            runParams["Filename"] = iconParams["Filename"];
+            runParams["Description"] = cmStrCat(
+              "\"{cm:LaunchProgram,", PrepareForConstant(icons[name]), "}\"");
+            runParams["Flags"] = "nowait postinstall skipifsilent";
+
+            if (!componentParam.empty()) {
+              runParams["Components"] = componentParam;
+            }
+
+            runInstructions.push_back(ISKeyValueLine(runParams));
+          }
+        }
+      }
+    }
+  }
+
+  // Additional icons
+  static cmsys::RegularExpression urlRegex(
+    "^(mailto:|(ftps?|https?|news)://).*$");
+
+  if (IsSet("CPACK_INNOSETUP_MENU_LINKS")) {
+    const cmList menuIcons(GetOption("CPACK_INNOSETUP_MENU_LINKS"));
+    if (menuIcons.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_INNOSETUP_MENU_LINKS should "
+                    "contain pairs of <shortcut target> and <shortcut label>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = menuIcons.begin(); it != menuIcons.end(); ++it) {
+      const std::string& target = *it;
+      const std::string& label = *(++it);
+      cmCPackInnoSetupKeyValuePairs params;
+
+      params["Name"] = QuotePath(cmStrCat(iconsPrefix, label));
+      if (urlRegex.find(target)) {
+        params["Filename"] = Quote(target);
+      } else {
+        std::string dir = "{app}";
+        std::string componentName;
+        for (const auto& i : Components) {
+          if (cmSystemTools::FileExists(cmSystemTools::CollapseFullPath(
+                cmStrCat(i.second.Name, '\\', target), toplevel))) {
+            dir = CustomComponentInstallDirectory(&i.second);
+            componentName =
+              CreateRecursiveComponentPath(i.second.Group, i.second.Name);
+
+            if (i.second.IsHidden && i.second.IsDisabledByDefault) {
+              goto continueOuterLoop;
+            } else if (i.second.IsHidden) {
+              componentName.clear();
+            }
+
+            break;
+          }
+        }
+
+        params["Filename"] = QuotePath(cmStrCat(dir, '\\', target));
+
+        if (!componentName.empty()) {
+          params["Components"] = componentName;
+        }
+      }
+
+      iconInstructions.push_back(ISKeyValueLine(params));
+    continueOuterLoop:;
+    }
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK", "OFF");
+  if (GetOption("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK").IsOn()) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = QuotePath(
+      cmStrCat(iconsPrefix, "{cm:UninstallProgram,",
+               PrepareForConstant(GetOption("CPACK_PACKAGE_NAME")), '}'));
+    params["Filename"] = "\"{uninstallexe}\"";
+
+    iconInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ProcessComponents()
+{
+  codeIncludes.push_back("{ The following lines are required by CPack because "
+                         "this script uses components }");
+
+  // Installation types
+  bool noTypes = true;
+  std::vector<cmCPackInstallationType*> types(InstallationTypes.size());
+  for (auto& i : InstallationTypes) {
+    noTypes = false;
+    types[i.second.Index - 1] = &i.second;
+  }
+
+  std::vector<std::string> allTypes; // For required components
+  for (cmCPackInstallationType* i : types) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(i->Name);
+    params["Description"] = Quote(i->DisplayName);
+
+    allTypes.push_back(i->Name);
+    typeInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  if (!noTypes) {
+    // Inno Setup requires the "custom" type
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = "\"custom\"";
+    params["Description"] = "\"{code:CPackGetCustomInstallationMessage}\"";
+    params["Flags"] = "iscustom";
+
+    allTypes.push_back("custom");
+    typeInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  // Components
+  std::vector<cmCPackComponent*> downloadedComponents;
+  std::stack<cmCPackComponentGroup*> groups;
+  for (auto& i : Components) {
+    cmCPackInnoSetupKeyValuePairs params;
+    cmCPackComponent* component = &i.second;
+
+    if (component->IsHidden) {
+      continue;
+    }
+
+    CreateRecursiveComponentGroups(component->Group);
+
+    params["Name"] =
+      Quote(CreateRecursiveComponentPath(component->Group, component->Name));
+    params["Description"] = Quote(component->DisplayName);
+
+    if (component->IsRequired) {
+      params["Types"] = cmJoin(allTypes, " ");
+      params["Flags"] = "fixed";
+    } else if (!component->InstallationTypes.empty()) {
+      std::vector<std::string> installationTypes;
+
+      for (cmCPackInstallationType* j : component->InstallationTypes) {
+        installationTypes.push_back(j->Name);
+      }
+
+      params["Types"] = cmJoin(installationTypes, " ");
+    }
+
+    componentInstructions.push_back(ISKeyValueLine(params));
+
+    if (component->IsDownloaded) {
+      downloadedComponents.push_back(component);
+
+      if (component->ArchiveFile.empty()) {
+        // Compute the name of the archive.
+        if (!RequireOption("CPACK_TEMPORARY_DIRECTORY")) {
+          return false;
+        }
+
+        std::string packagesDir =
+          cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
+        component->ArchiveFile =
+          cmStrCat(cmSystemTools::GetFilenameWithoutLastExtension(packagesDir),
+                   '-', component->Name, ".zip");
+      } else if (!cmHasSuffix(component->ArchiveFile, ".zip")) {
+        component->ArchiveFile = cmStrCat(component->ArchiveFile, ".zip");
+      }
+    }
+  }
+
+  // Downloaded components
+  if (!downloadedComponents.empty()) {
+    // Create the directory for the upload area
+    cmValue userUploadDirectory = GetOption("CPACK_UPLOAD_DIRECTORY");
+    std::string uploadDirectory;
+    if (cmNonempty(userUploadDirectory)) {
+      uploadDirectory = *userUploadDirectory;
+    } else {
+      if (!RequireOption("CPACK_PACKAGE_DIRECTORY")) {
+        return false;
+      }
+
+      uploadDirectory =
+        cmStrCat(GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
+    }
+
+    if (!cmSystemTools::FileExists(uploadDirectory)) {
+      if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
+        cmCPackLogger(cmCPackLog::LOG_ERROR,
+                      "Unable to create Inno Setup upload directory "
+                        << uploadDirectory << std::endl);
+        return false;
+      }
+    }
+
+    if (!RequireOption("CPACK_DOWNLOAD_SITE")) {
+      return false;
+    }
+
+    SetOptionIfNotSet("CPACK_INNOSETUP_VERIFY_DOWNLOADS", "ON");
+    const bool verifyDownloads =
+      GetOption("CPACK_INNOSETUP_VERIFY_DOWNLOADS").IsOn();
+
+    const std::string& urlPrefix =
+      cmHasSuffix(GetOption("CPACK_DOWNLOAD_SITE").GetCStr(), '/')
+      ? GetOption("CPACK_DOWNLOAD_SITE")
+      : cmStrCat(GetOption("CPACK_DOWNLOAD_SITE"), '/');
+
+    std::vector<std::string> archiveUrls;
+    std::vector<std::string> archiveFiles;
+    std::vector<std::string> archiveHashes;
+    std::vector<std::string> archiveComponents;
+    for (cmCPackComponent* i : downloadedComponents) {
+      std::string hash;
+      if (!BuildDownloadedComponentArchive(
+            i, uploadDirectory, (verifyDownloads ? &hash : nullptr))) {
+        return false;
+      }
+
+      archiveUrls.push_back(Quote(cmStrCat(urlPrefix, i->ArchiveFile)));
+      archiveFiles.push_back(
+        Quote(cmSystemTools::GetFilenameWithoutLastExtension(i->ArchiveFile)));
+      archiveHashes.push_back(Quote(hash));
+      archiveComponents.push_back(
+        Quote(CreateRecursiveComponentPath(i->Group, i->Name)));
+    }
+
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL",
+              std::to_string(archiveFiles.size()));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL",
+              cmJoin(archiveUrls, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL",
+              cmJoin(archiveFiles, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL",
+              cmJoin(archiveHashes, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL",
+              cmJoin(archiveComponents, ", "));
+
+    static const std::string& downloadLines =
+      "#define protected CPackDownloadCount "
+      "@CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL@\n"
+      "#dim protected CPackDownloadUrls[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL@}\n"
+      "#dim protected CPackDownloadArchives[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL@}\n"
+      "#dim protected CPackDownloadHashes[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL@}\n"
+      "#dim protected CPackDownloadComponents[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL@}";
+
+    std::string output;
+    if (!ConfigureString(downloadLines, output)) {
+      return false;
+    }
+    codeIncludes.push_back(output);
+  }
+
+  // Add the required script
+  const std::string& componentsScriptTemplate =
+    FindTemplate("ISComponents.pas");
+  if (componentsScriptTemplate.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Could not find additional Inno Setup script file."
+                    << std::endl);
+    return false;
+  }
+
+  codeIncludes.push_back("#include " + QuotePath(componentsScriptTemplate) +
+                         "\n");
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ConfigureISScript()
+{
+  const std::string& isScriptTemplate = FindTemplate("ISScript.template.in");
+  const std::string& isScriptFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
+
+  if (isScriptTemplate.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Could not find Inno Setup installer template file."
+                    << std::endl);
+    return false;
+  }
+
+  // Create internal variables
+  std::vector<std::string> setupSection;
+  for (const auto& i : setupDirectives) {
+    setupSection.push_back(cmStrCat(i.first, '=', TranslateBool(i.second)));
+  }
+
+  // Also create comments if the sections are empty
+  const std::string& defaultMessage =
+    "; CPack didn't find any entries for this section";
+
+  if (IsSet("CPACK_CREATE_DESKTOP_LINKS") &&
+      !GetOption("CPACK_CREATE_DESKTOP_LINKS").Get()->empty()) {
+    cmCPackInnoSetupKeyValuePairs tasks;
+    tasks["Name"] = "\"desktopicon\"";
+    tasks["Description"] = "\"{cm:CreateDesktopIcon}\"";
+    tasks["GroupDescription"] = "\"{cm:AdditionalIcons}\"";
+    tasks["Flags"] = "unchecked";
+
+    if (!desktopIconComponents.empty()) {
+      tasks["Components"] = cmJoin(desktopIconComponents, " ");
+    }
+
+    SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", ISKeyValueLine(tasks));
+  } else {
+    SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", defaultMessage);
+  }
+
+  SetOption("CPACK_INNOSETUP_INCLUDES_INTERNAL",
+            includeDirectives.empty() ? "; No extra script files specified"
+                                      : cmJoin(includeDirectives, "\n"));
+  SetOption("CPACK_INNOSETUP_SETUP_INTERNAL",
+            setupSection.empty() ? defaultMessage
+                                 : cmJoin(setupSection, "\n"));
+  SetOption("CPACK_INNOSETUP_LANGUAGES_INTERNAL",
+            languageInstructions.empty() ? defaultMessage
+                                         : cmJoin(languageInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_DIRS_INTERNAL",
+            dirInstructions.empty() ? defaultMessage
+                                    : cmJoin(dirInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_FILES_INTERNAL",
+            fileInstructions.empty() ? defaultMessage
+                                     : cmJoin(fileInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_TYPES_INTERNAL",
+            typeInstructions.empty() ? defaultMessage
+                                     : cmJoin(typeInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_COMPONENTS_INTERNAL",
+            componentInstructions.empty()
+              ? defaultMessage
+              : cmJoin(componentInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_ICONS_INTERNAL",
+            iconInstructions.empty() ? defaultMessage
+                                     : cmJoin(iconInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_RUN_INTERNAL",
+            runInstructions.empty() ? defaultMessage
+                                    : cmJoin(runInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_CODE_INTERNAL",
+            codeIncludes.empty() ? "{ No extra code files specified }"
+                                 : cmJoin(codeIncludes, "\n"));
+
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+                "Configure file: " << isScriptTemplate << " to "
+                                   << isScriptFile << std::endl);
+
+  return ConfigureFile(isScriptTemplate, isScriptFile);
+}
+
+bool cmCPackInnoSetupGenerator::Compile()
+{
+  const std::string& isScriptFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
+  const std::string& isccLogFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISCCOutput.log");
+
+  std::vector<std::string> isccArgs;
+
+  // Custom defines
+  for (const std::string& i : GetOptions()) {
+    if (cmHasPrefix(i, "CPACK_INNOSETUP_DEFINE_")) {
+      const std::string& name = i.substr(cmStrLen("CPACK_INNOSETUP_DEFINE_"));
+      isccArgs.push_back(
+        cmStrCat("\"/D", name, '=', TranslateBool(GetOption(i)), '"'));
+    }
+  }
+
+  if (IsSet("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS")) {
+    const cmList args(GetOption("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS"));
+
+    isccArgs.insert(isccArgs.end(), args.begin(), args.end());
+  }
+
+  const std::string& isccCmd =
+    cmStrCat(QuotePath(GetOption("CPACK_INSTALLER_PROGRAM")), ' ',
+             cmJoin(isccArgs, " "), ' ', QuotePath(isScriptFile));
+
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << isccCmd << std::endl);
+
+  std::string output;
+  int retVal = 1;
+  const bool res = cmSystemTools::RunSingleCommand(
+    isccCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
+    cmDuration::zero());
+
+  if (!res || retVal) {
+    cmGeneratedFileStream ofs(isccLogFile);
+    ofs << "# Run command: " << isccCmd << std::endl
+        << "# Output:" << std::endl
+        << output << std::endl;
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem running ISCC. Please check "
+                    << isccLogFile << " for errors." << std::endl);
+    return false;
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::BuildDownloadedComponentArchive(
+  cmCPackComponent* component, const std::string& uploadDirectory,
+  std::string* hash)
+{
+  // Remove the old archive, if one exists
+  const std::string& archiveFile =
+    uploadDirectory + '/' + component->ArchiveFile;
+  cmCPackLogger(cmCPackLog::LOG_OUTPUT,
+                "-   Building downloaded component archive: " << archiveFile
+                                                              << std::endl);
+  if (cmSystemTools::FileExists(archiveFile, true)) {
+    if (!cmSystemTools::RemoveFile(archiveFile)) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Unable to remove archive file " << archiveFile
+                                                     << std::endl);
+      return false;
+    }
+  }
+
+  // Find a ZIP program
+  if (!IsSet("ZIP_EXECUTABLE")) {
+    ReadListFile("Internal/CPack/CPackZIP.cmake");
+
+    if (!IsSet("ZIP_EXECUTABLE")) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Unable to find ZIP program" << std::endl);
+      return false;
+    }
+  }
+
+  if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY") ||
+      !RequireOption("CPACK_TEMPORARY_DIRECTORY") ||
+      !RequireOption("CPACK_ZIP_NEED_QUOTES") ||
+      !RequireOption("CPACK_ZIP_COMMAND")) {
+    return false;
+  }
+
+  // The directory where this component's files reside
+  const std::string& dirName =
+    cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name);
+
+  // Build the list of files to go into this archive
+  const std::string& zipListFileName =
+    cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
+  const bool needQuotesInFile = cmIsOn(GetOption("CPACK_ZIP_NEED_QUOTES"));
+  { // the scope is needed for cmGeneratedFileStream
+    cmGeneratedFileStream out(zipListFileName);
+    for (const std::string& i : component->Files) {
+      out << (needQuotesInFile ? Quote(i) : i) << std::endl;
+    }
+  }
+
+  // Build the archive in the upload area
+  std::string cmd = GetOption("CPACK_ZIP_COMMAND");
+  cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
+  cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
+                                    zipListFileName.c_str());
+  std::string output;
+  int retVal = -1;
+  const bool res = cmSystemTools::RunSingleCommand(
+    cmd, &output, &output, &retVal, dirName.c_str(), this->GeneratorVerbose,
+    cmDuration::zero());
+  if (!res || retVal) {
+    std::string tmpFile =
+      cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
+    cmGeneratedFileStream ofs(tmpFile);
+    ofs << "# Run command: " << cmd << std::endl
+        << "# Output:" << std::endl
+        << output << std::endl;
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem running zip command: " << cmd << std::endl
+                                                  << "Please check " << tmpFile
+                                                  << " for errors"
+                                                  << std::endl);
+    return false;
+  }
+
+  // Try to get the SHA256 hash of the archive file
+  if (hash == nullptr) {
+    return true;
+  }
+
+#ifdef _WIN32
+  const std::string& hashCmd =
+    cmStrCat("certutil -hashfile ", QuotePath(archiveFile), " SHA256");
+
+  std::string hashOutput;
+  int hashRetVal = -1;
+  const bool hashRes = cmSystemTools::RunSingleCommand(
+    hashCmd, &hashOutput, &hashOutput, &hashRetVal, nullptr,
+    this->GeneratorVerbose, cmDuration::zero());
+  if (!hashRes || hashRetVal) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Problem running certutil command: " << hashCmd
+                                                       << std::endl);
+  }
+  *hash = cmTrimWhitespace(cmTokenize(hashOutput, "\n").at(1));
+
+  if (hash->length() != 64) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Problem parsing certutil output of command: " << hashCmd
+                                                                 << std::endl);
+    hash->clear();
+  }
+#endif
+
+  return true;
+}
+
+cmValue cmCPackInnoSetupGenerator::RequireOption(const std::string& key)
+{
+  cmValue value = GetOption(key);
+
+  if (!value) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Required variable " << key << " not set" << std::endl);
+  }
+
+  return value;
+}
+
+std::string cmCPackInnoSetupGenerator::CustomComponentInstallDirectory(
+  const cmCPackComponent* component)
+{
+  cmValue outputDir = GetOption(
+    cmStrCat("CPACK_INNOSETUP_", component->Name, "_INSTALL_DIRECTORY"));
+  if (outputDir) {
+    std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(outputDir);
+    cmStripSuffixIfExists(destDir, '\\');
+
+    /*
+     * Add a dir instruction for the custom directory
+     * (only once and not for Inno Setup constants ending with '}')
+     */
+    static std::vector<std::string> customDirectories;
+    if (!cmHasSuffix(destDir, '}') &&
+        std::find(customDirectories.begin(), customDirectories.end(),
+                  component->Name) == customDirectories.end()) {
+      cmCPackInnoSetupKeyValuePairs params;
+      params["Name"] = QuotePath(destDir);
+      params["Components"] =
+        CreateRecursiveComponentPath(component->Group, component->Name);
+
+      dirInstructions.push_back(ISKeyValueLine(params));
+      customDirectories.push_back(component->Name);
+    }
+    return destDir;
+  }
+
+  return "{app}";
+}
+
+std::string cmCPackInnoSetupGenerator::TranslateBool(const std::string& value)
+{
+  if (value.empty()) {
+    return value;
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT", "ON");
+  if (GetOption("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT").IsOn()) {
+    if (cmIsOn(value)) {
+      return "yes";
+    }
+    if (cmIsOff(value)) {
+      return "no";
+    }
+  }
+
+  return value;
+}
+
+std::string cmCPackInnoSetupGenerator::ISKeyValueLine(
+  const cmCPackInnoSetupKeyValuePairs& params)
+{
+  /*
+   * To simplify readability of the generated code, the keys are sorted.
+   * Unknown keys are ignored to avoid errors during compilation.
+   */
+  static const char* const availableKeys[] = {
+    "Source",       "DestDir",          "Name",         "Filename",
+    "Description",  "GroupDescription", "MessagesFile", "Types",
+    "ExternalSize", "BeforeInstall",    "Flags",        "Components",
+    "Tasks"
+  };
+
+  std::vector<std::string> keys;
+  for (const char* i : availableKeys) {
+    if (params.count(i)) {
+      keys.push_back(cmStrCat(i, ": ", params.at(i)));
+    }
+  }
+
+  return cmJoin(keys, "; ");
+}
+
+std::string cmCPackInnoSetupGenerator::CreateRecursiveComponentPath(
+  cmCPackComponentGroup* group, const std::string& path)
+{
+  if (group == nullptr) {
+    return path;
+  }
+
+  const std::string& newPath =
+    path.empty() ? group->Name : cmStrCat(group->Name, '\\', path);
+  return CreateRecursiveComponentPath(group->ParentGroup, newPath);
+}
+
+void cmCPackInnoSetupGenerator::CreateRecursiveComponentGroups(
+  cmCPackComponentGroup* group)
+{
+  if (group == nullptr) {
+    return;
+  }
+
+  CreateRecursiveComponentGroups(group->ParentGroup);
+
+  static std::vector<cmCPackComponentGroup*> processedGroups;
+  if (std::find(processedGroups.begin(), processedGroups.end(), group) ==
+      processedGroups.end()) {
+    processedGroups.push_back(group);
+
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(CreateRecursiveComponentPath(group));
+    params["Description"] = Quote(group->DisplayName);
+
+    componentInstructions.push_back(ISKeyValueLine(params));
+  }
+}
+
+std::string cmCPackInnoSetupGenerator::Quote(const std::string& string)
+{
+  if (cmHasPrefix(string, '"') && cmHasSuffix(string, '"')) {
+    return Quote(string.substr(1, string.length() - 2));
+  }
+
+  // Double quote syntax
+  std::string nString = string;
+  cmSystemTools::ReplaceString(nString, "\"", "\"\"");
+  return cmStrCat('"', nString, '"');
+}
+
+std::string cmCPackInnoSetupGenerator::QuotePath(const std::string& path)
+{
+  return Quote(cmSystemTools::ConvertToWindowsOutputPath(path));
+}
+
+std::string cmCPackInnoSetupGenerator::PrepareForConstant(
+  const std::string& string)
+{
+  std::string nString = string;
+
+  cmSystemTools::ReplaceString(nString, "%", "%25"); // First replacement!
+  cmSystemTools::ReplaceString(nString, "\"", "%22");
+  cmSystemTools::ReplaceString(nString, ",", "%2c");
+  cmSystemTools::ReplaceString(nString, "|", "%7c");
+  cmSystemTools::ReplaceString(nString, "}", "%7d");
+
+  return nString;
+}

+ 116 - 0
Source/CPack/cmCPackInnoSetupGenerator.h

@@ -0,0 +1,116 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "cmCPackGenerator.h"
+#include "cmValue.h"
+
+using cmCPackInnoSetupKeyValuePairs = std::map<std::string, std::string>;
+
+class cmCPackComponentGroup;
+class cmCPackComponent;
+
+/** \class cmCPackInnoSetupGenerator
+ * \brief A generator for Inno Setup
+ *
+ * https://jrsoftware.org/isinfo.php
+ */
+class cmCPackInnoSetupGenerator : public cmCPackGenerator
+{
+public:
+  cmCPackTypeMacro(cmCPackInnoSetupGenerator, cmCPackGenerator);
+
+  /**
+   * Construct generator
+   */
+  cmCPackInnoSetupGenerator();
+  ~cmCPackInnoSetupGenerator() override;
+
+  static bool CanGenerate();
+
+protected:
+  int InitializeInternal() override;
+  int PackageFiles() override;
+
+  inline const char* GetOutputExtension() override { return ".exe"; }
+
+  inline cmCPackGenerator::CPackSetDestdirSupport SupportsSetDestdir()
+    const override
+  {
+    return cmCPackGenerator::SETDESTDIR_UNSUPPORTED;
+  }
+
+  inline bool SupportsAbsoluteDestination() const override { return false; }
+  inline bool SupportsComponentInstallation() const override { return true; }
+
+private:
+  bool ProcessSetupSection();
+  bool ProcessFiles();
+  bool ProcessComponents();
+
+  bool ConfigureISScript();
+  bool Compile();
+
+  bool BuildDownloadedComponentArchive(cmCPackComponent* component,
+                                       const std::string& uploadDirectory,
+                                       std::string* hash);
+
+  /**
+   * Returns the option's value or an empty string if the option isn't set.
+   */
+  cmValue RequireOption(const std::string& key);
+
+  std::string CustomComponentInstallDirectory(
+    const cmCPackComponent* component);
+
+  /**
+   * Translates boolean expressions into "yes" or "no", as required in
+   * Inno Setup (only if "CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT" is on).
+   */
+  std::string TranslateBool(const std::string& value);
+
+  /**
+   * Creates a typical line of key and value pairs using the given map.
+   *
+   * (e.g.: Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}";
+   * GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked)
+   */
+  std::string ISKeyValueLine(const cmCPackInnoSetupKeyValuePairs& params);
+
+  std::string CreateRecursiveComponentPath(cmCPackComponentGroup* group,
+                                           const std::string& path = "");
+
+  void CreateRecursiveComponentGroups(cmCPackComponentGroup* group);
+
+  /**
+   * These functions add quotes if the given value hasn't already quotes.
+   * Paths are converted into the format used by Windows before.
+   */
+  std::string Quote(const std::string& string);
+  std::string QuotePath(const std::string& path);
+
+  /**
+   * This function replaces the following 5 characters with their %-encoding:
+   * '|'  '}'  ','  '%'  '"'
+   * Required for Inno Setup constants like {cm:...}
+   */
+  std::string PrepareForConstant(const std::string& string);
+
+  std::vector<std::string> includeDirectives;
+  cmCPackInnoSetupKeyValuePairs setupDirectives;
+  bool toplevelProgramFolder;
+  std::vector<std::string> languageInstructions;
+  std::vector<std::string> fileInstructions;
+  std::vector<std::string> dirInstructions;
+  std::vector<std::string> typeInstructions;
+  std::vector<std::string> componentInstructions;
+  std::vector<std::string> iconInstructions;
+  std::vector<std::string> desktopIconComponents;
+  std::vector<std::string> runInstructions;
+  std::vector<std::string> codeIncludes;
+};

+ 24 - 0
Tests/CMakeLists.txt

@@ -986,6 +986,30 @@ if(BUILD_TESTING)
     endif()
     endif()
   endif()
   endif()
 
 
+  # On Windows run the CPackInnoSetupGenerator test
+  if(WIN32 AND CMake_TEST_CPACK_INNOSETUP)
+    add_test(CPackInnoSetupGenerator ${CMAKE_CTEST_COMMAND}
+      -C \${CTEST_CONFIGURATION_TYPE}
+      --build-and-test
+      "${CMake_SOURCE_DIR}/Tests/CPackInnoSetupGenerator"
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator"
+      ${build_generator_args}
+      --build-project CPackInnoSetupGenerator
+      --build-options
+      --test-command ${CMAKE_CMAKE_COMMAND}
+      "-DCPackInnoSetupGenerator_BINARY_DIR:PATH=${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator"
+      "-Dconfig=\${CTEST_CONFIGURATION_TYPE}"
+      -P "${CMake_SOURCE_DIR}/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake")
+
+    set_property(TEST CPackInnoSetupGenerator PROPERTY
+      ATTACHED_FILES_ON_FAIL
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator/_CPack_Packages/win32/INNOSETUP/ISCCOutput.log")
+
+    set_property(TEST CPackInnoSetupGenerator PROPERTY
+      ATTACHED_FILES
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator/_CPack_Packages/win32/INNOSETUP/ISScript.iss")
+  endif()
+
   # On Windows run the CPackNSISGenerator test
   # On Windows run the CPackNSISGenerator test
   # if the nsis is available
   # if the nsis is available
   if(WIN32 AND NSIS_MAKENSIS_EXECUTABLE)
   if(WIN32 AND NSIS_MAKENSIS_EXECUTABLE)

+ 55 - 0
Tests/CPackInnoSetupGenerator/CMakeLists.txt

@@ -0,0 +1,55 @@
+cmake_minimum_required(VERSION 3.13)
+
+project(CPackInnoSetupGenerator VERSION 42.0 HOMEPAGE_URL "https://www.example.com")
+
+add_executable(hello main.c)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/empty)
+
+install(TARGETS hello DESTINATION / COMPONENT application)
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/empty DESTINATION / COMPONENT extras)
+install(FILES my_bitmap.bmp DESTINATION awesome COMPONENT extras)
+install(FILES my_file.txt DESTINATION / COMPONENT hidden_component)
+install(FILES my_file.txt DESTINATION / COMPONENT hidden_component2)
+
+set(CPACK_GENERATOR "INNOSETUP")
+
+set(CPACK_PACKAGE_NAME "Hello, World!") # Test constant escape (like {cm:...}, see code documentation)
+set(CPACK_PACKAGE_VENDOR "Sheldon Cooper")
+set(CPACK_PACKAGE_INSTALL_DIRECTORY "hello_world")
+set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "hello_world")
+set(CPACK_PACKAGE_FILE_NAME "hello_world_setup")
+set(CPACK_SYSTEM_NAME "win32")
+set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/my_bitmap.bmp")
+set(CPACK_VERBATIM_VARIABLES ON)
+set(CPACK_PACKAGE_EXECUTABLES "hello" "Hello, World!")
+set(CPACK_CREATE_DESKTOP_LINKS hello)
+
+set(CPACK_INNOSETUP_INSTALL_ROOT "{autopf}\\Sheldon Cooper")
+set(CPACK_INNOSETUP_PROGRAM_MENU_FOLDER ".")
+set(CPACK_INNOSETUP_IGNORE_LICENSE_PAGE ON)
+set(CPACK_INNOSETUP_IGNORE_README_PAGE OFF) # Test if only readme page is shown
+set(CPACK_INNOSETUP_SETUP_AppComments ON) # Test if CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT works
+set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS "extras/empty"
+    "Name: \"{userdocs}\\empty\"\; Check: ReturnTrue\; Components: accessories\\extras")
+set(CPACK_INNOSETUP_MENU_LINKS "https://www.example.com" "Web"
+    "my_file.txt" "Text")
+set(CPACK_INNOSETUP_RUN_EXECUTABLES hello)
+set(CPACK_INNOSETUP_CREATE_UNINSTALL_LINK ON)
+# Test if this macro is available in the code file below containing the check function
+set(CPACK_INNOSETUP_DEFINE_PascalMacro "end;")
+set(CPACK_INNOSETUP_CODE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Code.pas")
+set(CPACK_INNOSETUP_EXECUTABLE "ISCC.exe")
+
+include(CPackComponent)
+
+cpack_add_install_type(basic DISPLAY_NAME "Basic installation")
+cpack_add_install_type(full DISPLAY_NAME "\"Large\" installation") # Test double quote syntax
+cpack_add_component_group(accessories DISPLAY_NAME "Accessories")
+
+cpack_add_component(application DISPLAY_NAME "Application" INSTALL_TYPES basic full REQUIRED)
+cpack_add_component(extras DISPLAY_NAME "Additional components" INSTALL_TYPES full GROUP accessories)
+cpack_add_component(hidden_component HIDDEN)
+cpack_add_component(hidden_component2 HIDDEN DISABLED)
+set(CPACK_INNOSETUP_extras_INSTALL_DIRECTORY "{userdocs}")
+
+include(CPack)

+ 4 - 0
Tests/CPackInnoSetupGenerator/Code.pas

@@ -0,0 +1,4 @@
+function ReturnTrue(): Boolean;
+begin
+  Result := true;
+{#PascalMacro}

+ 136 - 0
Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake

@@ -0,0 +1,136 @@
+message(STATUS "=============================================================")
+message(STATUS "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)")
+message(STATUS "")
+
+if(NOT CPackInnoSetupGenerator_BINARY_DIR)
+  message(FATAL_ERROR "CPackInnoSetupGenerator_BINARY_DIR not set")
+endif()
+
+message(STATUS "CMAKE_COMMAND: ${CMAKE_COMMAND}")
+message(STATUS "CMAKE_CPACK_COMMAND: ${CMAKE_CPACK_COMMAND}")
+message(STATUS "CPackInnoSetupGenerator_BINARY_DIR: ${CPackInnoSetupGenerator_BINARY_DIR}")
+
+if(config)
+  set(_C_config -C ${config})
+endif()
+
+execute_process(COMMAND "${CMAKE_CPACK_COMMAND}"
+  ${_C_config}
+  RESULT_VARIABLE CPack_result
+  OUTPUT_VARIABLE CPack_output
+  ERROR_VARIABLE CPack_output
+  WORKING_DIRECTORY "${CPackInnoSetupGenerator_BINARY_DIR}")
+
+if(CPack_result)
+  message(FATAL_ERROR "CPack execution went wrong!, Output: ${CPack_output}")
+else ()
+  message(STATUS "Output: ${CPack_output}")
+endif()
+
+file(GLOB project_file "${CPackInnoSetupGenerator_BINARY_DIR}/_CPack_Packages/win32/INNOSETUP/ISScript.iss")
+file(GLOB installer_file "${CPackInnoSetupGenerator_BINARY_DIR}/_CPack_Packages/win32/INNOSETUP/hello_world_setup.exe")
+
+message(STATUS "Project file: '${project_file}'")
+message(STATUS "Installer file: '${installer_file}'")
+
+if(NOT project_file)
+  message(FATAL_ERROR "Project file does not exist")
+endif()
+
+if(NOT installer_file)
+  message(FATAL_ERROR "Installer file does not exist")
+endif()
+
+# Test if the correct registry key is set
+file(STRINGS "${project_file}" results REGEX "^AppId=hello_world$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "CPACK_PACKAGE_INSTALL_REGISTRY_KEY doesn't match AppId")
+endif()
+
+# Test if only readme page is shown
+file(STRINGS "${project_file}" results REGEX "^LicenseFile=")
+file(STRINGS "${project_file}" results2 REGEX "^InfoBeforeFile=")
+if(NOT results STREQUAL "" OR results2 STREQUAL "")
+  message(FATAL_ERROR "Erroneous output with license and readme files")
+endif()
+
+# Test if classic style is used by default
+file(STRINGS "${project_file}" results REGEX "compiler:SetupClassicIcon\\.ico")
+file(STRINGS "${project_file}" results2 REGEX "compiler:WizClassicImage\\.bmp")
+if(results STREQUAL "" OR results2 STREQUAL "")
+  message(FATAL_ERROR "Images of classic style not used")
+endif()
+
+# Test if the top-level start menu folder is used
+file(STRINGS "${project_file}" results REGEX "{autoprograms}")
+file(STRINGS "${project_file}" results2 REGEX "{group}")
+if(results STREQUAL "" OR NOT results2 STREQUAL "")
+  message(FATAL_ERROR "Top-level start menu folder not used")
+endif()
+
+# Test CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT
+file(STRINGS "${project_file}" results REGEX "^AppComments=yes$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT doesn't convert booleans")
+endif()
+
+# Test the custom installation rule
+file(STRINGS "${project_file}" results REGEX "^Name: \"{userdocs}\\\\empty\"; Check: ReturnTrue; Components: accessories\\\\extras$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "Custom installation rule not found or incomplete")
+endif()
+
+# Test if an uninstall shortcut has been created
+file(STRINGS "${project_file}" results REGEX "{uninstallexe}")
+if(results STREQUAL "")
+  message(FATAL_ERROR "No uninstall shortcut created")
+endif()
+
+# Test CPACK_INNOSETUP_<compName>_INSTALL_DIRECTORY
+file(STRINGS "${project_file}" results REGEX "{app}.*Components: accessories\\\\extras")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Component not in custom install directory")
+endif()
+
+# Test if component names are nested correctly
+file(STRINGS "${project_file}" results REGEX "Components:.* extras")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Component names must contain their parent groups according to the documentation")
+endif()
+
+# Test if custom installation type exists
+file(STRINGS "${project_file}" results REGEX "Flags: .*iscustom")
+if(results STREQUAL "")
+  message(FATAL_ERROR "Custom installation type doesn't exist")
+endif()
+
+# Test if hidden components are processed but not displayed
+file(STRINGS "${project_file}" results REGEX "Source:.+hidden_component\\\\my_file\\.txt")
+file(STRINGS "${project_file}" results2 REGEX "Name: \"hidden_component\"")
+if(results STREQUAL "" OR NOT results2 STREQUAL "")
+  message(FATAL_ERROR "Hidden component displayed or one of its files ignored")
+endif()
+
+# Test if disabled and hidden components are ignored at all
+file(STRINGS "${project_file}" results REGEX "Source:.+hidden_component2\\\\my_file\\.txt")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Disabled and hidden component not ignored")
+endif()
+
+# Test if required components ignore their installation types
+file(STRINGS "${project_file}" results REGEX "Types: (basic|full|custom|basic full|full basic|basic custom|full custom); Flags: fixed")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Required components don't ignore their installation types")
+endif()
+
+# Test constant escape (should contain Hello%2c World!)
+file(STRINGS "${project_file}" results REGEX "Hello%2c World!")
+if(results STREQUAL "")
+  message(FATAL_ERROR "The comma character isn't escaped to %2c")
+endif()
+
+# Test double quote syntax
+file(STRINGS "${project_file}" results REGEX "\"\"Large\"\"")
+if(results STREQUAL "")
+  message(FATAL_ERROR "The quote character isn't escaped correctly")
+endif()

+ 7 - 0
Tests/CPackInnoSetupGenerator/main.c

@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main()
+{
+  printf("Hello, World!\n");
+  return 42;
+}

二进制
Tests/CPackInnoSetupGenerator/my_bitmap.bmp


+ 1 - 0
Tests/CPackInnoSetupGenerator/my_file.txt

@@ -0,0 +1 @@
+Hello, World!