diff options
Diffstat (limited to 'Source/CPack/cmCPackNSISGenerator.cxx')
-rw-r--r-- | Source/CPack/cmCPackNSISGenerator.cxx | 920 |
1 files changed, 920 insertions, 0 deletions
diff --git a/Source/CPack/cmCPackNSISGenerator.cxx b/Source/CPack/cmCPackNSISGenerator.cxx new file mode 100644 index 0000000..6afd7d5 --- /dev/null +++ b/Source/CPack/cmCPackNSISGenerator.cxx @@ -0,0 +1,920 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCPackNSISGenerator.h" + +#include "cmCPackComponentGroup.h" +#include "cmCPackGenerator.h" +#include "cmCPackLog.h" +#include "cmDuration.h" +#include "cmGeneratedFileStream.h" +#include "cmSystemTools.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/RegularExpression.hxx" +#include <algorithm> +#include <map> +#include <sstream> +#include <stdlib.h> +#include <string.h> +#include <utility> + +/* NSIS uses different command line syntax on Windows and others */ +#ifdef _WIN32 +# define NSIS_OPT "/" +#else +# define NSIS_OPT "-" +#endif + +cmCPackNSISGenerator::cmCPackNSISGenerator(bool nsis64) +{ + Nsis64 = nsis64; +} + +cmCPackNSISGenerator::~cmCPackNSISGenerator() = default; + +int cmCPackNSISGenerator::PackageFiles() +{ + // TODO: Fix nsis to force out file name + + std::string nsisInFileName = this->FindTemplate("NSIS.template.in"); + if (nsisInFileName.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "CPack error: Could not find NSIS installer template file." + << std::endl); + return false; + } + std::string nsisInInstallOptions = + this->FindTemplate("NSIS.InstallOptions.ini.in"); + if (nsisInInstallOptions.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "CPack error: Could not find NSIS installer options file." + << std::endl); + return false; + } + + std::string nsisFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + std::string tmpFile = nsisFileName; + tmpFile += "/NSISOutput.log"; + std::string nsisInstallOptions = nsisFileName + "/NSIS.InstallOptions.ini"; + nsisFileName += "/project.nsi"; + std::ostringstream str; + for (std::string const& file : files) { + std::string outputDir = "$INSTDIR"; + std::string fileN = cmSystemTools::RelativePath(toplevel, file); + if (!this->Components.empty()) { + const std::string::size_type pos = fileN.find('/'); + + // Use the custom component install directory if we have one + if (pos != std::string::npos) { + const std::string componentName = fileN.substr(0, pos); + outputDir = CustomComponentInstallDirectory(componentName); + } else { + outputDir = CustomComponentInstallDirectory(fileN); + } + + // Strip off the component part of the path. + fileN = fileN.substr(pos + 1); + } + std::replace(fileN.begin(), fileN.end(), '/', '\\'); + + str << " Delete \"" << outputDir << "\\" << fileN << "\"" << std::endl; + } + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "Uninstall Files: " << str.str() << std::endl); + this->SetOptionIfNotSet("CPACK_NSIS_DELETE_FILES", str.str().c_str()); + std::vector<std::string> dirs; + this->GetListOfSubdirectories(toplevel.c_str(), dirs); + std::ostringstream dstr; + for (std::string const& dir : dirs) { + std::string componentName; + std::string fileN = cmSystemTools::RelativePath(toplevel, dir); + if (fileN.empty()) { + continue; + } + if (!Components.empty()) { + // If this is a component installation, strip off the component + // part of the path. + std::string::size_type slash = fileN.find('/'); + if (slash != std::string::npos) { + // If this is a component installation, determine which component it + // is. + componentName = fileN.substr(0, slash); + + // Strip off the component part of the path. + fileN = fileN.substr(slash + 1); + } + } + std::replace(fileN.begin(), fileN.end(), '/', '\\'); + + const std::string componentOutputDir = + CustomComponentInstallDirectory(componentName); + + dstr << " RMDir \"" << componentOutputDir << "\\" << fileN << "\"" + << std::endl; + if (!componentName.empty()) { + this->Components[componentName].Directories.push_back(std::move(fileN)); + } + } + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "Uninstall Dirs: " << dstr.str() << std::endl); + this->SetOptionIfNotSet("CPACK_NSIS_DELETE_DIRECTORIES", dstr.str().c_str()); + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "Configure file: " << nsisInFileName << " to " << nsisFileName + << std::endl); + if (this->IsSet("CPACK_NSIS_MUI_ICON") || + this->IsSet("CPACK_NSIS_MUI_UNIICON")) { + std::string installerIconCode; + if (this->IsSet("CPACK_NSIS_MUI_ICON")) { + installerIconCode += "!define MUI_ICON \""; + installerIconCode += this->GetOption("CPACK_NSIS_MUI_ICON"); + installerIconCode += "\"\n"; + } + if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) { + installerIconCode += "!define MUI_UNICON \""; + installerIconCode += this->GetOption("CPACK_NSIS_MUI_UNIICON"); + installerIconCode += "\"\n"; + } + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE", + installerIconCode.c_str()); + } + if (this->IsSet("CPACK_PACKAGE_ICON")) { + std::string installerIconCode = "!define MUI_HEADERIMAGE_BITMAP \""; + installerIconCode += this->GetOption("CPACK_PACKAGE_ICON"); + installerIconCode += "\"\n"; + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE", + installerIconCode.c_str()); + } + + if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) { + std::string installerBitmapCode = + "!define MUI_WELCOMEFINISHPAGE_BITMAP \""; + installerBitmapCode += + this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"); + installerBitmapCode += "\"\n"; + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE", + installerBitmapCode.c_str()); + } + + if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) { + std::string installerBitmapCode = + "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \""; + installerBitmapCode += + this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"); + installerBitmapCode += "\"\n"; + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE", + installerBitmapCode.c_str()); + } + + if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) { + std::string installerRunCode = "!define MUI_FINISHPAGE_RUN \"$INSTDIR\\"; + installerRunCode += this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"); + installerRunCode += "\\"; + installerRunCode += this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"); + installerRunCode += "\"\n"; + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE", + installerRunCode.c_str()); + } + + // Setup all of the component sections + if (this->Components.empty()) { + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES", ""); + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", ""); + this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS", ""); + this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL", + "File /r \"${INST_DIR}\\*.*\""); + this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS", ""); + this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST", ""); + this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS", ""); + } else { + std::string componentCode; + std::string sectionList; + std::string selectedVarsList; + std::string componentDescriptions; + std::string groupDescriptions; + std::string installTypesCode; + std::string defines; + std::ostringstream macrosOut; + bool anyDownloadedComponents = false; + + // Create installation types. The order is significant, so we first fill + // in a vector based on the indices, and print them in that order. + std::vector<cmCPackInstallationType*> installTypes( + this->InstallationTypes.size()); + for (auto& installType : this->InstallationTypes) { + installTypes[installType.second.Index - 1] = &installType.second; + } + for (cmCPackInstallationType* installType : installTypes) { + installTypesCode += "InstType \""; + installTypesCode += installType->DisplayName; + installTypesCode += "\"\n"; + } + + // Create installation groups first + for (auto& group : this->ComponentGroups) { + if (group.second.ParentGroup == nullptr) { + componentCode += + this->CreateComponentGroupDescription(&group.second, macrosOut); + } + + // Add the group description, if any. + if (!group.second.Description.empty()) { + groupDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" + + group.first + "} \"" + + cmCPackNSISGenerator::TranslateNewlines(group.second.Description) + + "\"\n"; + } + } + + // Create the remaining components, which aren't associated with groups. + for (auto& comp : this->Components) { + if (comp.second.Files.empty()) { + // NSIS cannot cope with components that have no files. + continue; + } + + anyDownloadedComponents = + anyDownloadedComponents || comp.second.IsDownloaded; + + if (!comp.second.Group) { + componentCode += + this->CreateComponentDescription(&comp.second, macrosOut); + } + + // Add this component to the various section lists. + sectionList += " !insertmacro \"${MacroName}\" \""; + sectionList += comp.first; + sectionList += "\"\n"; + selectedVarsList += "Var " + comp.first + "_selected\n"; + selectedVarsList += "Var " + comp.first + "_was_installed\n"; + + // Add the component description, if any. + if (!comp.second.Description.empty()) { + componentDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" + + comp.first + "} \"" + + cmCPackNSISGenerator::TranslateNewlines(comp.second.Description) + + "\"\n"; + } + } + + componentCode += macrosOut.str(); + + if (componentDescriptions.empty() && groupDescriptions.empty()) { + // Turn off the "Description" box + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", + "!define MUI_COMPONENTSPAGE_NODESC"); + } else { + componentDescriptions = "!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN\n" + + componentDescriptions + groupDescriptions + + "!insertmacro MUI_FUNCTION_DESCRIPTION_END\n"; + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", + componentDescriptions.c_str()); + } + + if (anyDownloadedComponents) { + defines += "!define CPACK_USES_DOWNLOAD\n"; + if (cmSystemTools::IsOn(this->GetOption("CPACK_ADD_REMOVE"))) { + defines += "!define CPACK_NSIS_ADD_REMOVE\n"; + } + } + + this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES", + installTypesCode.c_str()); + this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS", + "!insertmacro MUI_PAGE_COMPONENTS"); + this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL", ""); + this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS", + componentCode.c_str()); + this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST", + sectionList.c_str()); + this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS", + selectedVarsList.c_str()); + this->SetOption("CPACK_NSIS_DEFINES", defines.c_str()); + } + + this->ConfigureFile(nsisInInstallOptions, nsisInstallOptions); + this->ConfigureFile(nsisInFileName, nsisFileName); + std::string nsisCmd = "\""; + nsisCmd += this->GetOption("CPACK_INSTALLER_PROGRAM"); + nsisCmd += "\" \"" + nsisFileName + "\""; + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << nsisCmd << std::endl); + std::string output; + int retVal = 1; + bool res = cmSystemTools::RunSingleCommand( + nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose, + cmDuration::zero()); + if (!res || retVal) { + cmGeneratedFileStream ofs(tmpFile); + ofs << "# Run command: " << nsisCmd << std::endl + << "# Output:" << std::endl + << output << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem running NSIS command: " << nsisCmd << std::endl + << "Please check " + << tmpFile << " for errors" + << std::endl); + return 0; + } + return 1; +} + +int cmCPackNSISGenerator::InitializeInternal() +{ + if (cmSystemTools::IsOn( + this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) { + cmCPackLogger( + cmCPackLog::LOG_WARNING, + "NSIS Generator cannot work with CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. " + "This option will be reset to 0 (for this generator only)." + << std::endl); + this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr); + } + + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "cmCPackNSISGenerator::Initialize()" << std::endl); + std::vector<std::string> path; + std::string nsisPath; + bool gotRegValue = false; + +#ifdef _WIN32 + if (Nsis64) { + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath, + cmsys::SystemTools::KeyWOW64_64)) { + gotRegValue = true; + } + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath, + cmsys::SystemTools::KeyWOW64_64)) { + gotRegValue = true; + } + } + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath, + cmsys::SystemTools::KeyWOW64_32)) { + gotRegValue = true; + } + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) { + gotRegValue = true; + } + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath, + cmsys::SystemTools::KeyWOW64_32)) { + gotRegValue = true; + } + if (!gotRegValue && + cmsys::SystemTools::ReadRegistryValue( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) { + gotRegValue = true; + } + + if (gotRegValue) { + path.push_back(nsisPath); + } +#endif + + nsisPath = cmSystemTools::FindProgram("makensis", path, false); + + if (nsisPath.empty()) { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "Cannot find NSIS compiler makensis: likely it is not installed, " + "or not in your PATH" + << std::endl); + + if (!gotRegValue) { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "Could not read NSIS registry value. This is usually caused by " + "NSIS not being installed. Please install NSIS from " + "http://nsis.sourceforge.net" + << std::endl); + } + + return 0; + } + + std::string nsisCmd = "\"" + nsisPath + "\" " NSIS_OPT "VERSION"; + cmCPackLogger(cmCPackLog::LOG_VERBOSE, + "Test NSIS version: " << nsisCmd << std::endl); + std::string output; + int retVal = 1; + bool resS = cmSystemTools::RunSingleCommand( + nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose, + cmDuration::zero()); + cmsys::RegularExpression versionRex("v([0-9]+.[0-9]+)"); + cmsys::RegularExpression versionRexCVS("v(.*)\\.cvs"); + if (!resS || retVal || + (!versionRex.find(output) && !versionRexCVS.find(output))) { + const char* topDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + std::string tmpFile = topDir ? topDir : "."; + tmpFile += "/NSISOutput.log"; + cmGeneratedFileStream ofs(tmpFile); + ofs << "# Run command: " << nsisCmd << std::endl + << "# Output:" << std::endl + << output << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem checking NSIS version with command: " + << nsisCmd << std::endl + << "Please check " << tmpFile << " for errors" + << std::endl); + return 0; + } + if (versionRex.find(output)) { + double nsisVersion = atof(versionRex.match(1).c_str()); + double minNSISVersion = 2.09; + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "NSIS Version: " << nsisVersion << std::endl); + if (nsisVersion < minNSISVersion) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "CPack requires NSIS Version 2.09 or greater. " + "NSIS found on the system was: " + << nsisVersion << std::endl); + return 0; + } + } + if (versionRexCVS.find(output)) { + // No version check for NSIS cvs build + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "NSIS Version: CVS " << versionRexCVS.match(1) << std::endl); + } + this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", nsisPath.c_str()); + this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLES_DIRECTORY", "bin"); + const char* cpackPackageExecutables = + this->GetOption("CPACK_PACKAGE_EXECUTABLES"); + const char* cpackPackageDeskTopLinks = + this->GetOption("CPACK_CREATE_DESKTOP_LINKS"); + const char* cpackNsisExecutablesDirectory = + this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"); + std::vector<std::string> cpackPackageDesktopLinksVector; + if (cpackPackageDeskTopLinks) { + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks + << std::endl); + + cmSystemTools::ExpandListArgument(cpackPackageDeskTopLinks, + cpackPackageDesktopLinksVector); + for (std::string const& cpdl : cpackPackageDesktopLinksVector) { + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl); + } + } else { + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "CPACK_CREATE_DESKTOP_LINKS: " + << "not set" << std::endl); + } + + std::ostringstream str; + std::ostringstream deleteStr; + + if (cpackPackageExecutables) { + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "The cpackPackageExecutables: " << cpackPackageExecutables + << "." << std::endl); + std::vector<std::string> cpackPackageExecutablesVector; + cmSystemTools::ExpandListArgument(cpackPackageExecutables, + cpackPackageExecutablesVector); + if (cpackPackageExecutablesVector.size() % 2 != 0) { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and " + "<icon name>." + << std::endl); + return 0; + } + std::vector<std::string>::iterator it; + for (it = cpackPackageExecutablesVector.begin(); + it != cpackPackageExecutablesVector.end(); ++it) { + std::string execName = *it; + ++it; + std::string linkName = *it; + str << " CreateShortCut \"$SMPROGRAMS\\$STARTMENU_FOLDER\\" << linkName + << ".lnk\" \"$INSTDIR\\" << cpackNsisExecutablesDirectory << "\\" + << execName << ".exe\"" << std::endl; + deleteStr << " Delete \"$SMPROGRAMS\\$MUI_TEMP\\" << linkName + << ".lnk\"" << std::endl; + // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on + // if so add a desktop link + if (!cpackPackageDesktopLinksVector.empty() && + std::find(cpackPackageDesktopLinksVector.begin(), + cpackPackageDesktopLinksVector.end(), + execName) != cpackPackageDesktopLinksVector.end()) { + str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; + str << " CreateShortCut \"$DESKTOP\\" << linkName + << ".lnk\" \"$INSTDIR\\" << cpackNsisExecutablesDirectory << "\\" + << execName << ".exe\"" << std::endl; + deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; + deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\"" + << std::endl; + } + } + } + + this->CreateMenuLinks(str, deleteStr); + this->SetOptionIfNotSet("CPACK_NSIS_CREATE_ICONS", str.str().c_str()); + this->SetOptionIfNotSet("CPACK_NSIS_DELETE_ICONS", deleteStr.str().c_str()); + + this->SetOptionIfNotSet("CPACK_NSIS_COMPRESSOR", "lzma"); + + return this->Superclass::InitializeInternal(); +} + +void cmCPackNSISGenerator::CreateMenuLinks(std::ostream& str, + std::ostream& deleteStr) +{ + const char* cpackMenuLinks = this->GetOption("CPACK_NSIS_MENU_LINKS"); + if (!cpackMenuLinks) { + return; + } + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "The cpackMenuLinks: " << cpackMenuLinks << "." << std::endl); + std::vector<std::string> cpackMenuLinksVector; + cmSystemTools::ExpandListArgument(cpackMenuLinks, cpackMenuLinksVector); + if (cpackMenuLinksVector.size() % 2 != 0) { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and " + "<shortcut label>." + << std::endl); + return; + } + + static cmsys::RegularExpression urlRegex( + "^(mailto:|(ftps?|https?|news)://).*$"); + + std::vector<std::string>::iterator it; + for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end(); + ++it) { + std::string sourceName = *it; + const bool url = urlRegex.find(sourceName); + + // Convert / to \ in filenames, but not in urls: + // + if (!url) { + std::replace(sourceName.begin(), sourceName.end(), '/', '\\'); + } + + ++it; + std::string linkName = *it; + if (!url) { + str << " CreateShortCut \"$SMPROGRAMS\\$STARTMENU_FOLDER\\" << linkName + << ".lnk\" \"$INSTDIR\\" << sourceName << "\"" << std::endl; + deleteStr << " Delete \"$SMPROGRAMS\\$MUI_TEMP\\" << linkName + << ".lnk\"" << std::endl; + } else { + str << " WriteINIStr \"$SMPROGRAMS\\$STARTMENU_FOLDER\\" << linkName + << ".url\" \"InternetShortcut\" \"URL\" \"" << sourceName << "\"" + << std::endl; + deleteStr << " Delete \"$SMPROGRAMS\\$MUI_TEMP\\" << linkName + << ".url\"" << std::endl; + } + // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on + // if so add a desktop link + std::string desktop = "CPACK_CREATE_DESKTOP_LINK_"; + desktop += linkName; + if (this->IsSet(desktop)) { + str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; + str << " CreateShortCut \"$DESKTOP\\" << linkName + << ".lnk\" \"$INSTDIR\\" << sourceName << "\"" << std::endl; + deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; + deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\"" + << std::endl; + } + } +} + +bool cmCPackNSISGenerator::GetListOfSubdirectories( + const char* topdir, std::vector<std::string>& dirs) +{ + cmsys::Directory dir; + dir.Load(topdir); + for (unsigned long i = 0; i < dir.GetNumberOfFiles(); ++i) { + const char* fileName = dir.GetFile(i); + if (strcmp(fileName, ".") != 0 && strcmp(fileName, "..") != 0) { + std::string const fullPath = + std::string(topdir).append("/").append(fileName); + if (cmsys::SystemTools::FileIsDirectory(fullPath) && + !cmsys::SystemTools::FileIsSymlink(fullPath)) { + if (!this->GetListOfSubdirectories(fullPath.c_str(), dirs)) { + return false; + } + } + } + } + dirs.emplace_back(topdir); + return true; +} + +enum cmCPackGenerator::CPackSetDestdirSupport +cmCPackNSISGenerator::SupportsSetDestdir() const +{ + return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED; +} + +bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const +{ + return false; +} + +bool cmCPackNSISGenerator::SupportsComponentInstallation() const +{ + return true; +} + +std::string cmCPackNSISGenerator::CreateComponentDescription( + cmCPackComponent* component, std::ostream& macrosOut) +{ + // Basic description of the component + std::string componentCode = "Section "; + if (component->IsDisabledByDefault) { + componentCode += "/o "; + } + componentCode += "\""; + if (component->IsHidden) { + componentCode += "-"; + } + componentCode += component->DisplayName + "\" " + component->Name + "\n"; + if (component->IsRequired) { + componentCode += " SectionIn RO\n"; + } else if (!component->InstallationTypes.empty()) { + std::ostringstream out; + for (cmCPackInstallationType const* installType : + component->InstallationTypes) { + out << " " << installType->Index; + } + componentCode += " SectionIn" + out.str() + "\n"; + } + + const std::string componentOutputDir = + CustomComponentInstallDirectory(component->Name); + componentCode += " SetOutPath \"" + componentOutputDir + "\"\n"; + + // Create the actual installation commands + if (component->IsDownloaded) { + if (component->ArchiveFile.empty()) { + // Compute the name of the archive. + std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + packagesDir += ".dummy"; + std::ostringstream out; + out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-" + << component->Name << ".zip"; + component->ArchiveFile = out.str(); + } + + // Create the directory for the upload area + const char* userUploadDirectory = + this->GetOption("CPACK_UPLOAD_DIRECTORY"); + std::string uploadDirectory; + if (userUploadDirectory && *userUploadDirectory) { + uploadDirectory = userUploadDirectory; + } else { + uploadDirectory = this->GetOption("CPACK_PACKAGE_DIRECTORY"); + uploadDirectory += "/CPackUploads"; + } + if (!cmSystemTools::FileExists(uploadDirectory)) { + if (!cmSystemTools::MakeDirectory(uploadDirectory)) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Unable to create NSIS upload directory " + << uploadDirectory << std::endl); + return ""; + } + } + + // Remove the old archive, if one exists + std::string archiveFile = uploadDirectory + '/' + component->ArchiveFile; + cmCPackLogger(cmCPackLog::LOG_OUTPUT, + "- Building downloaded component archive: " << archiveFile + << std::endl); + if (cmSystemTools::FileExists(archiveFile, true)) { + if (!cmSystemTools::RemoveFile(archiveFile)) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Unable to remove archive file " << archiveFile + << std::endl); + return ""; + } + } + + // Find a ZIP program + if (!this->IsSet("ZIP_EXECUTABLE")) { + this->ReadListFile("Internal/CPack/CPackZIP.cmake"); + + if (!this->IsSet("ZIP_EXECUTABLE")) { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Unable to find ZIP program" << std::endl); + return ""; + } + } + + // The directory where this component's files reside + std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + dirName += '/'; + dirName += component->Name; + dirName += '/'; + + // Build the list of files to go into this archive, and determine the + // size of the installed component. + std::string zipListFileName = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + zipListFileName += "/winZip.filelist"; + bool needQuotesInFile = + cmSystemTools::IsOn(this->GetOption("CPACK_ZIP_NEED_QUOTES")); + unsigned long totalSize = 0; + { // the scope is needed for cmGeneratedFileStream + cmGeneratedFileStream out(zipListFileName); + for (std::string const& file : component->Files) { + if (needQuotesInFile) { + out << "\""; + } + out << file; + if (needQuotesInFile) { + out << "\""; + } + out << std::endl; + + totalSize += cmSystemTools::FileLength(dirName + file); + } + } + + // Build the archive in the upload area + std::string cmd = this->GetOption("CPACK_ZIP_COMMAND"); + cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str()); + cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>", + zipListFileName.c_str()); + std::string output; + int retVal = -1; + int res = cmSystemTools::RunSingleCommand( + cmd, &output, &output, &retVal, dirName.c_str(), + cmSystemTools::OUTPUT_NONE, cmDuration::zero()); + if (!res || retVal) { + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + tmpFile += "/CompressZip.log"; + cmGeneratedFileStream ofs(tmpFile); + ofs << "# Run command: " << cmd << std::endl + << "# Output:" << std::endl + << output << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem running zip command: " << cmd << std::endl + << "Please check " + << tmpFile << " for errors" + << std::endl); + return ""; + } + + // Create the NSIS code to download this file on-the-fly. + unsigned long totalSizeInKbytes = (totalSize + 512) / 1024; + if (totalSizeInKbytes == 0) { + totalSizeInKbytes = 1; + } + std::ostringstream out; + /* clang-format off */ + out << " AddSize " << totalSizeInKbytes << "\n" + << " Push \"" << component->ArchiveFile << "\"\n" + << " Call DownloadFile\n" + << " ZipDLL::extractall \"$INSTDIR\\" + << component->ArchiveFile << "\" \"$INSTDIR\"\n" + << " Pop $2 ; error message\n" + " StrCmp $2 \"success\" +2 0\n" + " MessageBox MB_OK \"Failed to unzip $2\"\n" + " Delete $INSTDIR\\$0\n"; + /* clang-format on */ + componentCode += out.str(); + } else { + componentCode += + " File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n"; + } + componentCode += "SectionEnd\n"; + + // Macro used to remove the component + macrosOut << "!macro Remove_${" << component->Name << "}\n"; + macrosOut << " IntCmp $" << component->Name << "_was_installed 0 noremove_" + << component->Name << "\n"; + std::string path; + for (std::string const& pathIt : component->Files) { + path = pathIt; + std::replace(path.begin(), path.end(), '/', '\\'); + macrosOut << " Delete \"" << componentOutputDir << "\\" << path << "\"\n"; + } + for (std::string const& pathIt : component->Directories) { + path = pathIt; + std::replace(path.begin(), path.end(), '/', '\\'); + macrosOut << " RMDir \"" << componentOutputDir << "\\" << path << "\"\n"; + } + macrosOut << " noremove_" << component->Name << ":\n"; + macrosOut << "!macroend\n"; + + // Macro used to select each of the components that this component + // depends on. + std::set<cmCPackComponent*> visited; + macrosOut << "!macro Select_" << component->Name << "_depends\n"; + macrosOut << CreateSelectionDependenciesDescription(component, visited); + macrosOut << "!macroend\n"; + + // Macro used to deselect each of the components that depend on this + // component. + visited.clear(); + macrosOut << "!macro Deselect_required_by_" << component->Name << "\n"; + macrosOut << CreateDeselectionDependenciesDescription(component, visited); + macrosOut << "!macroend\n"; + return componentCode; +} + +std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription( + cmCPackComponent* component, std::set<cmCPackComponent*>& visited) +{ + // Don't visit a component twice + if (visited.count(component)) { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + for (cmCPackComponent* depend : component->Dependencies) { + // Write NSIS code to select this dependency + out << " SectionGetFlags ${" << depend->Name << "} $0\n"; + out << " IntOp $0 $0 | ${SF_SELECTED}\n"; + out << " SectionSetFlags ${" << depend->Name << "} $0\n"; + out << " IntOp $" << depend->Name << "_selected 0 + ${SF_SELECTED}\n"; + // Recurse + out << CreateSelectionDependenciesDescription(depend, visited).c_str(); + } + + return out.str(); +} + +std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription( + cmCPackComponent* component, std::set<cmCPackComponent*>& visited) +{ + // Don't visit a component twice + if (visited.count(component)) { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + for (cmCPackComponent* depend : component->ReverseDependencies) { + // Write NSIS code to deselect this dependency + out << " SectionGetFlags ${" << depend->Name << "} $0\n"; + out << " IntOp $1 ${SF_SELECTED} ~\n"; + out << " IntOp $0 $0 & $1\n"; + out << " SectionSetFlags ${" << depend->Name << "} $0\n"; + out << " IntOp $" << depend->Name << "_selected 0 + 0\n"; + + // Recurse + out << CreateDeselectionDependenciesDescription(depend, visited).c_str(); + } + + return out.str(); +} + +std::string cmCPackNSISGenerator::CreateComponentGroupDescription( + cmCPackComponentGroup* group, std::ostream& macrosOut) +{ + if (group->Components.empty() && group->Subgroups.empty()) { + // Silently skip empty groups. NSIS doesn't support them. + return std::string(); + } + + std::string code = "SectionGroup "; + if (group->IsExpandedByDefault) { + code += "/e "; + } + if (group->IsBold) { + code += "\"!" + group->DisplayName + "\" " + group->Name + "\n"; + } else { + code += "\"" + group->DisplayName + "\" " + group->Name + "\n"; + } + + for (cmCPackComponentGroup* g : group->Subgroups) { + code += this->CreateComponentGroupDescription(g, macrosOut); + } + + for (cmCPackComponent* comp : group->Components) { + if (comp->Files.empty()) { + continue; + } + + code += this->CreateComponentDescription(comp, macrosOut); + } + code += "SectionGroupEnd\n"; + return code; +} + +std::string cmCPackNSISGenerator::CustomComponentInstallDirectory( + const std::string& componentName) +{ + const char* outputDir = + this->GetOption("CPACK_NSIS_" + componentName + "_INSTALL_DIRECTORY"); + const std::string componentOutputDir = (outputDir ? outputDir : "$INSTDIR"); + return componentOutputDir; +} + +std::string cmCPackNSISGenerator::TranslateNewlines(std::string str) +{ + cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n"); + return str; +} |