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"
23 #include "cmStringAlgorithms.h"
24 #include "cmSystemTools.h"
27 /* NSIS uses different command line syntax on Windows and others */
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."
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."
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
);
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
);
99 if (!this->Components
.empty()) {
100 // If this is a component installation, strip off the component
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
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
<< "\""
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
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")) {
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",
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",
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",
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",
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",
223 if (possiblePositions
.find(wantedPosition
) ==
224 possiblePositions
.end()) {
225 cmCPackLogger(cmCPackLog::LOG_ERROR
,
226 "Unsupported branding text trim position "
227 << wantedPosition
<< std::endl
);
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", "");
282 std::string componentCode
;
283 std::string sectionList
;
284 std::string selectedVarsList
;
285 std::string componentDescriptions
;
286 std::string groupDescriptions
;
287 std::string installTypesCode
;
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) {
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
) +
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.
328 anyDownloadedComponents
=
329 anyDownloadedComponents
|| comp
.second
.IsDownloaded
;
331 if (!comp
.second
.Group
) {
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) +
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
");
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
",
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);
395 bool res = cmSystemTools::RunSingleCommand(
396 nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
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
406 << tmpFile << " for errors"
413 int cmCPackNSISGenerator::InitializeInternal()
415 if (this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY").IsOn()) {
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)."
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;
433 cmsys::SystemTools::ReadRegistryValue(
434 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
435 cmsys::SystemTools::KeyWOW64_64)) {
439 cmsys::SystemTools::ReadRegistryValue(
440 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
441 cmsys::SystemTools::KeyWOW64_64)) {
446 cmsys::SystemTools::ReadRegistryValue(
447 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
448 cmsys::SystemTools::KeyWOW64_32)) {
452 cmsys::SystemTools::ReadRegistryValue(
453 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) {
457 cmsys::SystemTools::ReadRegistryValue(
458 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
459 cmsys::SystemTools::KeyWOW64_32)) {
463 cmsys::SystemTools::ReadRegistryValue(
464 "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) {
469 path.push_back(nsisPath);
473 this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLE", "makensis");
474 nsisPath = cmSystemTools::FindProgram(
475 *this->GetOption("CPACK_NSIS_EXECUTABLE"), path, false);
477 if (nsisPath.empty()) {
479 cmCPackLog::LOG_ERROR,
480 "Cannot find NSIS compiler makensis: likely it is not installed, "
481 "or not in your PATH"
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"
496 std::string nsisCmd
= "\"" + nsisPath
+ "\" " NSIS_OPT
"VERSION";
497 cmCPackLogger(cmCPackLog::LOG_VERBOSE
,
498 "Test NSIS version: " << nsisCmd
<< std::endl
);
501 bool resS
= cmSystemTools::RunSingleCommand(
502 nsisCmd
, &output
, &output
, &retVal
, nullptr, this->GeneratorVerbose
,
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"
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
);
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
553 cpackPackageDesktopLinksList
.assign(cpackPackageDeskTopLinks
);
554 for (std::string
const& cpdl
: cpackPackageDesktopLinksList
) {
555 cmCPackLogger(cmCPackLog::LOG_DEBUG
,
556 "CPACK_CREATE_DESKTOP_LINKS: " << cpdl
<< std::endl
);
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) {
574 cmCPackLog::LOG_ERROR
,
575 "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
581 for (it
= cpackPackageExecutablesList
.begin();
582 it
!= cpackPackageExecutablesList
.end(); ++it
) {
583 std::string execName
= *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\""
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
) {
621 cmCPackLogger(cmCPackLog::LOG_DEBUG
,
622 "The cpackMenuLinks: " << cpackMenuLinks
<< "." << std::endl
);
623 cmList cpackMenuLinksList
{ cpackMenuLinks
};
624 if (cpackMenuLinksList
.size() % 2 != 0) {
626 cmCPackLog::LOG_ERROR
,
627 "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
633 static cmsys::RegularExpression
urlRegex(
634 "^(mailto:|(ftps?|https?|news)://).*$");
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:
644 std::replace(sourceName
.begin(), sourceName
.end(), '/', '\\');
648 std::string linkName
= *it
;
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
;
655 str
<< R
"( WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\
)" << linkName
656 << R"(.url
" "InternetShortcut
" "URL
" ")" << sourceName << "\""
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\""
675 bool cmCPackNSISGenerator::GetListOfSubdirectories(
676 const char* topdir
, std::vector
<std::string
>& dirs
)
678 cmsys::Directory dir
;
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
)) {
693 dirs
.emplace_back(topdir
);
697 enum cmCPackGenerator::CPackSetDestdirSupport
698 cmCPackNSISGenerator::SupportsSetDestdir() const
700 return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED
;
703 bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const
708 bool cmCPackNSISGenerator::SupportsComponentInstallation() const
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
;
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
);
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
776 if (cmSystemTools::FileExists(archiveFile
, true)) {
777 if (!cmSystemTools::RemoveFile(archiveFile
)) {
778 cmCPackLogger(cmCPackLog::LOG_ERROR
,
779 "Unable to remove archive file " << archiveFile
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
);
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
) {
813 if (needQuotesInFile
) {
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());
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
842 << tmpFile
<< " for errors"
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();
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";
876 for (std::string
const& pathIt
: component
->Files
) {
878 std::replace(path
.begin(), path
.end(), '/', '\\');
879 macrosOut
<< " Delete \"" << componentOutputDir
<< "\\" << path
<< "\"\n";
881 for (std::string
const& pathIt
: component
->Directories
) {
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
891 std::set
<cmCPackComponent
*> visited
;
892 macrosOut
<< "!macro Select_" << component
->Name
<< "_depends\n";
893 macrosOut
<< this->CreateSelectionDependenciesDescription(component
,
895 macrosOut
<< "!macroend\n";
897 // Macro used to deselect each of the components that depend on this
900 macrosOut
<< "!macro Deselect_required_by_" << component
->Name
<< "\n";
901 macrosOut
<< this->CreateDeselectionDependenciesDescription(component
,
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
)) {
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";
925 << this->CreateSelectionDependenciesDescription(depend
, visited
).c_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
)) {
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";
950 out
<< this->CreateDeselectionDependenciesDescription(depend
, visited
)
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.
965 std::string code
= "SectionGroup ";
966 if (group
->IsExpandedByDefault
) {
970 code
+= "\"!" + group
->DisplayName
+ "\" " + group
->Name
+ "\n";
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()) {
984 code
+= this->CreateComponentDescription(comp
, macrosOut
);
986 code
+= "SectionGroupEnd\n";
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");