summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake1
-rw-r--r--Help/cpack_gen/innosetup.rst420
-rw-r--r--Help/manual/cpack-generators.7.rst1
-rw-r--r--Help/release/dev/cpack-innosetup.rst12
-rw-r--r--Modules/CPack.cmake24
-rw-r--r--Modules/Internal/CPack/ISComponents.pas88
-rw-r--r--Modules/Internal/CPack/ISScript.template.in34
-rw-r--r--Source/CMakeLists.txt1
-rw-r--r--Source/CPack/cmCPackGeneratorFactory.cxx5
-rw-r--r--Source/CPack/cmCPackInnoSetupGenerator.cxx1159
-rw-r--r--Source/CPack/cmCPackInnoSetupGenerator.h116
-rw-r--r--Tests/CMakeLists.txt24
-rw-r--r--Tests/CPackInnoSetupGenerator/CMakeLists.txt55
-rw-r--r--Tests/CPackInnoSetupGenerator/Code.pas4
-rw-r--r--Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake136
-rw-r--r--Tests/CPackInnoSetupGenerator/main.c7
-rw-r--r--Tests/CPackInnoSetupGenerator/my_bitmap.bmpbin0 -> 9294 bytes
-rw-r--r--Tests/CPackInnoSetupGenerator/my_file.txt1
18 files changed, 2081 insertions, 7 deletions
diff --git a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake b/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake
index 5bf0be8..54abf72 100644
--- a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake
+++ b/.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 "")
diff --git a/Help/cpack_gen/innosetup.rst b/Help/cpack_gen/innosetup.rst
new file mode 100644
index 0000000..f48e7f5
--- /dev/null
+++ b/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
diff --git a/Help/manual/cpack-generators.7.rst b/Help/manual/cpack-generators.7.rst
index ade9149..abb291b 100644
--- a/Help/manual/cpack-generators.7.rst
+++ b/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
diff --git a/Help/release/dev/cpack-innosetup.rst b/Help/release/dev/cpack-innosetup.rst
new file mode 100644
index 0000000..a9f8e8e
--- /dev/null
+++ b/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)
diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake
index f9cf33f..ff1cb7e 100644
--- a/Modules/CPack.cmake
+++ b/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}")
diff --git a/Modules/Internal/CPack/ISComponents.pas b/Modules/Internal/CPack/ISComponents.pas
new file mode 100644
index 0000000..8b5c8b4
--- /dev/null
+++ b/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
diff --git a/Modules/Internal/CPack/ISScript.template.in b/Modules/Internal/CPack/ISScript.template.in
new file mode 100644
index 0000000..1171058
--- /dev/null
+++ b/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@
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 4a7d9bc..2354f3d 100644
--- a/Source/CMakeLists.txt
+++ b/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
diff --git a/Source/CPack/cmCPackGeneratorFactory.cxx b/Source/CPack/cmCPackGeneratorFactory.cxx
index efb94b9..6ca48bf 100644
--- a/Source/CPack/cmCPackGeneratorFactory.cxx
+++ b/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);
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx
new file mode 100644
index 0000000..d8825d4
--- /dev/null
+++ b/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;
+}
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.h b/Source/CPack/cmCPackInnoSetupGenerator.h
new file mode 100644
index 0000000..342f227
--- /dev/null
+++ b/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;
+};
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index e92d1c1..e3b5ec4 100644
--- a/Tests/CMakeLists.txt
+++ b/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)
diff --git a/Tests/CPackInnoSetupGenerator/CMakeLists.txt b/Tests/CPackInnoSetupGenerator/CMakeLists.txt
new file mode 100644
index 0000000..bca0ad6
--- /dev/null
+++ b/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)
diff --git a/Tests/CPackInnoSetupGenerator/Code.pas b/Tests/CPackInnoSetupGenerator/Code.pas
new file mode 100644
index 0000000..d96d82f
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/Code.pas
@@ -0,0 +1,4 @@
+function ReturnTrue(): Boolean;
+begin
+ Result := true;
+{#PascalMacro}
diff --git a/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake b/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake
new file mode 100644
index 0000000..72a26ee
--- /dev/null
+++ b/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()
diff --git a/Tests/CPackInnoSetupGenerator/main.c b/Tests/CPackInnoSetupGenerator/main.c
new file mode 100644
index 0000000..413899c
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/main.c
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main()
+{
+ printf("Hello, World!\n");
+ return 42;
+}
diff --git a/Tests/CPackInnoSetupGenerator/my_bitmap.bmp b/Tests/CPackInnoSetupGenerator/my_bitmap.bmp
new file mode 100644
index 0000000..d0e562f
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/my_bitmap.bmp
Binary files differ
diff --git a/Tests/CPackInnoSetupGenerator/my_file.txt b/Tests/CPackInnoSetupGenerator/my_file.txt
new file mode 100644
index 0000000..8ab686e
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/my_file.txt
@@ -0,0 +1 @@
+Hello, World!