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 "cmsys/Directory.hxx"
13 #include "cmsys/RegularExpression.hxx"
15 #include "cmAlgorithms.h"
16 #include "cmCPackComponentGroup.h"
17 #include "cmCPackGenerator.h"
18 #include "cmCPackLog.h"
19 #include "cmDuration.h"
20 #include "cmGeneratedFileStream.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
24 /* NSIS uses different command line syntax on Windows and others */
31 cmCPackNSISGenerator::cmCPackNSISGenerator(bool nsis64)
36 cmCPackNSISGenerator::~cmCPackNSISGenerator() = default;
38 int cmCPackNSISGenerator::PackageFiles()
40 // TODO: Fix nsis to force out file name
42 std::string nsisInFileName = this->FindTemplate("NSIS.template.in");
43 if (nsisInFileName.empty()) {
44 cmCPackLogger(cmCPackLog::LOG_ERROR,
45 "CPack error: Could not find NSIS installer template file."
49 std::string nsisInInstallOptions =
50 this->FindTemplate("NSIS.InstallOptions.ini.in");
51 if (nsisInInstallOptions.empty()) {
52 cmCPackLogger(cmCPackLog::LOG_ERROR,
53 "CPack error: Could not find NSIS installer options file."
58 std::string nsisFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
59 std::string tmpFile = cmStrCat(nsisFileName, "/NSISOutput.log");
60 std::string nsisInstallOptions = nsisFileName + "/NSIS.InstallOptions.ini";
61 nsisFileName += "/project.nsi";
62 std::ostringstream str;
63 for (std::string const& file : files) {
64 std::string outputDir = "$INSTDIR";
65 std::string fileN = cmSystemTools::RelativePath(toplevel, file);
66 if (!this->Components.empty()) {
67 const std::string::size_type pos = fileN.find('/');
69 // Use the custom component install directory if we have one
70 if (pos != std::string::npos) {
71 const std::string componentName = fileN.substr(0, pos);
72 outputDir = CustomComponentInstallDirectory(componentName);
74 outputDir = CustomComponentInstallDirectory(fileN);
77 // Strip off the component part of the path.
78 fileN = fileN.substr(pos + 1);
80 std::replace(fileN.begin(), fileN.end(), '/', '\\');
82 str << " Delete \"" << outputDir << "\\" << fileN << "\"" << std::endl;
84 cmCPackLogger(cmCPackLog::LOG_DEBUG,
85 "Uninstall Files: " << str.str() << std::endl);
86 this->SetOptionIfNotSet("CPACK_NSIS_DELETE_FILES", str.str().c_str());
87 std::vector<std::string> dirs;
88 this->GetListOfSubdirectories(toplevel.c_str(), dirs);
89 std::ostringstream dstr;
90 for (std::string const& dir : dirs) {
91 std::string componentName;
92 std::string fileN = cmSystemTools::RelativePath(toplevel, dir);
96 if (!Components.empty()) {
97 // If this is a component installation, strip off the component
99 std::string::size_type slash = fileN.find('/');
100 if (slash != std::string::npos) {
101 // If this is a component installation, determine which component it
103 componentName = fileN.substr(0, slash);
105 // Strip off the component part of the path.
106 fileN = fileN.substr(slash + 1);
109 std::replace(fileN.begin(), fileN.end(), '/', '\\');
111 const std::string componentOutputDir =
112 CustomComponentInstallDirectory(componentName);
114 dstr << " RMDir \"" << componentOutputDir << "\\" << fileN << "\""
116 if (!componentName.empty()) {
117 this->Components[componentName].Directories.push_back(std::move(fileN));
120 cmCPackLogger(cmCPackLog::LOG_DEBUG,
121 "Uninstall Dirs: " << dstr.str() << std::endl);
122 this->SetOptionIfNotSet("CPACK_NSIS_DELETE_DIRECTORIES", dstr.str().c_str());
124 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
125 "Configure file: " << nsisInFileName << " to " << nsisFileName
127 if (this->IsSet("CPACK_NSIS_MUI_ICON") ||
128 this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
129 std::string installerIconCode;
130 if (this->IsSet("CPACK_NSIS_MUI_ICON")) {
131 installerIconCode += cmStrCat(
132 "!define MUI_ICON \"", this->GetOption("CPACK_NSIS_MUI_ICON"), "\"\n");
134 if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
136 cmStrCat("!define MUI_UNICON \"",
137 this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n");
139 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE",
140 installerIconCode.c_str());
142 std::string installerHeaderImage;
143 if (this->IsSet("CPACK_NSIS_MUI_HEADERIMAGE")) {
144 installerHeaderImage = this->GetOption("CPACK_NSIS_MUI_HEADERIMAGE");
145 } else if (this->IsSet("CPACK_PACKAGE_ICON")) {
146 installerHeaderImage = this->GetOption("CPACK_PACKAGE_ICON");
148 if (!installerHeaderImage.empty()) {
149 std::string installerIconCode = cmStrCat(
150 "!define MUI_HEADERIMAGE_BITMAP \"", installerHeaderImage, "\"\n");
151 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE",
152 installerIconCode.c_str());
155 if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) {
156 std::string installerBitmapCode = cmStrCat(
157 "!define MUI_WELCOMEFINISHPAGE_BITMAP \"",
158 this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"), "\"\n");
159 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE",
160 installerBitmapCode.c_str());
163 if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) {
164 std::string installerBitmapCode = cmStrCat(
165 "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"",
166 this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"), "\"\n");
167 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE",
168 installerBitmapCode.c_str());
171 if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) {
172 std::string installerRunCode =
173 cmStrCat("!define MUI_FINISHPAGE_RUN \"$INSTDIR\\",
174 this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\',
175 this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"), "\"\n");
176 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE",
177 installerRunCode.c_str());
180 if (this->IsSet("CPACK_NSIS_WELCOME_TITLE")) {
181 std::string welcomeTitleCode =
182 cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"",
183 this->GetOption("CPACK_NSIS_WELCOME_TITLE"), "\"");
184 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE",
185 welcomeTitleCode.c_str());
188 if (this->IsSet("CPACK_NSIS_WELCOME_TITLE_3LINES")) {
189 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE",
190 "!define MUI_WELCOMEPAGE_TITLE_3LINES");
193 if (this->IsSet("CPACK_NSIS_FINISH_TITLE")) {
194 std::string finishTitleCode =
195 cmStrCat("!define MUI_FINISHPAGE_TITLE \"",
196 this->GetOption("CPACK_NSIS_FINISH_TITLE"), "\"");
197 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE",
198 finishTitleCode.c_str());
201 if (this->IsSet("CPACK_NSIS_FINISH_TITLE_3LINES")) {
202 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE",
203 "!define MUI_FINISHPAGE_TITLE_3LINES");
206 // Setup all of the component sections
207 if (this->Components.empty()) {
208 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES", "");
209 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", "");
210 this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS", "");
211 this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL",
212 R"(File /r "${INST_DIR}\*.*")");
213 this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS", "");
214 this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST", "");
215 this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS", "");
217 std::string componentCode;
218 std::string sectionList;
219 std::string selectedVarsList;
220 std::string componentDescriptions;
221 std::string groupDescriptions;
222 std::string installTypesCode;
224 std::ostringstream macrosOut;
225 bool anyDownloadedComponents = false;
227 // Create installation types. The order is significant, so we first fill
228 // in a vector based on the indices, and print them in that order.
229 std::vector<cmCPackInstallationType*> installTypes(
230 this->InstallationTypes.size());
231 for (auto& installType : this->InstallationTypes) {
232 installTypes[installType.second.Index - 1] = &installType.second;
234 for (cmCPackInstallationType* installType : installTypes) {
235 installTypesCode += "InstType \"";
236 installTypesCode += installType->DisplayName;
237 installTypesCode += "\"\n";
240 // Create installation groups first
241 for (auto& group : this->ComponentGroups) {
242 if (group.second.ParentGroup == nullptr) {
244 this->CreateComponentGroupDescription(&group.second, macrosOut);
247 // Add the group description, if any.
248 if (!group.second.Description.empty()) {
249 groupDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" +
250 group.first + "} \"" +
251 cmCPackNSISGenerator::TranslateNewlines(group.second.Description) +
256 // Create the remaining components, which aren't associated with groups.
257 for (auto& comp : this->Components) {
258 if (comp.second.Files.empty()) {
259 // NSIS cannot cope with components that have no files.
263 anyDownloadedComponents =
264 anyDownloadedComponents || comp.second.IsDownloaded;
266 if (!comp.second.Group) {
268 this->CreateComponentDescription(&comp.second, macrosOut);
271 // Add this component to the various section lists.
272 sectionList += R"( !insertmacro "${MacroName}" ")";
273 sectionList += comp.first;
274 sectionList += "\"\n";
275 selectedVarsList += "Var " + comp.first + "_selected\n";
276 selectedVarsList += "Var " + comp.first + "_was_installed\n";
278 // Add the component description, if any.
279 if (!comp.second.Description.empty()) {
280 componentDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" +
281 comp.first + "} \"" +
282 cmCPackNSISGenerator::TranslateNewlines(comp.second.Description) +
287 componentCode += macrosOut.str();
289 if (componentDescriptions.empty() && groupDescriptions.empty()) {
290 // Turn off the "Description" box
291 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
292 "!define MUI_COMPONENTSPAGE_NODESC");
294 componentDescriptions = "!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN\n" +
295 componentDescriptions + groupDescriptions +
296 "!insertmacro MUI_FUNCTION_DESCRIPTION_END\n";
297 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
298 componentDescriptions.c_str());
301 if (anyDownloadedComponents) {
302 defines += "!define CPACK_USES_DOWNLOAD\n";
303 if (cmIsOn(this->GetOption("CPACK_ADD_REMOVE"))) {
304 defines += "!define CPACK_NSIS_ADD_REMOVE\n";
308 this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES",
309 installTypesCode.c_str());
310 this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS",
311 "!insertmacro MUI_PAGE_COMPONENTS");
312 this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL", "");
313 this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS",
314 componentCode.c_str());
315 this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST",
316 sectionList.c_str());
317 this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS",
318 selectedVarsList.c_str());
319 this->SetOption("CPACK_NSIS_DEFINES", defines.c_str());
322 this->ConfigureFile(nsisInInstallOptions, nsisInstallOptions);
323 this->ConfigureFile(nsisInFileName, nsisFileName);
324 std::string nsisCmd =
325 cmStrCat('"', this->GetOption("CPACK_INSTALLER_PROGRAM"), "\" \"",
327 cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << nsisCmd << std::endl);
330 bool res = cmSystemTools::RunSingleCommand(
331 nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
333 if (!res || retVal) {
334 cmGeneratedFileStream ofs(tmpFile);
335 ofs << "# Run command: " << nsisCmd << std::endl
336 << "# Output:" << std::endl
337 << output << std::endl;
338 cmCPackLogger(cmCPackLog::LOG_ERROR,
339 "Problem running NSIS command: " << nsisCmd << std::endl
341 << tmpFile << " for errors"
348 int cmCPackNSISGenerator::InitializeInternal()
350 if (cmIsOn(this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
352 cmCPackLog::LOG_WARNING,
353 "NSIS Generator cannot work with CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
354 "This option will be reset to 0 (for this generator only)."
356 this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
359 cmCPackLogger(cmCPackLog::LOG_DEBUG,
360 "cmCPackNSISGenerator::Initialize()" << std::endl);
361 std::vector<std::string> path;
362 std::string nsisPath;
363 bool gotRegValue = false;
368 cmsys::SystemTools::ReadRegistryValue(
369 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
370 cmsys::SystemTools::KeyWOW64_64)) {
374 cmsys::SystemTools::ReadRegistryValue(
375 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
376 cmsys::SystemTools::KeyWOW64_64)) {
381 cmsys::SystemTools::ReadRegistryValue(
382 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
383 cmsys::SystemTools::KeyWOW64_32)) {
387 cmsys::SystemTools::ReadRegistryValue(
388 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) {
392 cmsys::SystemTools::ReadRegistryValue(
393 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
394 cmsys::SystemTools::KeyWOW64_32)) {
398 cmsys::SystemTools::ReadRegistryValue(
399 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) {
404 path.push_back(nsisPath);
408 nsisPath = cmSystemTools::FindProgram("makensis", path, false);
410 if (nsisPath.empty()) {
412 cmCPackLog::LOG_ERROR,
413 "Cannot find NSIS compiler makensis: likely it is not installed, "
414 "or not in your PATH"
419 cmCPackLog::LOG_ERROR,
420 "Could not read NSIS registry value. This is usually caused by "
421 "NSIS not being installed. Please install NSIS from "
422 "http://nsis.sourceforge.net"
429 std::string nsisCmd = "\"" + nsisPath + "\" " NSIS_OPT "VERSION";
430 cmCPackLogger(cmCPackLog::LOG_VERBOSE,
431 "Test NSIS version: " << nsisCmd << std::endl);
434 bool resS = cmSystemTools::RunSingleCommand(
435 nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
437 cmsys::RegularExpression versionRex("v([0-9]+.[0-9]+)");
438 cmsys::RegularExpression versionRexCVS("v(.*)\\.cvs");
439 if (!resS || retVal ||
440 (!versionRex.find(output) && !versionRexCVS.find(output))) {
441 const char* topDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
442 std::string tmpFile = cmStrCat(topDir ? topDir : ".", "/NSISOutput.log");
443 cmGeneratedFileStream ofs(tmpFile);
444 ofs << "# Run command: " << nsisCmd << std::endl
445 << "# Output:" << std::endl
446 << output << std::endl;
447 cmCPackLogger(cmCPackLog::LOG_ERROR,
448 "Problem checking NSIS version with command: "
449 << nsisCmd << std::endl
450 << "Please check " << tmpFile << " for errors"
454 if (versionRex.find(output)) {
455 double nsisVersion = atof(versionRex.match(1).c_str());
456 double minNSISVersion = 3.0;
457 cmCPackLogger(cmCPackLog::LOG_DEBUG,
458 "NSIS Version: " << nsisVersion << std::endl);
459 if (nsisVersion < minNSISVersion) {
460 cmCPackLogger(cmCPackLog::LOG_ERROR,
461 "CPack requires NSIS Version 3.0 or greater. "
462 "NSIS found on the system was: "
463 << nsisVersion << std::endl);
467 if (versionRexCVS.find(output)) {
468 // No version check for NSIS cvs build
469 cmCPackLogger(cmCPackLog::LOG_DEBUG,
470 "NSIS Version: CVS " << versionRexCVS.match(1) << std::endl);
472 this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", nsisPath.c_str());
473 this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLES_DIRECTORY", "bin");
474 const char* cpackPackageExecutables =
475 this->GetOption("CPACK_PACKAGE_EXECUTABLES");
476 const char* cpackPackageDeskTopLinks =
477 this->GetOption("CPACK_CREATE_DESKTOP_LINKS");
478 const char* cpackNsisExecutablesDirectory =
479 this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY");
480 std::vector<std::string> cpackPackageDesktopLinksVector;
481 if (cpackPackageDeskTopLinks) {
482 cmCPackLogger(cmCPackLog::LOG_DEBUG,
483 "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks
486 cmExpandList(cpackPackageDeskTopLinks, cpackPackageDesktopLinksVector);
487 for (std::string const& cpdl : cpackPackageDesktopLinksVector) {
488 cmCPackLogger(cmCPackLog::LOG_DEBUG,
489 "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl);
492 cmCPackLogger(cmCPackLog::LOG_DEBUG,
493 "CPACK_CREATE_DESKTOP_LINKS: "
494 << "not set" << std::endl);
497 std::ostringstream str;
498 std::ostringstream deleteStr;
500 if (cpackPackageExecutables) {
501 cmCPackLogger(cmCPackLog::LOG_DEBUG,
502 "The cpackPackageExecutables: " << cpackPackageExecutables
503 << "." << std::endl);
504 std::vector<std::string> cpackPackageExecutablesVector =
505 cmExpandedList(cpackPackageExecutables);
506 if (cpackPackageExecutablesVector.size() % 2 != 0) {
508 cmCPackLog::LOG_ERROR,
509 "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
514 std::vector<std::string>::iterator it;
515 for (it = cpackPackageExecutablesVector.begin();
516 it != cpackPackageExecutablesVector.end(); ++it) {
517 std::string execName = *it;
519 std::string linkName = *it;
520 str << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
521 << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
522 << execName << ".exe\"" << std::endl;
523 deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
524 << ".lnk\"" << std::endl;
525 // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
526 // if so add a desktop link
527 if (cmContains(cpackPackageDesktopLinksVector, execName)) {
528 str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
529 str << " CreateShortCut \"$DESKTOP\\" << linkName
530 << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
531 << execName << ".exe\"" << std::endl;
532 deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
533 deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\""
539 this->CreateMenuLinks(str, deleteStr);
540 this->SetOptionIfNotSet("CPACK_NSIS_CREATE_ICONS", str.str().c_str());
541 this->SetOptionIfNotSet("CPACK_NSIS_DELETE_ICONS", deleteStr.str().c_str());
543 this->SetOptionIfNotSet("CPACK_NSIS_COMPRESSOR", "lzma");
545 return this->Superclass::InitializeInternal();
548 void cmCPackNSISGenerator::CreateMenuLinks(std::ostream& str,
549 std::ostream& deleteStr)
551 const char* cpackMenuLinks = this->GetOption("CPACK_NSIS_MENU_LINKS");
552 if (!cpackMenuLinks) {
555 cmCPackLogger(cmCPackLog::LOG_DEBUG,
556 "The cpackMenuLinks: " << cpackMenuLinks << "." << std::endl);
557 std::vector<std::string> cpackMenuLinksVector =
558 cmExpandedList(cpackMenuLinks);
559 if (cpackMenuLinksVector.size() % 2 != 0) {
561 cmCPackLog::LOG_ERROR,
562 "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
568 static cmsys::RegularExpression urlRegex(
569 "^(mailto:|(ftps?|https?|news)://).*$");
571 std::vector<std::string>::iterator it;
572 for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end();
574 std::string sourceName = *it;
575 const bool url = urlRegex.find(sourceName);
577 // Convert / to \ in filenames, but not in urls:
580 std::replace(sourceName.begin(), sourceName.end(), '/', '\\');
584 std::string linkName = *it;
586 str << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
587 << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
588 deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
589 << ".lnk\"" << std::endl;
591 str << R"( WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
592 << R"(.url" "InternetShortcut" "URL" ")" << sourceName << "\""
594 deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
595 << ".url\"" << std::endl;
597 // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
598 // if so add a desktop link
599 std::string desktop = cmStrCat("CPACK_CREATE_DESKTOP_LINK_", linkName);
600 if (this->IsSet(desktop)) {
601 str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
602 str << " CreateShortCut \"$DESKTOP\\" << linkName
603 << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
604 deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
605 deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\""
611 bool cmCPackNSISGenerator::GetListOfSubdirectories(
612 const char* topdir, std::vector<std::string>& dirs)
614 cmsys::Directory dir;
616 for (unsigned long i = 0; i < dir.GetNumberOfFiles(); ++i) {
617 const char* fileName = dir.GetFile(i);
618 if (strcmp(fileName, ".") != 0 && strcmp(fileName, "..") != 0) {
619 std::string const fullPath =
620 std::string(topdir).append("/").append(fileName);
621 if (cmsys::SystemTools::FileIsDirectory(fullPath) &&
622 !cmsys::SystemTools::FileIsSymlink(fullPath)) {
623 if (!this->GetListOfSubdirectories(fullPath.c_str(), dirs)) {
629 dirs.emplace_back(topdir);
633 enum cmCPackGenerator::CPackSetDestdirSupport
634 cmCPackNSISGenerator::SupportsSetDestdir() const
636 return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED;
639 bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const
644 bool cmCPackNSISGenerator::SupportsComponentInstallation() const
649 std::string cmCPackNSISGenerator::CreateComponentDescription(
650 cmCPackComponent* component, std::ostream& macrosOut)
652 // Basic description of the component
653 std::string componentCode = "Section ";
654 if (component->IsDisabledByDefault) {
655 componentCode += "/o ";
657 componentCode += "\"";
658 if (component->IsHidden) {
659 componentCode += "-";
661 componentCode += component->DisplayName + "\" " + component->Name + "\n";
662 if (component->IsRequired) {
663 componentCode += " SectionIn RO\n";
664 } else if (!component->InstallationTypes.empty()) {
665 std::ostringstream out;
666 for (cmCPackInstallationType const* installType :
667 component->InstallationTypes) {
668 out << " " << installType->Index;
670 componentCode += " SectionIn" + out.str() + "\n";
673 const std::string componentOutputDir =
674 CustomComponentInstallDirectory(component->Name);
675 componentCode += " SetOutPath \"" + componentOutputDir + "\"\n";
677 // Create the actual installation commands
678 if (component->IsDownloaded) {
679 if (component->ArchiveFile.empty()) {
680 // Compute the name of the archive.
681 std::string packagesDir =
682 cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
683 std::ostringstream out;
684 out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-"
685 << component->Name << ".zip";
686 component->ArchiveFile = out.str();
689 // Create the directory for the upload area
690 const char* userUploadDirectory =
691 this->GetOption("CPACK_UPLOAD_DIRECTORY");
692 std::string uploadDirectory;
693 if (userUploadDirectory && *userUploadDirectory) {
694 uploadDirectory = userUploadDirectory;
697 cmStrCat(this->GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
699 if (!cmSystemTools::FileExists(uploadDirectory)) {
700 if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
701 cmCPackLogger(cmCPackLog::LOG_ERROR,
702 "Unable to create NSIS upload directory "
703 << uploadDirectory << std::endl);
708 // Remove the old archive, if one exists
709 std::string archiveFile = uploadDirectory + '/' + component->ArchiveFile;
710 cmCPackLogger(cmCPackLog::LOG_OUTPUT,
711 "- Building downloaded component archive: " << archiveFile
713 if (cmSystemTools::FileExists(archiveFile, true)) {
714 if (!cmSystemTools::RemoveFile(archiveFile)) {
715 cmCPackLogger(cmCPackLog::LOG_ERROR,
716 "Unable to remove archive file " << archiveFile
722 // Find a ZIP program
723 if (!this->IsSet("ZIP_EXECUTABLE")) {
724 this->ReadListFile("Internal/CPack/CPackZIP.cmake");
726 if (!this->IsSet("ZIP_EXECUTABLE")) {
727 cmCPackLogger(cmCPackLog::LOG_ERROR,
728 "Unable to find ZIP program" << std::endl);
733 // The directory where this component's files reside
734 std::string dirName = cmStrCat(
735 this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name, '/');
737 // Build the list of files to go into this archive, and determine the
738 // size of the installed component.
739 std::string zipListFileName = cmStrCat(
740 this->GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
741 bool needQuotesInFile = cmIsOn(this->GetOption("CPACK_ZIP_NEED_QUOTES"));
742 unsigned long totalSize = 0;
743 { // the scope is needed for cmGeneratedFileStream
744 cmGeneratedFileStream out(zipListFileName);
745 for (std::string const& file : component->Files) {
746 if (needQuotesInFile) {
750 if (needQuotesInFile) {
755 totalSize += cmSystemTools::FileLength(dirName + file);
759 // Build the archive in the upload area
760 std::string cmd = this->GetOption("CPACK_ZIP_COMMAND");
761 cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
762 cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
763 zipListFileName.c_str());
766 int res = cmSystemTools::RunSingleCommand(
767 cmd, &output, &output, &retVal, dirName.c_str(),
768 cmSystemTools::OUTPUT_NONE, cmDuration::zero());
769 if (!res || retVal) {
770 std::string tmpFile = cmStrCat(
771 this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
772 cmGeneratedFileStream ofs(tmpFile);
773 ofs << "# Run command: " << cmd << std::endl
774 << "# Output:" << std::endl
775 << output << std::endl;
776 cmCPackLogger(cmCPackLog::LOG_ERROR,
777 "Problem running zip command: " << cmd << std::endl
779 << tmpFile << " for errors"
784 // Create the NSIS code to download this file on-the-fly.
785 unsigned long totalSizeInKbytes = (totalSize + 512) / 1024;
786 if (totalSizeInKbytes == 0) {
787 totalSizeInKbytes = 1;
789 std::ostringstream out;
790 /* clang-format off */
791 out << " AddSize " << totalSizeInKbytes << "\n"
792 << " Push \"" << component->ArchiveFile << "\"\n"
793 << " Call DownloadFile\n"
794 << " ZipDLL::extractall \"$INSTDIR\\"
795 << component->ArchiveFile << "\" \"$INSTDIR\"\n"
796 << " Pop $2 ; error message\n"
797 " StrCmp $2 \"success\" +2 0\n"
798 " MessageBox MB_OK \"Failed to unzip $2\"\n"
799 " Delete $INSTDIR\\$0\n";
800 /* clang-format on */
801 componentCode += out.str();
804 " File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n";
806 componentCode += "SectionEnd\n";
808 // Macro used to remove the component
809 macrosOut << "!macro Remove_${" << component->Name << "}\n";
810 macrosOut << " IntCmp $" << component->Name << "_was_installed 0 noremove_"
811 << component->Name << "\n";
813 for (std::string const& pathIt : component->Files) {
815 std::replace(path.begin(), path.end(), '/', '\\');
816 macrosOut << " Delete \"" << componentOutputDir << "\\" << path << "\"\n";
818 for (std::string const& pathIt : component->Directories) {
820 std::replace(path.begin(), path.end(), '/', '\\');
821 macrosOut << " RMDir \"" << componentOutputDir << "\\" << path << "\"\n";
823 macrosOut << " noremove_" << component->Name << ":\n";
824 macrosOut << "!macroend\n";
826 // Macro used to select each of the components that this component
828 std::set<cmCPackComponent*> visited;
829 macrosOut << "!macro Select_" << component->Name << "_depends\n";
830 macrosOut << CreateSelectionDependenciesDescription(component, visited);
831 macrosOut << "!macroend\n";
833 // Macro used to deselect each of the components that depend on this
836 macrosOut << "!macro Deselect_required_by_" << component->Name << "\n";
837 macrosOut << CreateDeselectionDependenciesDescription(component, visited);
838 macrosOut << "!macroend\n";
839 return componentCode;
842 std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription(
843 cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
845 // Don't visit a component twice
846 if (visited.count(component)) {
847 return std::string();
849 visited.insert(component);
851 std::ostringstream out;
852 for (cmCPackComponent* depend : component->Dependencies) {
853 // Write NSIS code to select this dependency
854 out << " SectionGetFlags ${" << depend->Name << "} $0\n";
855 out << " IntOp $0 $0 | ${SF_SELECTED}\n";
856 out << " SectionSetFlags ${" << depend->Name << "} $0\n";
857 out << " IntOp $" << depend->Name << "_selected 0 + ${SF_SELECTED}\n";
859 out << CreateSelectionDependenciesDescription(depend, visited).c_str();
865 std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription(
866 cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
868 // Don't visit a component twice
869 if (visited.count(component)) {
870 return std::string();
872 visited.insert(component);
874 std::ostringstream out;
875 for (cmCPackComponent* depend : component->ReverseDependencies) {
876 // Write NSIS code to deselect this dependency
877 out << " SectionGetFlags ${" << depend->Name << "} $0\n";
878 out << " IntOp $1 ${SF_SELECTED} ~\n";
879 out << " IntOp $0 $0 & $1\n";
880 out << " SectionSetFlags ${" << depend->Name << "} $0\n";
881 out << " IntOp $" << depend->Name << "_selected 0 + 0\n";
884 out << CreateDeselectionDependenciesDescription(depend, visited).c_str();
890 std::string cmCPackNSISGenerator::CreateComponentGroupDescription(
891 cmCPackComponentGroup* group, std::ostream& macrosOut)
893 if (group->Components.empty() && group->Subgroups.empty()) {
894 // Silently skip empty groups. NSIS doesn't support them.
895 return std::string();
898 std::string code = "SectionGroup ";
899 if (group->IsExpandedByDefault) {
903 code += "\"!" + group->DisplayName + "\" " + group->Name + "\n";
905 code += "\"" + group->DisplayName + "\" " + group->Name + "\n";
908 for (cmCPackComponentGroup* g : group->Subgroups) {
909 code += this->CreateComponentGroupDescription(g, macrosOut);
912 for (cmCPackComponent* comp : group->Components) {
913 if (comp->Files.empty()) {
917 code += this->CreateComponentDescription(comp, macrosOut);
919 code += "SectionGroupEnd\n";
923 std::string cmCPackNSISGenerator::CustomComponentInstallDirectory(
924 const std::string& componentName)
926 const char* outputDir =
927 this->GetOption("CPACK_NSIS_" + componentName + "_INSTALL_DIRECTORY");
928 const std::string componentOutputDir = (outputDir ? outputDir : "$INSTDIR");
929 return componentOutputDir;
932 std::string cmCPackNSISGenerator::TranslateNewlines(std::string str)
934 cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n");