CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / CPack / cmCPackInnoSetupGenerator.cxx
blob17ea89fa1641b6a1bda5fc9ab56aa63d3ebd1085
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #include "cmCPackInnoSetupGenerator.h"
6 #include <algorithm>
7 #include <cctype>
8 #include <cstdlib>
9 #include <ostream>
10 #include <utility>
12 #include "cmsys/RegularExpression.hxx"
14 #include "cmCPackComponentGroup.h"
15 #include "cmCPackLog.h"
16 #include "cmDuration.h"
17 #include "cmGeneratedFileStream.h"
18 #include "cmList.h"
19 #include "cmStringAlgorithms.h"
20 #include "cmSystemTools.h"
22 cmCPackInnoSetupGenerator::cmCPackInnoSetupGenerator() = default;
23 cmCPackInnoSetupGenerator::~cmCPackInnoSetupGenerator() = default;
25 bool cmCPackInnoSetupGenerator::CanGenerate()
27 return true;
30 int cmCPackInnoSetupGenerator::InitializeInternal()
32 if (GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY").IsOn()) {
33 cmCPackLogger(cmCPackLog::LOG_WARNING,
34 "Inno Setup Generator cannot work with "
35 "CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
36 "This option will be reset to 0 (for this generator only)."
37 << std::endl);
38 SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
41 std::vector<std::string> path;
43 #ifdef _WIN32
44 path.push_back("C:\\Program Files (x86)\\Inno Setup 5");
45 path.push_back("C:\\Program Files (x86)\\Inno Setup 6");
46 #endif
48 SetOptionIfNotSet("CPACK_INNOSETUP_EXECUTABLE", "ISCC");
49 const std::string& isccPath = cmSystemTools::FindProgram(
50 GetOption("CPACK_INNOSETUP_EXECUTABLE"), path, false);
52 if (isccPath.empty()) {
53 cmCPackLogger(cmCPackLog::LOG_ERROR,
54 "Cannot find Inno Setup compiler ISCC: "
55 "likely it is not installed, or not in your PATH"
56 << std::endl);
58 return 0;
61 const std::string isccCmd =
62 cmStrCat(QuotePath(isccPath, PathType::Native), "/?");
63 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
64 "Test Inno Setup version: " << isccCmd << std::endl);
65 std::string output;
66 cmSystemTools::RunSingleCommand(isccCmd, &output, &output, nullptr, nullptr,
67 this->GeneratorVerbose, cmDuration::zero());
68 cmsys::RegularExpression vRex("Inno Setup ([0-9]+)");
69 if (!vRex.find(output)) {
70 cmCPackLogger(cmCPackLog::LOG_ERROR,
71 "Problem checking Inno Setup version with command: "
72 << isccCmd << std::endl
73 << "Have you downloaded Inno Setup from "
74 "https://jrsoftware.org/isinfo.php?"
75 << std::endl);
76 return 0;
79 const int isccVersion = atoi(vRex.match(1).c_str());
80 const int minIsccVersion = 6;
81 cmCPackLogger(cmCPackLog::LOG_DEBUG,
82 "Inno Setup Version: " << isccVersion << std::endl);
84 if (isccVersion < minIsccVersion) {
85 cmCPackLogger(cmCPackLog::LOG_ERROR,
86 "CPack requires Inno Setup Version 6 or greater. "
87 "Inno Setup found on the system was: "
88 << isccVersion << std::endl);
89 return 0;
92 SetOption("CPACK_INSTALLER_PROGRAM", isccPath);
94 return this->Superclass::InitializeInternal();
97 int cmCPackInnoSetupGenerator::PackageFiles()
99 // Includes
100 if (IsSet("CPACK_INNOSETUP_EXTRA_SCRIPTS")) {
101 const cmList extraScripts(GetOption("CPACK_INNOSETUP_EXTRA_SCRIPTS"));
103 for (const std::string& i : extraScripts) {
104 includeDirectives.emplace_back(cmStrCat(
105 "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
109 // [Languages] section
110 SetOptionIfNotSet("CPACK_INNOSETUP_LANGUAGES", "english");
111 const cmList languages(GetOption("CPACK_INNOSETUP_LANGUAGES"));
112 for (std::string i : languages) {
113 cmCPackInnoSetupKeyValuePairs params;
115 params["Name"] = Quote(i);
117 if (cmSystemTools::LowerCase(i) == "english") {
118 params["MessagesFile"] = "\"compiler:Default.isl\"";
119 } else {
120 i[0] = static_cast<char>(std::toupper(i[0]));
121 params["MessagesFile"] = cmStrCat("\"compiler:Languages\\", i, ".isl\"");
124 languageInstructions.push_back(ISKeyValueLine(params));
127 if (!Components.empty() && !ProcessComponents()) {
128 return false;
131 if (!(ProcessSetupSection() && ProcessFiles())) {
132 return false;
135 // [Code] section
136 if (IsSet("CPACK_INNOSETUP_CODE_FILES")) {
137 const cmList codeFiles(GetOption("CPACK_INNOSETUP_CODE_FILES"));
139 for (const std::string& i : codeFiles) {
140 codeIncludes.emplace_back(cmStrCat(
141 "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
145 return ConfigureISScript() && Compile();
148 bool cmCPackInnoSetupGenerator::ProcessSetupSection()
150 if (!RequireOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY")) {
151 return false;
153 setupDirectives["AppId"] = GetOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY");
155 if (!RequireOption("CPACK_PACKAGE_NAME")) {
156 return false;
158 setupDirectives["AppName"] = GetOption("CPACK_PACKAGE_NAME");
159 setupDirectives["UninstallDisplayName"] = GetOption("CPACK_PACKAGE_NAME");
161 if (!RequireOption("CPACK_PACKAGE_VERSION")) {
162 return false;
164 setupDirectives["AppVersion"] = GetOption("CPACK_PACKAGE_VERSION");
166 if (!RequireOption("CPACK_PACKAGE_VENDOR")) {
167 return false;
169 setupDirectives["AppPublisher"] = GetOption("CPACK_PACKAGE_VENDOR");
171 if (IsSet("CPACK_PACKAGE_HOMEPAGE_URL")) {
172 setupDirectives["AppPublisherURL"] =
173 GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
174 setupDirectives["AppSupportURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
175 setupDirectives["AppUpdatesURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
178 SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE", "OFF");
179 if (IsSet("CPACK_RESOURCE_FILE_LICENSE") &&
180 !GetOption("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE").IsOn()) {
181 setupDirectives["LicenseFile"] = cmSystemTools::ConvertToWindowsOutputPath(
182 GetOption("CPACK_RESOURCE_FILE_LICENSE"));
185 SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_README_PAGE", "ON");
186 if (IsSet("CPACK_RESOURCE_FILE_README") &&
187 !GetOption("CPACK_INNOSETUP_IGNORE_README_PAGE").IsOn()) {
188 setupDirectives["InfoBeforeFile"] =
189 cmSystemTools::ConvertToWindowsOutputPath(
190 GetOption("CPACK_RESOURCE_FILE_README"));
193 SetOptionIfNotSet("CPACK_INNOSETUP_USE_MODERN_WIZARD", "OFF");
194 if (GetOption("CPACK_INNOSETUP_USE_MODERN_WIZARD").IsOn()) {
195 setupDirectives["WizardStyle"] = "modern";
196 } else {
197 setupDirectives["WizardStyle"] = "classic";
198 setupDirectives["WizardSmallImageFile"] =
199 "compiler:WizClassicSmallImage.bmp";
200 setupDirectives["WizardImageFile"] = "compiler:WizClassicImage.bmp";
201 setupDirectives["SetupIconFile"] = "compiler:SetupClassicIcon.ico";
204 if (IsSet("CPACK_INNOSETUP_ICON_FILE")) {
205 setupDirectives["SetupIconFile"] =
206 cmSystemTools::ConvertToWindowsOutputPath(
207 GetOption("CPACK_INNOSETUP_ICON_FILE"));
210 if (IsSet("CPACK_PACKAGE_ICON")) {
211 setupDirectives["WizardSmallImageFile"] =
212 cmSystemTools::ConvertToWindowsOutputPath(
213 GetOption("CPACK_PACKAGE_ICON"));
216 if (!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY")) {
217 return false;
219 SetOptionIfNotSet("CPACK_INNOSETUP_INSTALL_ROOT", "{autopf}");
220 setupDirectives["DefaultDirName"] =
221 cmSystemTools::ConvertToWindowsOutputPath(
222 cmStrCat(GetOption("CPACK_INNOSETUP_INSTALL_ROOT"), '\\',
223 GetOption("CPACK_PACKAGE_INSTALL_DIRECTORY")));
225 SetOptionIfNotSet("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY", "ON");
226 if (GetOption("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY").IsOff()) {
227 setupDirectives["DisableDirPage"] = "yes";
230 SetOptionIfNotSet("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER",
231 GetOption("CPACK_PACKAGE_NAME"));
232 if (GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER") == ".") {
233 setupDirectives["DisableProgramGroupPage"] = "yes";
234 toplevelProgramFolder = true;
235 } else {
236 setupDirectives["DefaultGroupName"] =
237 GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER");
238 toplevelProgramFolder = false;
241 if (IsSet("CPACK_INNOSETUP_PASSWORD")) {
242 setupDirectives["Password"] = GetOption("CPACK_INNOSETUP_PASSWORD");
243 setupDirectives["Encryption"] = "yes";
247 * These directives can only be modified using the
248 * CPACK_INNOSETUP_SETUP_<directive> variables
250 setupDirectives["ShowLanguageDialog"] = "auto";
251 setupDirectives["AllowNoIcons"] = "yes";
252 setupDirectives["Compression"] = "lzma";
253 setupDirectives["SolidCompression"] = "yes";
255 // Output file and directory
256 if (!RequireOption("CPACK_PACKAGE_FILE_NAME")) {
257 return false;
259 setupDirectives["OutputBaseFilename"] = GetOption("CPACK_PACKAGE_FILE_NAME");
261 if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY")) {
262 return false;
264 setupDirectives["OutputDir"] = cmSystemTools::ConvertToWindowsOutputPath(
265 GetOption("CPACK_TOPLEVEL_DIRECTORY"));
267 setupDirectives["SourceDir"] =
268 cmSystemTools::ConvertToWindowsOutputPath(toplevel);
270 // Target architecture
271 if (!RequireOption("CPACK_INNOSETUP_ARCHITECTURE")) {
272 return false;
275 cmValue const architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE");
276 if (architecture != "x86" && architecture != "x64" &&
277 architecture != "arm64" && architecture != "ia64") {
278 cmCPackLogger(cmCPackLog::LOG_ERROR,
279 "CPACK_INNOSETUP_ARCHITECTURE must be either x86, x64, "
280 "arm64 or ia64"
281 << std::endl);
282 return false;
285 // The following directives must not be set to target x86
286 if (architecture != "x86") {
287 setupDirectives["ArchitecturesAllowed"] = architecture;
288 setupDirectives["ArchitecturesInstallIn64BitMode"] = architecture;
292 * Handle custom directives (they have higher priority than other variables,
293 * so they have to be processed after all other variables)
295 for (const std::string& i : GetOptions()) {
296 if (cmHasPrefix(i, "CPACK_INNOSETUP_SETUP_")) {
297 const std::string& directive =
298 i.substr(cmStrLen("CPACK_INNOSETUP_SETUP_"));
299 setupDirectives[directive] = GetOption(i);
303 return true;
306 bool cmCPackInnoSetupGenerator::ProcessFiles()
308 std::map<std::string, std::string> customFileInstructions;
309 if (IsSet("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS")) {
310 const cmList instructions(
311 GetOption("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS"));
312 if (instructions.size() % 2 != 0) {
313 cmCPackLogger(cmCPackLog::LOG_ERROR,
314 "CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS should "
315 "contain pairs of <path> and <instruction>"
316 << std::endl);
317 return false;
320 for (auto it = instructions.begin(); it != instructions.end(); ++it) {
321 const std::string& key =
322 QuotePath(cmSystemTools::CollapseFullPath(*it, toplevel));
323 customFileInstructions[key] = *(++it);
327 const std::string& iconsPrefix =
328 toplevelProgramFolder ? "{autoprograms}\\" : "{group}\\";
330 std::map<std::string, std::string> icons;
331 if (IsSet("CPACK_PACKAGE_EXECUTABLES")) {
332 const cmList executables(GetOption("CPACK_PACKAGE_EXECUTABLES"));
333 if (executables.size() % 2 != 0) {
334 cmCPackLogger(cmCPackLog::LOG_ERROR,
335 "CPACK_PACKAGE_EXECUTABLES should should contain pairs of "
336 "<executable> and <text label>"
337 << std::endl);
338 return false;
341 for (auto it = executables.begin(); it != executables.end(); ++it) {
342 const std::string& key = *it;
343 icons[key] = *(++it);
347 std::vector<std::string> desktopIcons;
348 if (IsSet("CPACK_CREATE_DESKTOP_LINKS")) {
349 cmExpandList(GetOption("CPACK_CREATE_DESKTOP_LINKS"), desktopIcons);
352 std::vector<std::string> runExecutables;
353 if (IsSet("CPACK_INNOSETUP_RUN_EXECUTABLES")) {
354 cmExpandList(GetOption("CPACK_INNOSETUP_RUN_EXECUTABLES"), runExecutables);
357 for (const std::string& i : files) {
358 cmCPackInnoSetupKeyValuePairs params;
360 std::string toplevelDirectory;
361 std::string outputDir;
362 cmCPackComponent* component = nullptr;
363 std::string componentParam;
364 if (!Components.empty()) {
365 const std::string& fileName = cmSystemTools::RelativePath(toplevel, i);
366 const std::string::size_type pos = fileName.find('/');
368 // Use the custom component install directory if we have one
369 if (pos != std::string::npos) {
370 const std::string& componentName = fileName.substr(0, pos);
371 component = &Components[componentName];
373 toplevelDirectory =
374 cmSystemTools::CollapseFullPath(componentName, toplevel);
375 outputDir = CustomComponentInstallDirectory(component);
376 componentParam =
377 CreateRecursiveComponentPath(component->Group, component->Name);
379 if (component->IsHidden && component->IsDisabledByDefault) {
380 continue;
383 if (component->IsHidden) {
384 componentParam.clear();
386 } else {
387 // Don't install component directories
388 continue;
390 } else {
391 toplevelDirectory = toplevel;
392 outputDir = "{app}";
395 if (!componentParam.empty()) {
396 params["Components"] = componentParam;
399 if (cmSystemTools::FileIsDirectory(i)) {
400 // Custom instructions replace the automatic generated instructions
401 if (customFileInstructions.count(QuotePath(i))) {
402 dirInstructions.push_back(customFileInstructions[QuotePath(i)]);
403 } else {
404 std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
405 cmStrCat(outputDir, '\\',
406 cmSystemTools::RelativePath(toplevelDirectory, i)));
407 cmStripSuffixIfExists(destDir, '\\');
409 params["Name"] = QuotePath(destDir);
411 dirInstructions.push_back(ISKeyValueLine(params));
413 } else {
414 // Custom instructions replace the automatic generated instructions
415 if (customFileInstructions.count(QuotePath(i))) {
416 fileInstructions.push_back(customFileInstructions[QuotePath(i)]);
417 } else {
418 std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
419 cmStrCat(outputDir, '\\',
420 cmSystemTools::GetParentDirectory(
421 cmSystemTools::RelativePath(toplevelDirectory, i))));
422 cmStripSuffixIfExists(destDir, '\\');
424 params["DestDir"] = QuotePath(destDir);
426 if (component != nullptr && component->IsDownloaded) {
427 const std::string& archiveName =
428 cmSystemTools::GetFilenameWithoutLastExtension(
429 component->ArchiveFile);
430 const std::string& relativePath =
431 cmSystemTools::RelativePath(toplevelDirectory, i);
433 params["Source"] =
434 QuotePath(cmStrCat("{tmp}\\", archiveName, '\\', relativePath));
435 params["ExternalSize"] =
436 std::to_string(cmSystemTools::FileLength(i));
437 params["Flags"] = "external ignoreversion";
438 params["BeforeInstall"] =
439 cmStrCat("CPackExtractFile('", archiveName, "', '",
440 cmRemoveQuotes(cmSystemTools::ConvertToWindowsOutputPath(
441 relativePath)),
442 "')");
443 } else {
444 params["Source"] = QuotePath(i);
445 params["Flags"] = "ignoreversion";
448 fileInstructions.push_back(ISKeyValueLine(params));
450 // Icon
451 const std::string& name =
452 cmSystemTools::GetFilenameWithoutLastExtension(i);
453 const std::string& extension =
454 cmSystemTools::GetFilenameLastExtension(i);
455 if ((extension == ".exe" || extension == ".com") && // only .exe, .com
456 icons.count(name)) {
457 cmCPackInnoSetupKeyValuePairs iconParams;
459 iconParams["Name"] = QuotePath(cmStrCat(iconsPrefix, icons[name]));
460 iconParams["Filename"] =
461 QuotePath(cmStrCat(destDir, '\\', name, extension));
463 if (!componentParam.empty()) {
464 iconParams["Components"] = componentParam;
467 iconInstructions.push_back(ISKeyValueLine(iconParams));
469 // Desktop icon
470 if (std::find(desktopIcons.begin(), desktopIcons.end(), name) !=
471 desktopIcons.end()) {
472 iconParams["Name"] =
473 QuotePath(cmStrCat("{autodesktop}\\", icons[name]));
474 iconParams["Tasks"] = "desktopicon";
476 if (!componentParam.empty() &&
477 std::find(desktopIconComponents.begin(),
478 desktopIconComponents.end(),
479 componentParam) == desktopIconComponents.end()) {
480 desktopIconComponents.push_back(componentParam);
482 iconInstructions.push_back(ISKeyValueLine(iconParams));
485 // [Run] section
486 if (std::find(runExecutables.begin(), runExecutables.end(), name) !=
487 runExecutables.end()) {
488 cmCPackInnoSetupKeyValuePairs runParams;
490 runParams["Filename"] = iconParams["Filename"];
491 runParams["Description"] = cmStrCat(
492 "\"{cm:LaunchProgram,", PrepareForConstant(icons[name]), "}\"");
493 runParams["Flags"] = "nowait postinstall skipifsilent";
495 if (!componentParam.empty()) {
496 runParams["Components"] = componentParam;
499 runInstructions.push_back(ISKeyValueLine(runParams));
506 // Additional icons
507 static cmsys::RegularExpression urlRegex(
508 "^(mailto:|(ftps?|https?|news)://).*$");
510 if (IsSet("CPACK_INNOSETUP_MENU_LINKS")) {
511 const cmList menuIcons(GetOption("CPACK_INNOSETUP_MENU_LINKS"));
512 if (menuIcons.size() % 2 != 0) {
513 cmCPackLogger(cmCPackLog::LOG_ERROR,
514 "CPACK_INNOSETUP_MENU_LINKS should "
515 "contain pairs of <shortcut target> and <shortcut label>"
516 << std::endl);
517 return false;
520 for (auto it = menuIcons.begin(); it != menuIcons.end(); ++it) {
521 const std::string& target = *it;
522 const std::string& label = *(++it);
523 cmCPackInnoSetupKeyValuePairs params;
525 params["Name"] = QuotePath(cmStrCat(iconsPrefix, label));
526 if (urlRegex.find(target)) {
527 params["Filename"] = Quote(target);
528 } else {
529 std::string dir = "{app}";
530 std::string componentName;
531 for (const auto& i : Components) {
532 if (cmSystemTools::FileExists(cmSystemTools::CollapseFullPath(
533 cmStrCat(i.second.Name, '\\', target), toplevel))) {
534 dir = CustomComponentInstallDirectory(&i.second);
535 componentName =
536 CreateRecursiveComponentPath(i.second.Group, i.second.Name);
538 if (i.second.IsHidden && i.second.IsDisabledByDefault) {
539 goto continueOuterLoop;
540 } else if (i.second.IsHidden) {
541 componentName.clear();
544 break;
548 params["Filename"] = QuotePath(cmStrCat(dir, '\\', target));
550 if (!componentName.empty()) {
551 params["Components"] = componentName;
555 iconInstructions.push_back(ISKeyValueLine(params));
556 continueOuterLoop:;
560 SetOptionIfNotSet("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK", "OFF");
561 if (GetOption("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK").IsOn()) {
562 cmCPackInnoSetupKeyValuePairs params;
564 params["Name"] = QuotePath(
565 cmStrCat(iconsPrefix, "{cm:UninstallProgram,",
566 PrepareForConstant(GetOption("CPACK_PACKAGE_NAME")), '}'));
567 params["Filename"] = "\"{uninstallexe}\"";
569 iconInstructions.push_back(ISKeyValueLine(params));
572 return true;
575 bool cmCPackInnoSetupGenerator::ProcessComponents()
577 codeIncludes.emplace_back(
578 "{ The following lines are required by CPack because "
579 "this script uses components }");
581 // Installation types
582 std::vector<cmCPackInstallationType*> types(InstallationTypes.size());
583 for (auto& i : InstallationTypes) {
584 types[i.second.Index - 1] = &i.second;
587 std::vector<std::string> allTypes; // For required components
588 for (cmCPackInstallationType* i : types) {
589 cmCPackInnoSetupKeyValuePairs params;
591 params["Name"] = Quote(i->Name);
592 params["Description"] = Quote(i->DisplayName);
594 allTypes.push_back(i->Name);
595 typeInstructions.push_back(ISKeyValueLine(params));
598 // Inno Setup requires the additional "custom" type
599 cmCPackInnoSetupKeyValuePairs customTypeParams;
601 customTypeParams["Name"] = "\"custom\"";
602 customTypeParams["Description"] =
603 "\"{code:CPackGetCustomInstallationMessage}\"";
604 customTypeParams["Flags"] = "iscustom";
606 allTypes.emplace_back("custom");
607 typeInstructions.push_back(ISKeyValueLine(customTypeParams));
609 // Components
610 std::vector<cmCPackComponent*> downloadedComponents;
611 for (auto& i : Components) {
612 cmCPackInnoSetupKeyValuePairs params;
613 cmCPackComponent* component = &i.second;
615 if (component->IsHidden) {
616 continue;
619 CreateRecursiveComponentGroups(component->Group);
621 params["Name"] =
622 Quote(CreateRecursiveComponentPath(component->Group, component->Name));
623 params["Description"] = Quote(component->DisplayName);
625 if (component->IsRequired) {
626 params["Types"] = cmJoin(allTypes, " ");
627 params["Flags"] = "fixed";
628 } else if (!component->InstallationTypes.empty()) {
629 std::vector<std::string> installationTypes;
631 installationTypes.reserve(component->InstallationTypes.size());
632 for (cmCPackInstallationType* j : component->InstallationTypes) {
633 installationTypes.push_back(j->Name);
636 params["Types"] = cmJoin(installationTypes, " ");
639 componentInstructions.push_back(ISKeyValueLine(params));
641 if (component->IsDownloaded) {
642 downloadedComponents.push_back(component);
644 if (component->ArchiveFile.empty()) {
645 // Compute the name of the archive.
646 if (!RequireOption("CPACK_TEMPORARY_DIRECTORY")) {
647 return false;
650 std::string packagesDir =
651 cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
652 component->ArchiveFile =
653 cmStrCat(cmSystemTools::GetFilenameWithoutLastExtension(packagesDir),
654 '-', component->Name, ".zip");
655 } else if (!cmHasSuffix(component->ArchiveFile, ".zip")) {
656 component->ArchiveFile = cmStrCat(component->ArchiveFile, ".zip");
661 // Downloaded components
662 if (!downloadedComponents.empty()) {
663 // Create the directory for the upload area
664 cmValue userUploadDirectory = GetOption("CPACK_UPLOAD_DIRECTORY");
665 std::string uploadDirectory;
666 if (cmNonempty(userUploadDirectory)) {
667 uploadDirectory = *userUploadDirectory;
668 } else {
669 if (!RequireOption("CPACK_PACKAGE_DIRECTORY")) {
670 return false;
673 uploadDirectory =
674 cmStrCat(GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
677 if (!cmSystemTools::FileExists(uploadDirectory)) {
678 if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
679 cmCPackLogger(cmCPackLog::LOG_ERROR,
680 "Unable to create Inno Setup upload directory "
681 << uploadDirectory << std::endl);
682 return false;
686 if (!RequireOption("CPACK_DOWNLOAD_SITE")) {
687 return false;
690 SetOptionIfNotSet("CPACK_INNOSETUP_VERIFY_DOWNLOADS", "ON");
691 const bool verifyDownloads =
692 GetOption("CPACK_INNOSETUP_VERIFY_DOWNLOADS").IsOn();
694 const std::string& urlPrefix =
695 cmHasSuffix(GetOption("CPACK_DOWNLOAD_SITE").GetCStr(), '/')
696 ? GetOption("CPACK_DOWNLOAD_SITE")
697 : cmStrCat(GetOption("CPACK_DOWNLOAD_SITE"), '/');
699 std::vector<std::string> archiveUrls;
700 std::vector<std::string> archiveFiles;
701 std::vector<std::string> archiveHashes;
702 std::vector<std::string> archiveComponents;
703 for (cmCPackComponent* i : downloadedComponents) {
704 std::string hash;
705 if (!BuildDownloadedComponentArchive(
706 i, uploadDirectory, (verifyDownloads ? &hash : nullptr))) {
707 return false;
710 archiveUrls.push_back(Quote(cmStrCat(urlPrefix, i->ArchiveFile)));
711 archiveFiles.push_back(
712 Quote(cmSystemTools::GetFilenameWithoutLastExtension(i->ArchiveFile)));
713 archiveHashes.push_back(Quote(hash));
714 archiveComponents.push_back(
715 Quote(CreateRecursiveComponentPath(i->Group, i->Name)));
718 SetOption("CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL",
719 std::to_string(archiveFiles.size()));
720 SetOption("CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL",
721 cmJoin(archiveUrls, ", "));
722 SetOption("CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL",
723 cmJoin(archiveFiles, ", "));
724 SetOption("CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL",
725 cmJoin(archiveHashes, ", "));
726 SetOption("CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL",
727 cmJoin(archiveComponents, ", "));
729 static const std::string& downloadLines =
730 "#define protected CPackDownloadCount "
731 "@CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL@\n"
732 "#dim protected CPackDownloadUrls[CPackDownloadCount] "
733 "{@CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL@}\n"
734 "#dim protected CPackDownloadArchives[CPackDownloadCount] "
735 "{@CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL@}\n"
736 "#dim protected CPackDownloadHashes[CPackDownloadCount] "
737 "{@CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL@}\n"
738 "#dim protected CPackDownloadComponents[CPackDownloadCount] "
739 "{@CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL@}";
741 std::string output;
742 if (!ConfigureString(downloadLines, output)) {
743 return false;
745 codeIncludes.push_back(output);
748 // Add the required script
749 const std::string& componentsScriptTemplate =
750 FindTemplate("ISComponents.pas");
751 if (componentsScriptTemplate.empty()) {
752 cmCPackLogger(cmCPackLog::LOG_ERROR,
753 "Could not find additional Inno Setup script file."
754 << std::endl);
755 return false;
758 codeIncludes.push_back("#include " + QuotePath(componentsScriptTemplate) +
759 "\n");
761 return true;
764 bool cmCPackInnoSetupGenerator::ConfigureISScript()
766 const std::string& isScriptTemplate = FindTemplate("ISScript.template.in");
767 const std::string& isScriptFile =
768 cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
770 if (isScriptTemplate.empty()) {
771 cmCPackLogger(cmCPackLog::LOG_ERROR,
772 "Could not find Inno Setup installer template file."
773 << std::endl);
774 return false;
777 // Create internal variables
778 std::vector<std::string> setupSection;
779 for (const auto& i : setupDirectives) {
780 setupSection.emplace_back(cmStrCat(i.first, '=', TranslateBool(i.second)));
783 // Also create comments if the sections are empty
784 const std::string& defaultMessage =
785 "; CPack didn't find any entries for this section";
787 if (IsSet("CPACK_CREATE_DESKTOP_LINKS") &&
788 !GetOption("CPACK_CREATE_DESKTOP_LINKS").Get()->empty()) {
789 cmCPackInnoSetupKeyValuePairs tasks;
790 tasks["Name"] = "\"desktopicon\"";
791 tasks["Description"] = "\"{cm:CreateDesktopIcon}\"";
792 tasks["GroupDescription"] = "\"{cm:AdditionalIcons}\"";
793 tasks["Flags"] = "unchecked";
795 if (!desktopIconComponents.empty()) {
796 tasks["Components"] = cmJoin(desktopIconComponents, " ");
799 SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", ISKeyValueLine(tasks));
800 } else {
801 SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", defaultMessage);
804 SetOption("CPACK_INNOSETUP_INCLUDES_INTERNAL",
805 includeDirectives.empty() ? "; No extra script files specified"
806 : cmJoin(includeDirectives, "\n"));
807 SetOption("CPACK_INNOSETUP_SETUP_INTERNAL",
808 setupSection.empty() ? defaultMessage
809 : cmJoin(setupSection, "\n"));
810 SetOption("CPACK_INNOSETUP_LANGUAGES_INTERNAL",
811 languageInstructions.empty() ? defaultMessage
812 : cmJoin(languageInstructions, "\n"));
813 SetOption("CPACK_INNOSETUP_DIRS_INTERNAL",
814 dirInstructions.empty() ? defaultMessage
815 : cmJoin(dirInstructions, "\n"));
816 SetOption("CPACK_INNOSETUP_FILES_INTERNAL",
817 fileInstructions.empty() ? defaultMessage
818 : cmJoin(fileInstructions, "\n"));
819 SetOption("CPACK_INNOSETUP_TYPES_INTERNAL",
820 typeInstructions.empty() ? defaultMessage
821 : cmJoin(typeInstructions, "\n"));
822 SetOption("CPACK_INNOSETUP_COMPONENTS_INTERNAL",
823 componentInstructions.empty()
824 ? defaultMessage
825 : cmJoin(componentInstructions, "\n"));
826 SetOption("CPACK_INNOSETUP_ICONS_INTERNAL",
827 iconInstructions.empty() ? defaultMessage
828 : cmJoin(iconInstructions, "\n"));
829 SetOption("CPACK_INNOSETUP_RUN_INTERNAL",
830 runInstructions.empty() ? defaultMessage
831 : cmJoin(runInstructions, "\n"));
832 SetOption("CPACK_INNOSETUP_CODE_INTERNAL",
833 codeIncludes.empty() ? "{ No extra code files specified }"
834 : cmJoin(codeIncludes, "\n"));
836 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
837 "Configure file: " << isScriptTemplate << " to "
838 << isScriptFile << std::endl);
840 return ConfigureFile(isScriptTemplate, isScriptFile);
843 bool cmCPackInnoSetupGenerator::Compile()
845 const std::string& isScriptFile =
846 cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
847 const std::string& isccLogFile =
848 cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISCCOutput.log");
850 std::vector<std::string> isccArgs;
852 // Custom defines
853 for (const std::string& i : GetOptions()) {
854 if (cmHasPrefix(i, "CPACK_INNOSETUP_DEFINE_")) {
855 const std::string& name = i.substr(cmStrLen("CPACK_INNOSETUP_DEFINE_"));
856 isccArgs.push_back(
857 cmStrCat("\"/D", name, '=', TranslateBool(GetOption(i)), '"'));
861 if (IsSet("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS")) {
862 const cmList args(GetOption("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS"));
864 isccArgs.insert(isccArgs.end(), args.begin(), args.end());
867 const std::string& isccCmd =
868 cmStrCat(QuotePath(GetOption("CPACK_INSTALLER_PROGRAM"), PathType::Native),
869 ' ', cmJoin(isccArgs, " "), ' ', QuotePath(isScriptFile));
871 cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << isccCmd << std::endl);
873 std::string output;
874 int retVal = 1;
875 const bool res = cmSystemTools::RunSingleCommand(
876 isccCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
877 cmDuration::zero());
879 if (!res || retVal) {
880 cmGeneratedFileStream ofs(isccLogFile);
881 ofs << "# Run command: " << isccCmd << std::endl
882 << "# Output:" << std::endl
883 << output << std::endl;
884 cmCPackLogger(cmCPackLog::LOG_ERROR,
885 "Problem running ISCC. Please check "
886 << isccLogFile << " for errors." << std::endl);
887 return false;
890 return true;
893 bool cmCPackInnoSetupGenerator::BuildDownloadedComponentArchive(
894 cmCPackComponent* component, const std::string& uploadDirectory,
895 std::string* hash)
897 // Remove the old archive, if one exists
898 const std::string& archiveFile =
899 uploadDirectory + '/' + component->ArchiveFile;
900 cmCPackLogger(cmCPackLog::LOG_OUTPUT,
901 "- Building downloaded component archive: " << archiveFile
902 << std::endl);
903 if (cmSystemTools::FileExists(archiveFile, true)) {
904 if (!cmSystemTools::RemoveFile(archiveFile)) {
905 cmCPackLogger(cmCPackLog::LOG_ERROR,
906 "Unable to remove archive file " << archiveFile
907 << std::endl);
908 return false;
912 // Find a ZIP program
913 if (!IsSet("ZIP_EXECUTABLE")) {
914 ReadListFile("Internal/CPack/CPackZIP.cmake");
916 if (!IsSet("ZIP_EXECUTABLE")) {
917 cmCPackLogger(cmCPackLog::LOG_ERROR,
918 "Unable to find ZIP program" << std::endl);
919 return false;
923 if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY") ||
924 !RequireOption("CPACK_TEMPORARY_DIRECTORY") ||
925 !RequireOption("CPACK_ZIP_NEED_QUOTES") ||
926 !RequireOption("CPACK_ZIP_COMMAND")) {
927 return false;
930 // The directory where this component's files reside
931 const std::string& dirName =
932 cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name);
934 // Build the list of files to go into this archive
935 const std::string& zipListFileName =
936 cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
937 const bool needQuotesInFile = GetOption("CPACK_ZIP_NEED_QUOTES").IsOn();
938 { // the scope is needed for cmGeneratedFileStream
939 cmGeneratedFileStream out(zipListFileName);
940 for (const std::string& i : component->Files) {
941 out << (needQuotesInFile ? Quote(i) : i) << std::endl;
945 // Build the archive in the upload area
946 std::string cmd = GetOption("CPACK_ZIP_COMMAND");
947 cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
948 cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
949 zipListFileName.c_str());
950 std::string output;
951 int retVal = -1;
952 const bool res = cmSystemTools::RunSingleCommand(
953 cmd, &output, &output, &retVal, dirName.c_str(), this->GeneratorVerbose,
954 cmDuration::zero());
955 if (!res || retVal) {
956 std::string tmpFile =
957 cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
958 cmGeneratedFileStream ofs(tmpFile);
959 ofs << "# Run command: " << cmd << std::endl
960 << "# Output:" << std::endl
961 << output << std::endl;
962 cmCPackLogger(cmCPackLog::LOG_ERROR,
963 "Problem running zip command: " << cmd << std::endl
964 << "Please check " << tmpFile
965 << " for errors"
966 << std::endl);
967 return false;
970 // Try to get the SHA256 hash of the archive file
971 if (hash == nullptr) {
972 return true;
975 #ifdef _WIN32
976 const std::string& hashCmd =
977 cmStrCat("certutil -hashfile ", QuotePath(archiveFile), " SHA256");
979 std::string hashOutput;
980 int hashRetVal = -1;
981 const bool hashRes = cmSystemTools::RunSingleCommand(
982 hashCmd, &hashOutput, &hashOutput, &hashRetVal, nullptr,
983 this->GeneratorVerbose, cmDuration::zero());
984 if (!hashRes || hashRetVal) {
985 cmCPackLogger(cmCPackLog::LOG_WARNING,
986 "Problem running certutil command: " << hashCmd
987 << std::endl);
989 *hash = cmTrimWhitespace(cmTokenize(hashOutput, "\n").at(1));
991 if (hash->length() != 64) {
992 cmCPackLogger(cmCPackLog::LOG_WARNING,
993 "Problem parsing certutil output of command: " << hashCmd
994 << std::endl);
995 hash->clear();
997 #endif
999 return true;
1002 cmValue cmCPackInnoSetupGenerator::RequireOption(const std::string& key)
1004 cmValue value = GetOption(key);
1006 if (!value) {
1007 cmCPackLogger(cmCPackLog::LOG_ERROR,
1008 "Required variable " << key << " not set" << std::endl);
1011 return value;
1014 std::string cmCPackInnoSetupGenerator::CustomComponentInstallDirectory(
1015 const cmCPackComponent* component)
1017 cmValue outputDir = GetOption(
1018 cmStrCat("CPACK_INNOSETUP_", component->Name, "_INSTALL_DIRECTORY"));
1019 if (outputDir) {
1020 std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(outputDir);
1021 cmStripSuffixIfExists(destDir, '\\');
1024 * Add a dir instruction for the custom directory
1025 * (only once and not for Inno Setup constants ending with '}')
1027 static std::vector<std::string> customDirectories;
1028 if (!cmHasSuffix(destDir, '}') &&
1029 std::find(customDirectories.begin(), customDirectories.end(),
1030 component->Name) == customDirectories.end()) {
1031 cmCPackInnoSetupKeyValuePairs params;
1032 params["Name"] = QuotePath(destDir);
1033 params["Components"] =
1034 CreateRecursiveComponentPath(component->Group, component->Name);
1036 dirInstructions.push_back(ISKeyValueLine(params));
1037 customDirectories.push_back(component->Name);
1039 return destDir;
1042 return "{app}";
1045 std::string cmCPackInnoSetupGenerator::TranslateBool(const std::string& value)
1047 if (value.empty()) {
1048 return value;
1051 SetOptionIfNotSet("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT", "ON");
1052 if (GetOption("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT").IsOn()) {
1053 if (cmIsOn(value)) {
1054 return "yes";
1056 if (cmIsOff(value)) {
1057 return "no";
1061 return value;
1064 std::string cmCPackInnoSetupGenerator::ISKeyValueLine(
1065 const cmCPackInnoSetupKeyValuePairs& params)
1068 * To simplify readability of the generated code, the keys are sorted.
1069 * Unknown keys are ignored to avoid errors during compilation.
1071 static const char* const availableKeys[] = {
1072 "Source", "DestDir", "Name", "Filename",
1073 "Description", "GroupDescription", "MessagesFile", "Types",
1074 "ExternalSize", "BeforeInstall", "Flags", "Components",
1075 "Tasks"
1078 std::vector<std::string> keys;
1079 for (const char* i : availableKeys) {
1080 if (params.count(i)) {
1081 keys.emplace_back(cmStrCat(i, ": ", params.at(i)));
1085 return cmJoin(keys, "; ");
1088 std::string cmCPackInnoSetupGenerator::CreateRecursiveComponentPath(
1089 cmCPackComponentGroup* group, const std::string& path)
1091 if (group == nullptr) {
1092 return path;
1095 const std::string& newPath =
1096 path.empty() ? group->Name : cmStrCat(group->Name, '\\', path);
1097 return CreateRecursiveComponentPath(group->ParentGroup, newPath);
1100 void cmCPackInnoSetupGenerator::CreateRecursiveComponentGroups(
1101 cmCPackComponentGroup* group)
1103 if (group == nullptr) {
1104 return;
1107 CreateRecursiveComponentGroups(group->ParentGroup);
1109 static std::vector<cmCPackComponentGroup*> processedGroups;
1110 if (std::find(processedGroups.begin(), processedGroups.end(), group) ==
1111 processedGroups.end()) {
1112 processedGroups.push_back(group);
1114 cmCPackInnoSetupKeyValuePairs params;
1116 params["Name"] = Quote(CreateRecursiveComponentPath(group));
1117 params["Description"] = Quote(group->DisplayName);
1119 componentInstructions.push_back(ISKeyValueLine(params));
1123 std::string cmCPackInnoSetupGenerator::Quote(const std::string& string)
1125 if (cmHasPrefix(string, '"') && cmHasSuffix(string, '"')) {
1126 return Quote(string.substr(1, string.length() - 2));
1129 // Double quote syntax
1130 std::string nString = string;
1131 cmSystemTools::ReplaceString(nString, "\"", "\"\"");
1132 return cmStrCat('"', nString, '"');
1135 std::string cmCPackInnoSetupGenerator::QuotePath(const std::string& path,
1136 PathType type)
1138 #ifdef _WIN32
1139 static_cast<void>(type);
1140 #else
1141 if (type == PathType::Native) {
1142 return Quote(cmSystemTools::ConvertToUnixOutputPath(path));
1144 #endif
1145 return Quote(cmSystemTools::ConvertToWindowsOutputPath(path));
1148 std::string cmCPackInnoSetupGenerator::PrepareForConstant(
1149 const std::string& string)
1151 std::string nString = string;
1153 cmSystemTools::ReplaceString(nString, "%", "%25"); // First replacement!
1154 cmSystemTools::ReplaceString(nString, "\"", "%22");
1155 cmSystemTools::ReplaceString(nString, ",", "%2c");
1156 cmSystemTools::ReplaceString(nString, "|", "%7c");
1157 cmSystemTools::ReplaceString(nString, "}", "%7d");
1159 return nString;