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"
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 "cmStringAlgorithms.h"
23 #include "cmSystemTools.h"
26 /* NSIS uses different command line syntax on Windows and others */
33 cmCPackNSISGenerator::cmCPackNSISGenerator(bool nsis64)
35 this->Nsis64 = nsis64;
38 cmCPackNSISGenerator::~cmCPackNSISGenerator() = default;
40 int cmCPackNSISGenerator::PackageFiles()
42 // TODO: Fix nsis to force out file name
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."
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."
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('/');
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);
76 outputDir = this->CustomComponentInstallDirectory(fileN);
79 // Strip off the component part of the path.
80 fileN = fileN.substr(pos + 1);
82 std::replace(fileN.begin(), fileN.end(), '/', '\\');
84 str << " Delete \"" << outputDir << "\\" << fileN << "\"" << std::endl;
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);
98 if (!this->Components.empty()) {
99 // If this is a component installation, strip off the component
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
105 componentName = fileN.substr(0, slash);
107 // Strip off the component part of the path.
108 fileN.erase(0, slash + 1);
111 std::replace(fileN.begin(), fileN.end(), '/', '\\');
113 const std::string componentOutputDir =
114 this->CustomComponentInstallDirectory(componentName);
116 dstr << " RMDir \"" << componentOutputDir << "\\" << fileN << "\""
118 if (!componentName.empty()) {
119 this->Components[componentName].Directories.push_back(std::move(fileN));
122 cmCPackLogger(cmCPackLog::LOG_DEBUG,
123 "Uninstall Dirs: " << dstr.str() << std::endl);
124 this->SetOptionIfNotSet("CPACK_NSIS_DELETE_DIRECTORIES", dstr.str());
126 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
127 "Configure file: " << nsisInFileName << " to " << nsisFileName
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");
136 if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
138 cmStrCat("!define MUI_UNICON \"",
139 this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n");
141 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE",
142 installerIconCode.c_str());
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");
150 if (!installerHeaderImage.empty()) {
151 std::string installerIconCode = cmStrCat(
152 "!define MUI_HEADERIMAGE_BITMAP \"", installerHeaderImage, "\"\n");
153 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE",
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);
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);
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",
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",
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");
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",
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");
208 if (this->IsSet("CPACK_NSIS_MANIFEST_DPI_AWARE")) {
209 this->SetOptionIfNotSet("CPACK_NSIS_MANIFEST_DPI_AWARE_CODE",
210 "ManifestDPIAware true");
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",
222 if (possiblePositions.find(wantedPosition) ==
223 possiblePositions.end()) {
224 cmCPackLogger(cmCPackLog::LOG_ERROR,
225 "Unsupported branding text trim position "
226 << wantedPosition << std::endl);
229 brandingTextPosition = wantedPosition;
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);
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);
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", "");
256 std::string componentCode;
257 std::string sectionList;
258 std::string selectedVarsList;
259 std::string componentDescriptions;
260 std::string groupDescriptions;
261 std::string installTypesCode;
263 std::ostringstream macrosOut;
264 bool anyDownloadedComponents = false;
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;
273 for (cmCPackInstallationType* installType : installTypes) {
274 installTypesCode += "InstType \"";
275 installTypesCode += installType->DisplayName;
276 installTypesCode += "\"\n";
279 // Create installation groups first
280 for (auto& group : this->ComponentGroups) {
281 if (group.second.ParentGroup == nullptr) {
283 this->CreateComponentGroupDescription(&group.second, macrosOut);
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) +
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.
302 anyDownloadedComponents =
303 anyDownloadedComponents || comp.second.IsDownloaded;
305 if (!comp.second.Group) {
307 this->CreateComponentDescription(&comp.second, macrosOut);
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";
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) +
326 componentCode += macrosOut.str();
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");
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);
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";
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",
355 this->SetOption("CPACK_NSIS_DEFINES", defines);
358 this->ConfigureFile(nsisInInstallOptions, nsisInstallOptions);
359 this->ConfigureFile(nsisInFileName, nsisFileName);
360 std::string nsisCmd =
361 cmStrCat('"', this->GetOption("CPACK_INSTALLER_PROGRAM"), "\" \"",
363 cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << nsisCmd << std::endl);
366 bool res = cmSystemTools::RunSingleCommand(
367 nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
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
377 << tmpFile << " for errors"
384 int cmCPackNSISGenerator::InitializeInternal()
386 if (cmIsOn(this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
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)."
392 this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
395 cmCPackLogger(cmCPackLog::LOG_DEBUG,
396 "cmCPackNSISGenerator::Initialize()" << std::endl);
397 std::vector<std::string> path;
398 std::string nsisPath;
399 bool gotRegValue = false;
404 cmsys::SystemTools::ReadRegistryValue(
405 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
406 cmsys::SystemTools::KeyWOW64_64)) {
410 cmsys::SystemTools::ReadRegistryValue(
411 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
412 cmsys::SystemTools::KeyWOW64_64)) {
417 cmsys::SystemTools::ReadRegistryValue(
418 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
419 cmsys::SystemTools::KeyWOW64_32)) {
423 cmsys::SystemTools::ReadRegistryValue(
424 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) {
428 cmsys::SystemTools::ReadRegistryValue(
429 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
430 cmsys::SystemTools::KeyWOW64_32)) {
434 cmsys::SystemTools::ReadRegistryValue(
435 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) {
440 path.push_back(nsisPath);
444 this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLE", "makensis");
445 nsisPath = cmSystemTools::FindProgram(
446 this->GetOption("CPACK_NSIS_EXECUTABLE"), path, false);
448 if (nsisPath.empty()) {
450 cmCPackLog::LOG_ERROR,
451 "Cannot find NSIS compiler makensis: likely it is not installed, "
452 "or not in your PATH"
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"
467 std::string nsisCmd = "\"" + nsisPath + "\" " NSIS_OPT "VERSION";
468 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
469 "Test NSIS version: " << nsisCmd << std::endl);
472 bool resS = cmSystemTools::RunSingleCommand(
473 nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
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"
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);
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);
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
524 cmExpandList(cpackPackageDeskTopLinks, cpackPackageDesktopLinksVector);
525 for (std::string const& cpdl : cpackPackageDesktopLinksVector) {
526 cmCPackLogger(cmCPackLog::LOG_DEBUG,
527 "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl);
530 cmCPackLogger(cmCPackLog::LOG_DEBUG,
531 "CPACK_CREATE_DESKTOP_LINKS: "
532 << "not set" << std::endl);
535 std::ostringstream str;
536 std::ostringstream deleteStr;
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) {
546 cmCPackLog::LOG_ERROR,
547 "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
552 std::vector<std::string>::iterator it;
553 for (it = cpackPackageExecutablesVector.begin();
554 it != cpackPackageExecutablesVector.end(); ++it) {
555 std::string execName = *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\""
577 this->CreateMenuLinks(str, deleteStr);
578 this->SetOptionIfNotSet("CPACK_NSIS_CREATE_ICONS", str.str());
579 this->SetOptionIfNotSet("CPACK_NSIS_DELETE_ICONS", deleteStr.str());
581 this->SetOptionIfNotSet("CPACK_NSIS_COMPRESSOR", "lzma");
583 return this->Superclass::InitializeInternal();
586 void cmCPackNSISGenerator::CreateMenuLinks(std::ostream& str,
587 std::ostream& deleteStr)
589 cmValue cpackMenuLinks = this->GetOption("CPACK_NSIS_MENU_LINKS");
590 if (!cpackMenuLinks) {
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) {
599 cmCPackLog::LOG_ERROR,
600 "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
606 static cmsys::RegularExpression urlRegex(
607 "^(mailto:|(ftps?|https?|news)://).*$");
609 std::vector<std::string>::iterator it;
610 for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end();
612 std::string sourceName = *it;
613 const bool url = urlRegex.find(sourceName);
615 // Convert / to \ in filenames, but not in urls:
618 std::replace(sourceName.begin(), sourceName.end(), '/', '\\');
622 std::string linkName = *it;
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;
629 str << R"( WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
630 << R"(.url" "InternetShortcut" "URL" ")" << sourceName << "\""
632 deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
633 << ".url\"" << std::endl;
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\""
649 bool cmCPackNSISGenerator::GetListOfSubdirectories(
650 const char* topdir, std::vector<std::string>& dirs)
652 cmsys::Directory dir;
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)) {
667 dirs.emplace_back(topdir);
671 enum cmCPackGenerator::CPackSetDestdirSupport
672 cmCPackNSISGenerator::SupportsSetDestdir() const
674 return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED;
677 bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const
682 bool cmCPackNSISGenerator::SupportsComponentInstallation() const
687 std::string cmCPackNSISGenerator::CreateComponentDescription(
688 cmCPackComponent* component, std::ostream& macrosOut)
690 // Basic description of the component
691 std::string componentCode = "Section ";
692 if (component->IsDisabledByDefault) {
693 componentCode += "/o ";
695 componentCode += "\"";
696 if (component->IsHidden) {
697 componentCode += "-";
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;
708 componentCode += " SectionIn" + out.str() + "\n";
711 const std::string componentOutputDir =
712 this->CustomComponentInstallDirectory(component->Name);
713 componentCode += cmStrCat(" SetOutPath \"", componentOutputDir, "\"\n");
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();
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;
734 cmStrCat(this->GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
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);
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
750 if (cmSystemTools::FileExists(archiveFile, true)) {
751 if (!cmSystemTools::RemoveFile(archiveFile)) {
752 cmCPackLogger(cmCPackLog::LOG_ERROR,
753 "Unable to remove archive file " << archiveFile
759 // Find a ZIP program
760 if (!this->IsSet("ZIP_EXECUTABLE")) {
761 this->ReadListFile("Internal/CPack/CPackZIP.cmake");
763 if (!this->IsSet("ZIP_EXECUTABLE")) {
764 cmCPackLogger(cmCPackLog::LOG_ERROR,
765 "Unable to find ZIP program" << std::endl);
770 // The directory where this component's files reside
771 std::string dirName = cmStrCat(
772 this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name, '/');
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) {
787 if (needQuotesInFile) {
792 totalSize += cmSystemTools::FileLength(dirName + file);
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());
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
816 << tmpFile << " for errors"
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;
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();
841 " File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n";
843 componentCode += "SectionEnd\n";
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";
850 for (std::string const& pathIt : component->Files) {
852 std::replace(path.begin(), path.end(), '/', '\\');
853 macrosOut << " Delete \"" << componentOutputDir << "\\" << path << "\"\n";
855 for (std::string const& pathIt : component->Directories) {
857 std::replace(path.begin(), path.end(), '/', '\\');
858 macrosOut << " RMDir \"" << componentOutputDir << "\\" << path << "\"\n";
860 macrosOut << " noremove_" << component->Name << ":\n";
861 macrosOut << "!macroend\n";
863 // Macro used to select each of the components that this component
865 std::set<cmCPackComponent*> visited;
866 macrosOut << "!macro Select_" << component->Name << "_depends\n";
867 macrosOut << this->CreateSelectionDependenciesDescription(component,
869 macrosOut << "!macroend\n";
871 // Macro used to deselect each of the components that depend on this
874 macrosOut << "!macro Deselect_required_by_" << component->Name << "\n";
875 macrosOut << this->CreateDeselectionDependenciesDescription(component,
877 macrosOut << "!macroend\n";
878 return componentCode;
881 std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription(
882 cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
884 // Don't visit a component twice
885 if (visited.count(component)) {
888 visited.insert(component);
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";
899 << this->CreateSelectionDependenciesDescription(depend, visited).c_str();
905 std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription(
906 cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
908 // Don't visit a component twice
909 if (visited.count(component)) {
912 visited.insert(component);
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";
924 out << this->CreateDeselectionDependenciesDescription(depend, visited)
931 std::string cmCPackNSISGenerator::CreateComponentGroupDescription(
932 cmCPackComponentGroup* group, std::ostream& macrosOut)
934 if (group->Components.empty() && group->Subgroups.empty()) {
935 // Silently skip empty groups. NSIS doesn't support them.
939 std::string code = "SectionGroup ";
940 if (group->IsExpandedByDefault) {
944 code += "\"!" + group->DisplayName + "\" " + group->Name + "\n";
946 code += "\"" + group->DisplayName + "\" " + group->Name + "\n";
949 for (cmCPackComponentGroup* g : group->Subgroups) {
950 code += this->CreateComponentGroupDescription(g, macrosOut);
953 for (cmCPackComponent* comp : group->Components) {
954 if (comp->Files.empty()) {
958 code += this->CreateComponentDescription(comp, macrosOut);
960 code += "SectionGroupEnd\n";
964 std::string cmCPackNSISGenerator::CustomComponentInstallDirectory(
965 cm::string_view componentName)
967 cmValue outputDir = this->GetOption(
968 cmStrCat("CPACK_NSIS_", componentName, "_INSTALL_DIRECTORY"));
969 return outputDir ? *outputDir : "$INSTDIR";
972 std::string cmCPackNSISGenerator::TranslateNewlines(std::string str)
974 cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n");