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