Browse Source

CPack: Add Inno Setup generator

Jannik Alber 3 years ago
parent
commit
1d6db66179

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

@@ -1,4 +1,5 @@
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_CPACK_INNOSETUP "ON" CACHE STRING "")
   set(CMake_TEST_ISPC "ON" CACHE STRING "")
 endif()
 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/external
    /cpack_gen/freebsd
+   /cpack_gen/innosetup
    /cpack_gen/ifw
    /cpack_gen/nsis
    /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
   create Start Menu shortcuts.  For example, setting this to the list
   ``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
 
@@ -738,14 +738,16 @@ if(NOT CPACK_GENERATOR)
         )
     endif()
   else()
-    option(CPACK_BINARY_7Z    "Enable to build 7-Zip packages" OFF)
-    option(CPACK_BINARY_NSIS  "Enable to build NSIS packages" ON)
-    option(CPACK_BINARY_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(
       CPACK_BINARY_7Z
       CPACK_BINARY_NSIS
+      CPACK_BINARY_INNOSETUP
       CPACK_BINARY_NUGET
       CPACK_BINARY_WIX
       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_IFW          IFW)
   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_PRODUCTBUILD productbuild)
   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)
 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
 _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/cmCPackGenerator.cxx
   CPack/cmCPackLog.cxx
+  CPack/cmCPackInnoSetupGenerator.cxx
   CPack/cmCPackNSISGenerator.cxx
   CPack/cmCPackNuGetGenerator.cxx
   CPack/cmCPackSTGZGenerator.cxx

+ 5 - 0
Source/CPack/cmCPackGeneratorFactory.cxx

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

BIN
Tests/CPackInnoSetupGenerator/my_bitmap.bmp


+ 1 - 0
Tests/CPackInnoSetupGenerator/my_file.txt

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