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