Imported Upstream version 3.17.1
[platform/upstream/cmake.git] / Source / CPack / cmCPackNSISGenerator.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmCPackNSISGenerator.h"
4
5 #include <algorithm>
6 #include <cstdlib>
7 #include <cstring>
8 #include <map>
9 #include <sstream>
10 #include <utility>
11
12 #include "cmsys/Directory.hxx"
13 #include "cmsys/RegularExpression.hxx"
14
15 #include "cmAlgorithms.h"
16 #include "cmCPackComponentGroup.h"
17 #include "cmCPackGenerator.h"
18 #include "cmCPackLog.h"
19 #include "cmDuration.h"
20 #include "cmGeneratedFileStream.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
23
24 /* NSIS uses different command line syntax on Windows and others */
25 #ifdef _WIN32
26 #  define NSIS_OPT "/"
27 #else
28 #  define NSIS_OPT "-"
29 #endif
30
31 cmCPackNSISGenerator::cmCPackNSISGenerator(bool nsis64)
32 {
33   Nsis64 = nsis64;
34 }
35
36 cmCPackNSISGenerator::~cmCPackNSISGenerator() = default;
37
38 int cmCPackNSISGenerator::PackageFiles()
39 {
40   // TODO: Fix nsis to force out file name
41
42   std::string nsisInFileName = this->FindTemplate("NSIS.template.in");
43   if (nsisInFileName.empty()) {
44     cmCPackLogger(cmCPackLog::LOG_ERROR,
45                   "CPack error: Could not find NSIS installer template file."
46                     << std::endl);
47     return false;
48   }
49   std::string nsisInInstallOptions =
50     this->FindTemplate("NSIS.InstallOptions.ini.in");
51   if (nsisInInstallOptions.empty()) {
52     cmCPackLogger(cmCPackLog::LOG_ERROR,
53                   "CPack error: Could not find NSIS installer options file."
54                     << std::endl);
55     return false;
56   }
57
58   std::string nsisFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
59   std::string tmpFile = cmStrCat(nsisFileName, "/NSISOutput.log");
60   std::string nsisInstallOptions = nsisFileName + "/NSIS.InstallOptions.ini";
61   nsisFileName += "/project.nsi";
62   std::ostringstream str;
63   for (std::string const& file : files) {
64     std::string outputDir = "$INSTDIR";
65     std::string fileN = cmSystemTools::RelativePath(toplevel, file);
66     if (!this->Components.empty()) {
67       const std::string::size_type pos = fileN.find('/');
68
69       // Use the custom component install directory if we have one
70       if (pos != std::string::npos) {
71         const std::string componentName = fileN.substr(0, pos);
72         outputDir = CustomComponentInstallDirectory(componentName);
73       } else {
74         outputDir = CustomComponentInstallDirectory(fileN);
75       }
76
77       // Strip off the component part of the path.
78       fileN = fileN.substr(pos + 1);
79     }
80     std::replace(fileN.begin(), fileN.end(), '/', '\\');
81
82     str << "  Delete \"" << outputDir << "\\" << fileN << "\"" << std::endl;
83   }
84   cmCPackLogger(cmCPackLog::LOG_DEBUG,
85                 "Uninstall Files: " << str.str() << std::endl);
86   this->SetOptionIfNotSet("CPACK_NSIS_DELETE_FILES", str.str().c_str());
87   std::vector<std::string> dirs;
88   this->GetListOfSubdirectories(toplevel.c_str(), dirs);
89   std::ostringstream dstr;
90   for (std::string const& dir : dirs) {
91     std::string componentName;
92     std::string fileN = cmSystemTools::RelativePath(toplevel, dir);
93     if (fileN.empty()) {
94       continue;
95     }
96     if (!Components.empty()) {
97       // If this is a component installation, strip off the component
98       // part of the path.
99       std::string::size_type slash = fileN.find('/');
100       if (slash != std::string::npos) {
101         // If this is a component installation, determine which component it
102         // is.
103         componentName = fileN.substr(0, slash);
104
105         // Strip off the component part of the path.
106         fileN = fileN.substr(slash + 1);
107       }
108     }
109     std::replace(fileN.begin(), fileN.end(), '/', '\\');
110
111     const std::string componentOutputDir =
112       CustomComponentInstallDirectory(componentName);
113
114     dstr << "  RMDir \"" << componentOutputDir << "\\" << fileN << "\""
115          << std::endl;
116     if (!componentName.empty()) {
117       this->Components[componentName].Directories.push_back(std::move(fileN));
118     }
119   }
120   cmCPackLogger(cmCPackLog::LOG_DEBUG,
121                 "Uninstall Dirs: " << dstr.str() << std::endl);
122   this->SetOptionIfNotSet("CPACK_NSIS_DELETE_DIRECTORIES", dstr.str().c_str());
123
124   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
125                 "Configure file: " << nsisInFileName << " to " << nsisFileName
126                                    << std::endl);
127   if (this->IsSet("CPACK_NSIS_MUI_ICON") ||
128       this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
129     std::string installerIconCode;
130     if (this->IsSet("CPACK_NSIS_MUI_ICON")) {
131       installerIconCode += cmStrCat(
132         "!define MUI_ICON \"", this->GetOption("CPACK_NSIS_MUI_ICON"), "\"\n");
133     }
134     if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
135       installerIconCode +=
136         cmStrCat("!define MUI_UNICON \"",
137                  this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n");
138     }
139     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE",
140                             installerIconCode.c_str());
141   }
142   std::string installerHeaderImage;
143   if (this->IsSet("CPACK_NSIS_MUI_HEADERIMAGE")) {
144     installerHeaderImage = this->GetOption("CPACK_NSIS_MUI_HEADERIMAGE");
145   } else if (this->IsSet("CPACK_PACKAGE_ICON")) {
146     installerHeaderImage = this->GetOption("CPACK_PACKAGE_ICON");
147   }
148   if (!installerHeaderImage.empty()) {
149     std::string installerIconCode = cmStrCat(
150       "!define MUI_HEADERIMAGE_BITMAP \"", installerHeaderImage, "\"\n");
151     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE",
152                             installerIconCode.c_str());
153   }
154
155   if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) {
156     std::string installerBitmapCode = cmStrCat(
157       "!define MUI_WELCOMEFINISHPAGE_BITMAP \"",
158       this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"), "\"\n");
159     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE",
160                             installerBitmapCode.c_str());
161   }
162
163   if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) {
164     std::string installerBitmapCode = cmStrCat(
165       "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"",
166       this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"), "\"\n");
167     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE",
168                             installerBitmapCode.c_str());
169   }
170
171   if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) {
172     std::string installerRunCode =
173       cmStrCat("!define MUI_FINISHPAGE_RUN \"$INSTDIR\\",
174                this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\',
175                this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"), "\"\n");
176     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE",
177                             installerRunCode.c_str());
178   }
179
180   if (this->IsSet("CPACK_NSIS_WELCOME_TITLE")) {
181     std::string welcomeTitleCode =
182       cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"",
183                this->GetOption("CPACK_NSIS_WELCOME_TITLE"), "\"");
184     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE",
185                             welcomeTitleCode.c_str());
186   }
187
188   if (this->IsSet("CPACK_NSIS_WELCOME_TITLE_3LINES")) {
189     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE",
190                             "!define MUI_WELCOMEPAGE_TITLE_3LINES");
191   }
192
193   if (this->IsSet("CPACK_NSIS_FINISH_TITLE")) {
194     std::string finishTitleCode =
195       cmStrCat("!define MUI_FINISHPAGE_TITLE \"",
196                this->GetOption("CPACK_NSIS_FINISH_TITLE"), "\"");
197     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE",
198                             finishTitleCode.c_str());
199   }
200
201   if (this->IsSet("CPACK_NSIS_FINISH_TITLE_3LINES")) {
202     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE",
203                             "!define MUI_FINISHPAGE_TITLE_3LINES");
204   }
205
206   // Setup all of the component sections
207   if (this->Components.empty()) {
208     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES", "");
209     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", "");
210     this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS", "");
211     this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL",
212                             R"(File /r "${INST_DIR}\*.*")");
213     this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS", "");
214     this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST", "");
215     this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS", "");
216   } else {
217     std::string componentCode;
218     std::string sectionList;
219     std::string selectedVarsList;
220     std::string componentDescriptions;
221     std::string groupDescriptions;
222     std::string installTypesCode;
223     std::string defines;
224     std::ostringstream macrosOut;
225     bool anyDownloadedComponents = false;
226
227     // Create installation types. The order is significant, so we first fill
228     // in a vector based on the indices, and print them in that order.
229     std::vector<cmCPackInstallationType*> installTypes(
230       this->InstallationTypes.size());
231     for (auto& installType : this->InstallationTypes) {
232       installTypes[installType.second.Index - 1] = &installType.second;
233     }
234     for (cmCPackInstallationType* installType : installTypes) {
235       installTypesCode += "InstType \"";
236       installTypesCode += installType->DisplayName;
237       installTypesCode += "\"\n";
238     }
239
240     // Create installation groups first
241     for (auto& group : this->ComponentGroups) {
242       if (group.second.ParentGroup == nullptr) {
243         componentCode +=
244           this->CreateComponentGroupDescription(&group.second, macrosOut);
245       }
246
247       // Add the group description, if any.
248       if (!group.second.Description.empty()) {
249         groupDescriptions += "  !insertmacro MUI_DESCRIPTION_TEXT ${" +
250           group.first + "} \"" +
251           cmCPackNSISGenerator::TranslateNewlines(group.second.Description) +
252           "\"\n";
253       }
254     }
255
256     // Create the remaining components, which aren't associated with groups.
257     for (auto& comp : this->Components) {
258       if (comp.second.Files.empty()) {
259         // NSIS cannot cope with components that have no files.
260         continue;
261       }
262
263       anyDownloadedComponents =
264         anyDownloadedComponents || comp.second.IsDownloaded;
265
266       if (!comp.second.Group) {
267         componentCode +=
268           this->CreateComponentDescription(&comp.second, macrosOut);
269       }
270
271       // Add this component to the various section lists.
272       sectionList += R"(  !insertmacro "${MacroName}" ")";
273       sectionList += comp.first;
274       sectionList += "\"\n";
275       selectedVarsList += "Var " + comp.first + "_selected\n";
276       selectedVarsList += "Var " + comp.first + "_was_installed\n";
277
278       // Add the component description, if any.
279       if (!comp.second.Description.empty()) {
280         componentDescriptions += "  !insertmacro MUI_DESCRIPTION_TEXT ${" +
281           comp.first + "} \"" +
282           cmCPackNSISGenerator::TranslateNewlines(comp.second.Description) +
283           "\"\n";
284       }
285     }
286
287     componentCode += macrosOut.str();
288
289     if (componentDescriptions.empty() && groupDescriptions.empty()) {
290       // Turn off the "Description" box
291       this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
292                               "!define MUI_COMPONENTSPAGE_NODESC");
293     } else {
294       componentDescriptions = "!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN\n" +
295         componentDescriptions + groupDescriptions +
296         "!insertmacro MUI_FUNCTION_DESCRIPTION_END\n";
297       this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
298                               componentDescriptions.c_str());
299     }
300
301     if (anyDownloadedComponents) {
302       defines += "!define CPACK_USES_DOWNLOAD\n";
303       if (cmIsOn(this->GetOption("CPACK_ADD_REMOVE"))) {
304         defines += "!define CPACK_NSIS_ADD_REMOVE\n";
305       }
306     }
307
308     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES",
309                             installTypesCode.c_str());
310     this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS",
311                             "!insertmacro MUI_PAGE_COMPONENTS");
312     this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL", "");
313     this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS",
314                             componentCode.c_str());
315     this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST",
316                             sectionList.c_str());
317     this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS",
318                             selectedVarsList.c_str());
319     this->SetOption("CPACK_NSIS_DEFINES", defines.c_str());
320   }
321
322   this->ConfigureFile(nsisInInstallOptions, nsisInstallOptions);
323   this->ConfigureFile(nsisInFileName, nsisFileName);
324   std::string nsisCmd =
325     cmStrCat('"', this->GetOption("CPACK_INSTALLER_PROGRAM"), "\" \"",
326              nsisFileName, '"');
327   cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << nsisCmd << std::endl);
328   std::string output;
329   int retVal = 1;
330   bool res = cmSystemTools::RunSingleCommand(
331     nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
332     cmDuration::zero());
333   if (!res || retVal) {
334     cmGeneratedFileStream ofs(tmpFile);
335     ofs << "# Run command: " << nsisCmd << std::endl
336         << "# Output:" << std::endl
337         << output << std::endl;
338     cmCPackLogger(cmCPackLog::LOG_ERROR,
339                   "Problem running NSIS command: " << nsisCmd << std::endl
340                                                    << "Please check "
341                                                    << tmpFile << " for errors"
342                                                    << std::endl);
343     return 0;
344   }
345   return 1;
346 }
347
348 int cmCPackNSISGenerator::InitializeInternal()
349 {
350   if (cmIsOn(this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
351     cmCPackLogger(
352       cmCPackLog::LOG_WARNING,
353       "NSIS Generator cannot work with CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
354       "This option will be reset to 0 (for this generator only)."
355         << std::endl);
356     this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
357   }
358
359   cmCPackLogger(cmCPackLog::LOG_DEBUG,
360                 "cmCPackNSISGenerator::Initialize()" << std::endl);
361   std::vector<std::string> path;
362   std::string nsisPath;
363   bool gotRegValue = false;
364
365 #ifdef _WIN32
366   if (Nsis64) {
367     if (!gotRegValue &&
368         cmsys::SystemTools::ReadRegistryValue(
369           "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
370           cmsys::SystemTools::KeyWOW64_64)) {
371       gotRegValue = true;
372     }
373     if (!gotRegValue &&
374         cmsys::SystemTools::ReadRegistryValue(
375           "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
376           cmsys::SystemTools::KeyWOW64_64)) {
377       gotRegValue = true;
378     }
379   }
380   if (!gotRegValue &&
381       cmsys::SystemTools::ReadRegistryValue(
382         "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
383         cmsys::SystemTools::KeyWOW64_32)) {
384     gotRegValue = true;
385   }
386   if (!gotRegValue &&
387       cmsys::SystemTools::ReadRegistryValue(
388         "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) {
389     gotRegValue = true;
390   }
391   if (!gotRegValue &&
392       cmsys::SystemTools::ReadRegistryValue(
393         "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
394         cmsys::SystemTools::KeyWOW64_32)) {
395     gotRegValue = true;
396   }
397   if (!gotRegValue &&
398       cmsys::SystemTools::ReadRegistryValue(
399         "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) {
400     gotRegValue = true;
401   }
402
403   if (gotRegValue) {
404     path.push_back(nsisPath);
405   }
406 #endif
407
408   nsisPath = cmSystemTools::FindProgram("makensis", path, false);
409
410   if (nsisPath.empty()) {
411     cmCPackLogger(
412       cmCPackLog::LOG_ERROR,
413       "Cannot find NSIS compiler makensis: likely it is not installed, "
414       "or not in your PATH"
415         << std::endl);
416
417     if (!gotRegValue) {
418       cmCPackLogger(
419         cmCPackLog::LOG_ERROR,
420         "Could not read NSIS registry value. This is usually caused by "
421         "NSIS not being installed. Please install NSIS from "
422         "http://nsis.sourceforge.net"
423           << std::endl);
424     }
425
426     return 0;
427   }
428
429   std::string nsisCmd = "\"" + nsisPath + "\" " NSIS_OPT "VERSION";
430   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
431                 "Test NSIS version: " << nsisCmd << std::endl);
432   std::string output;
433   int retVal = 1;
434   bool resS = cmSystemTools::RunSingleCommand(
435     nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
436     cmDuration::zero());
437   cmsys::RegularExpression versionRex("v([0-9]+.[0-9]+)");
438   cmsys::RegularExpression versionRexCVS("v(.*)\\.cvs");
439   if (!resS || retVal ||
440       (!versionRex.find(output) && !versionRexCVS.find(output))) {
441     const char* topDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
442     std::string tmpFile = cmStrCat(topDir ? topDir : ".", "/NSISOutput.log");
443     cmGeneratedFileStream ofs(tmpFile);
444     ofs << "# Run command: " << nsisCmd << std::endl
445         << "# Output:" << std::endl
446         << output << std::endl;
447     cmCPackLogger(cmCPackLog::LOG_ERROR,
448                   "Problem checking NSIS version with command: "
449                     << nsisCmd << std::endl
450                     << "Please check " << tmpFile << " for errors"
451                     << std::endl);
452     return 0;
453   }
454   if (versionRex.find(output)) {
455     double nsisVersion = atof(versionRex.match(1).c_str());
456     double minNSISVersion = 3.0;
457     cmCPackLogger(cmCPackLog::LOG_DEBUG,
458                   "NSIS Version: " << nsisVersion << std::endl);
459     if (nsisVersion < minNSISVersion) {
460       cmCPackLogger(cmCPackLog::LOG_ERROR,
461                     "CPack requires NSIS Version 3.0 or greater.  "
462                     "NSIS found on the system was: "
463                       << nsisVersion << std::endl);
464       return 0;
465     }
466   }
467   if (versionRexCVS.find(output)) {
468     // No version check for NSIS cvs build
469     cmCPackLogger(cmCPackLog::LOG_DEBUG,
470                   "NSIS Version: CVS " << versionRexCVS.match(1) << std::endl);
471   }
472   this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", nsisPath.c_str());
473   this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLES_DIRECTORY", "bin");
474   const char* cpackPackageExecutables =
475     this->GetOption("CPACK_PACKAGE_EXECUTABLES");
476   const char* cpackPackageDeskTopLinks =
477     this->GetOption("CPACK_CREATE_DESKTOP_LINKS");
478   const char* cpackNsisExecutablesDirectory =
479     this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY");
480   std::vector<std::string> cpackPackageDesktopLinksVector;
481   if (cpackPackageDeskTopLinks) {
482     cmCPackLogger(cmCPackLog::LOG_DEBUG,
483                   "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks
484                                                  << std::endl);
485
486     cmExpandList(cpackPackageDeskTopLinks, cpackPackageDesktopLinksVector);
487     for (std::string const& cpdl : cpackPackageDesktopLinksVector) {
488       cmCPackLogger(cmCPackLog::LOG_DEBUG,
489                     "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl);
490     }
491   } else {
492     cmCPackLogger(cmCPackLog::LOG_DEBUG,
493                   "CPACK_CREATE_DESKTOP_LINKS: "
494                     << "not set" << std::endl);
495   }
496
497   std::ostringstream str;
498   std::ostringstream deleteStr;
499
500   if (cpackPackageExecutables) {
501     cmCPackLogger(cmCPackLog::LOG_DEBUG,
502                   "The cpackPackageExecutables: " << cpackPackageExecutables
503                                                   << "." << std::endl);
504     std::vector<std::string> cpackPackageExecutablesVector =
505       cmExpandedList(cpackPackageExecutables);
506     if (cpackPackageExecutablesVector.size() % 2 != 0) {
507       cmCPackLogger(
508         cmCPackLog::LOG_ERROR,
509         "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
510         "<icon name>."
511           << std::endl);
512       return 0;
513     }
514     std::vector<std::string>::iterator it;
515     for (it = cpackPackageExecutablesVector.begin();
516          it != cpackPackageExecutablesVector.end(); ++it) {
517       std::string execName = *it;
518       ++it;
519       std::string linkName = *it;
520       str << R"(  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
521           << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
522           << execName << ".exe\"" << std::endl;
523       deleteStr << R"(  Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
524                 << ".lnk\"" << std::endl;
525       // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
526       // if so add a desktop link
527       if (cmContains(cpackPackageDesktopLinksVector, execName)) {
528         str << "  StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
529         str << "    CreateShortCut \"$DESKTOP\\" << linkName
530             << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
531             << execName << ".exe\"" << std::endl;
532         deleteStr << "  StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
533         deleteStr << "    Delete \"$DESKTOP\\" << linkName << ".lnk\""
534                   << std::endl;
535       }
536     }
537   }
538
539   this->CreateMenuLinks(str, deleteStr);
540   this->SetOptionIfNotSet("CPACK_NSIS_CREATE_ICONS", str.str().c_str());
541   this->SetOptionIfNotSet("CPACK_NSIS_DELETE_ICONS", deleteStr.str().c_str());
542
543   this->SetOptionIfNotSet("CPACK_NSIS_COMPRESSOR", "lzma");
544
545   return this->Superclass::InitializeInternal();
546 }
547
548 void cmCPackNSISGenerator::CreateMenuLinks(std::ostream& str,
549                                            std::ostream& deleteStr)
550 {
551   const char* cpackMenuLinks = this->GetOption("CPACK_NSIS_MENU_LINKS");
552   if (!cpackMenuLinks) {
553     return;
554   }
555   cmCPackLogger(cmCPackLog::LOG_DEBUG,
556                 "The cpackMenuLinks: " << cpackMenuLinks << "." << std::endl);
557   std::vector<std::string> cpackMenuLinksVector =
558     cmExpandedList(cpackMenuLinks);
559   if (cpackMenuLinksVector.size() % 2 != 0) {
560     cmCPackLogger(
561       cmCPackLog::LOG_ERROR,
562       "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
563       "<shortcut label>."
564         << std::endl);
565     return;
566   }
567
568   static cmsys::RegularExpression urlRegex(
569     "^(mailto:|(ftps?|https?|news)://).*$");
570
571   std::vector<std::string>::iterator it;
572   for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end();
573        ++it) {
574     std::string sourceName = *it;
575     const bool url = urlRegex.find(sourceName);
576
577     // Convert / to \ in filenames, but not in urls:
578     //
579     if (!url) {
580       std::replace(sourceName.begin(), sourceName.end(), '/', '\\');
581     }
582
583     ++it;
584     std::string linkName = *it;
585     if (!url) {
586       str << R"(  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
587           << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
588       deleteStr << R"(  Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
589                 << ".lnk\"" << std::endl;
590     } else {
591       str << R"(  WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
592           << R"(.url" "InternetShortcut" "URL" ")" << sourceName << "\""
593           << std::endl;
594       deleteStr << R"(  Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
595                 << ".url\"" << std::endl;
596     }
597     // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
598     // if so add a desktop link
599     std::string desktop = cmStrCat("CPACK_CREATE_DESKTOP_LINK_", linkName);
600     if (this->IsSet(desktop)) {
601       str << "  StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
602       str << "    CreateShortCut \"$DESKTOP\\" << linkName
603           << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
604       deleteStr << "  StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
605       deleteStr << "    Delete \"$DESKTOP\\" << linkName << ".lnk\""
606                 << std::endl;
607     }
608   }
609 }
610
611 bool cmCPackNSISGenerator::GetListOfSubdirectories(
612   const char* topdir, std::vector<std::string>& dirs)
613 {
614   cmsys::Directory dir;
615   dir.Load(topdir);
616   for (unsigned long i = 0; i < dir.GetNumberOfFiles(); ++i) {
617     const char* fileName = dir.GetFile(i);
618     if (strcmp(fileName, ".") != 0 && strcmp(fileName, "..") != 0) {
619       std::string const fullPath =
620         std::string(topdir).append("/").append(fileName);
621       if (cmsys::SystemTools::FileIsDirectory(fullPath) &&
622           !cmsys::SystemTools::FileIsSymlink(fullPath)) {
623         if (!this->GetListOfSubdirectories(fullPath.c_str(), dirs)) {
624           return false;
625         }
626       }
627     }
628   }
629   dirs.emplace_back(topdir);
630   return true;
631 }
632
633 enum cmCPackGenerator::CPackSetDestdirSupport
634 cmCPackNSISGenerator::SupportsSetDestdir() const
635 {
636   return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED;
637 }
638
639 bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const
640 {
641   return false;
642 }
643
644 bool cmCPackNSISGenerator::SupportsComponentInstallation() const
645 {
646   return true;
647 }
648
649 std::string cmCPackNSISGenerator::CreateComponentDescription(
650   cmCPackComponent* component, std::ostream& macrosOut)
651 {
652   // Basic description of the component
653   std::string componentCode = "Section ";
654   if (component->IsDisabledByDefault) {
655     componentCode += "/o ";
656   }
657   componentCode += "\"";
658   if (component->IsHidden) {
659     componentCode += "-";
660   }
661   componentCode += component->DisplayName + "\" " + component->Name + "\n";
662   if (component->IsRequired) {
663     componentCode += "  SectionIn RO\n";
664   } else if (!component->InstallationTypes.empty()) {
665     std::ostringstream out;
666     for (cmCPackInstallationType const* installType :
667          component->InstallationTypes) {
668       out << " " << installType->Index;
669     }
670     componentCode += "  SectionIn" + out.str() + "\n";
671   }
672
673   const std::string componentOutputDir =
674     CustomComponentInstallDirectory(component->Name);
675   componentCode += "  SetOutPath \"" + componentOutputDir + "\"\n";
676
677   // Create the actual installation commands
678   if (component->IsDownloaded) {
679     if (component->ArchiveFile.empty()) {
680       // Compute the name of the archive.
681       std::string packagesDir =
682         cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
683       std::ostringstream out;
684       out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-"
685           << component->Name << ".zip";
686       component->ArchiveFile = out.str();
687     }
688
689     // Create the directory for the upload area
690     const char* userUploadDirectory =
691       this->GetOption("CPACK_UPLOAD_DIRECTORY");
692     std::string uploadDirectory;
693     if (userUploadDirectory && *userUploadDirectory) {
694       uploadDirectory = userUploadDirectory;
695     } else {
696       uploadDirectory =
697         cmStrCat(this->GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
698     }
699     if (!cmSystemTools::FileExists(uploadDirectory)) {
700       if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
701         cmCPackLogger(cmCPackLog::LOG_ERROR,
702                       "Unable to create NSIS upload directory "
703                         << uploadDirectory << std::endl);
704         return "";
705       }
706     }
707
708     // Remove the old archive, if one exists
709     std::string archiveFile = uploadDirectory + '/' + component->ArchiveFile;
710     cmCPackLogger(cmCPackLog::LOG_OUTPUT,
711                   "-   Building downloaded component archive: " << archiveFile
712                                                                 << std::endl);
713     if (cmSystemTools::FileExists(archiveFile, true)) {
714       if (!cmSystemTools::RemoveFile(archiveFile)) {
715         cmCPackLogger(cmCPackLog::LOG_ERROR,
716                       "Unable to remove archive file " << archiveFile
717                                                        << std::endl);
718         return "";
719       }
720     }
721
722     // Find a ZIP program
723     if (!this->IsSet("ZIP_EXECUTABLE")) {
724       this->ReadListFile("Internal/CPack/CPackZIP.cmake");
725
726       if (!this->IsSet("ZIP_EXECUTABLE")) {
727         cmCPackLogger(cmCPackLog::LOG_ERROR,
728                       "Unable to find ZIP program" << std::endl);
729         return "";
730       }
731     }
732
733     // The directory where this component's files reside
734     std::string dirName = cmStrCat(
735       this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name, '/');
736
737     // Build the list of files to go into this archive, and determine the
738     // size of the installed component.
739     std::string zipListFileName = cmStrCat(
740       this->GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
741     bool needQuotesInFile = cmIsOn(this->GetOption("CPACK_ZIP_NEED_QUOTES"));
742     unsigned long totalSize = 0;
743     { // the scope is needed for cmGeneratedFileStream
744       cmGeneratedFileStream out(zipListFileName);
745       for (std::string const& file : component->Files) {
746         if (needQuotesInFile) {
747           out << "\"";
748         }
749         out << file;
750         if (needQuotesInFile) {
751           out << "\"";
752         }
753         out << std::endl;
754
755         totalSize += cmSystemTools::FileLength(dirName + file);
756       }
757     }
758
759     // Build the archive in the upload area
760     std::string cmd = this->GetOption("CPACK_ZIP_COMMAND");
761     cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
762     cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
763                                       zipListFileName.c_str());
764     std::string output;
765     int retVal = -1;
766     int res = cmSystemTools::RunSingleCommand(
767       cmd, &output, &output, &retVal, dirName.c_str(),
768       cmSystemTools::OUTPUT_NONE, cmDuration::zero());
769     if (!res || retVal) {
770       std::string tmpFile = cmStrCat(
771         this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
772       cmGeneratedFileStream ofs(tmpFile);
773       ofs << "# Run command: " << cmd << std::endl
774           << "# Output:" << std::endl
775           << output << std::endl;
776       cmCPackLogger(cmCPackLog::LOG_ERROR,
777                     "Problem running zip command: " << cmd << std::endl
778                                                     << "Please check "
779                                                     << tmpFile << " for errors"
780                                                     << std::endl);
781       return "";
782     }
783
784     // Create the NSIS code to download this file on-the-fly.
785     unsigned long totalSizeInKbytes = (totalSize + 512) / 1024;
786     if (totalSizeInKbytes == 0) {
787       totalSizeInKbytes = 1;
788     }
789     std::ostringstream out;
790     /* clang-format off */
791     out << "  AddSize " << totalSizeInKbytes << "\n"
792         << "  Push \"" << component->ArchiveFile << "\"\n"
793         << "  Call DownloadFile\n"
794         << "  ZipDLL::extractall \"$INSTDIR\\"
795         << component->ArchiveFile << "\" \"$INSTDIR\"\n"
796         <<  "  Pop $2 ; error message\n"
797                      "  StrCmp $2 \"success\" +2 0\n"
798                      "  MessageBox MB_OK \"Failed to unzip $2\"\n"
799                      "  Delete $INSTDIR\\$0\n";
800     /* clang-format on */
801     componentCode += out.str();
802   } else {
803     componentCode +=
804       "  File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n";
805   }
806   componentCode += "SectionEnd\n";
807
808   // Macro used to remove the component
809   macrosOut << "!macro Remove_${" << component->Name << "}\n";
810   macrosOut << "  IntCmp $" << component->Name << "_was_installed 0 noremove_"
811             << component->Name << "\n";
812   std::string path;
813   for (std::string const& pathIt : component->Files) {
814     path = pathIt;
815     std::replace(path.begin(), path.end(), '/', '\\');
816     macrosOut << "  Delete \"" << componentOutputDir << "\\" << path << "\"\n";
817   }
818   for (std::string const& pathIt : component->Directories) {
819     path = pathIt;
820     std::replace(path.begin(), path.end(), '/', '\\');
821     macrosOut << "  RMDir \"" << componentOutputDir << "\\" << path << "\"\n";
822   }
823   macrosOut << "  noremove_" << component->Name << ":\n";
824   macrosOut << "!macroend\n";
825
826   // Macro used to select each of the components that this component
827   // depends on.
828   std::set<cmCPackComponent*> visited;
829   macrosOut << "!macro Select_" << component->Name << "_depends\n";
830   macrosOut << CreateSelectionDependenciesDescription(component, visited);
831   macrosOut << "!macroend\n";
832
833   // Macro used to deselect each of the components that depend on this
834   // component.
835   visited.clear();
836   macrosOut << "!macro Deselect_required_by_" << component->Name << "\n";
837   macrosOut << CreateDeselectionDependenciesDescription(component, visited);
838   macrosOut << "!macroend\n";
839   return componentCode;
840 }
841
842 std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription(
843   cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
844 {
845   // Don't visit a component twice
846   if (visited.count(component)) {
847     return std::string();
848   }
849   visited.insert(component);
850
851   std::ostringstream out;
852   for (cmCPackComponent* depend : component->Dependencies) {
853     // Write NSIS code to select this dependency
854     out << "  SectionGetFlags ${" << depend->Name << "} $0\n";
855     out << "  IntOp $0 $0 | ${SF_SELECTED}\n";
856     out << "  SectionSetFlags ${" << depend->Name << "} $0\n";
857     out << "  IntOp $" << depend->Name << "_selected 0 + ${SF_SELECTED}\n";
858     // Recurse
859     out << CreateSelectionDependenciesDescription(depend, visited).c_str();
860   }
861
862   return out.str();
863 }
864
865 std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription(
866   cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
867 {
868   // Don't visit a component twice
869   if (visited.count(component)) {
870     return std::string();
871   }
872   visited.insert(component);
873
874   std::ostringstream out;
875   for (cmCPackComponent* depend : component->ReverseDependencies) {
876     // Write NSIS code to deselect this dependency
877     out << "  SectionGetFlags ${" << depend->Name << "} $0\n";
878     out << "  IntOp $1 ${SF_SELECTED} ~\n";
879     out << "  IntOp $0 $0 & $1\n";
880     out << "  SectionSetFlags ${" << depend->Name << "} $0\n";
881     out << "  IntOp $" << depend->Name << "_selected 0 + 0\n";
882
883     // Recurse
884     out << CreateDeselectionDependenciesDescription(depend, visited).c_str();
885   }
886
887   return out.str();
888 }
889
890 std::string cmCPackNSISGenerator::CreateComponentGroupDescription(
891   cmCPackComponentGroup* group, std::ostream& macrosOut)
892 {
893   if (group->Components.empty() && group->Subgroups.empty()) {
894     // Silently skip empty groups. NSIS doesn't support them.
895     return std::string();
896   }
897
898   std::string code = "SectionGroup ";
899   if (group->IsExpandedByDefault) {
900     code += "/e ";
901   }
902   if (group->IsBold) {
903     code += "\"!" + group->DisplayName + "\" " + group->Name + "\n";
904   } else {
905     code += "\"" + group->DisplayName + "\" " + group->Name + "\n";
906   }
907
908   for (cmCPackComponentGroup* g : group->Subgroups) {
909     code += this->CreateComponentGroupDescription(g, macrosOut);
910   }
911
912   for (cmCPackComponent* comp : group->Components) {
913     if (comp->Files.empty()) {
914       continue;
915     }
916
917     code += this->CreateComponentDescription(comp, macrosOut);
918   }
919   code += "SectionGroupEnd\n";
920   return code;
921 }
922
923 std::string cmCPackNSISGenerator::CustomComponentInstallDirectory(
924   const std::string& componentName)
925 {
926   const char* outputDir =
927     this->GetOption("CPACK_NSIS_" + componentName + "_INSTALL_DIRECTORY");
928   const std::string componentOutputDir = (outputDir ? outputDir : "$INSTDIR");
929   return componentOutputDir;
930 }
931
932 std::string cmCPackNSISGenerator::TranslateNewlines(std::string str)
933 {
934   cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n");
935   return str;
936 }