/* 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 #include #include #include #include #include #include #include "cmsys/Directory.hxx" #include "cmsys/RegularExpression.hxx" #include "cmCPackComponentGroup.h" #include "cmCPackGenerator.h" #include "cmCPackLog.h" #include "cmDuration.h" #include "cmGeneratedFileStream.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" /* 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 = cmStrCat(nsisFileName, "/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) { auto componentName = cm::string_view(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 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.erase(0, 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 += cmStrCat( "!define MUI_ICON \"", this->GetOption("CPACK_NSIS_MUI_ICON"), "\"\n"); } if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) { installerIconCode += cmStrCat("!define MUI_UNICON \"", this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n"); } this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE", installerIconCode.c_str()); } std::string installerHeaderImage; if (this->IsSet("CPACK_NSIS_MUI_HEADERIMAGE")) { installerHeaderImage = this->GetOption("CPACK_NSIS_MUI_HEADERIMAGE"); } else if (this->IsSet("CPACK_PACKAGE_ICON")) { installerHeaderImage = this->GetOption("CPACK_PACKAGE_ICON"); } if (!installerHeaderImage.empty()) { std::string installerIconCode = cmStrCat( "!define MUI_HEADERIMAGE_BITMAP \"", installerHeaderImage, "\"\n"); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE", installerIconCode.c_str()); } if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) { std::string installerBitmapCode = cmStrCat( "!define MUI_WELCOMEFINISHPAGE_BITMAP \"", this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"), "\"\n"); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE", installerBitmapCode.c_str()); } if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) { std::string installerBitmapCode = cmStrCat( "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"", this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"), "\"\n"); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE", installerBitmapCode.c_str()); } if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) { std::string installerRunCode = cmStrCat("!define MUI_FINISHPAGE_RUN \"$INSTDIR\\", this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\', this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"), "\"\n"); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE", installerRunCode.c_str()); } if (this->IsSet("CPACK_NSIS_WELCOME_TITLE")) { std::string welcomeTitleCode = cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"", this->GetOption("CPACK_NSIS_WELCOME_TITLE"), "\""); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE", welcomeTitleCode.c_str()); } if (this->IsSet("CPACK_NSIS_WELCOME_TITLE_3LINES")) { this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE", "!define MUI_WELCOMEPAGE_TITLE_3LINES"); } if (this->IsSet("CPACK_NSIS_FINISH_TITLE")) { std::string finishTitleCode = cmStrCat("!define MUI_FINISHPAGE_TITLE \"", this->GetOption("CPACK_NSIS_FINISH_TITLE"), "\""); this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE", finishTitleCode.c_str()); } if (this->IsSet("CPACK_NSIS_FINISH_TITLE_3LINES")) { this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE", "!define MUI_FINISHPAGE_TITLE_3LINES"); } if (this->IsSet("CPACK_NSIS_MANIFEST_DPI_AWARE")) { this->SetOptionIfNotSet("CPACK_NSIS_MANIFEST_DPI_AWARE_CODE", "ManifestDPIAware true"); } // 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", R"(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 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 += R"( !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 (cmIsOn(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 = cmStrCat('"', this->GetOption("CPACK_INSTALLER_PROGRAM"), "\" \"", 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 (cmIsOn(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 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 = cmStrCat(topDir ? topDir : ".", "/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 = 3.0; cmCPackLogger(cmCPackLog::LOG_DEBUG, "NSIS Version: " << nsisVersion << std::endl); if (nsisVersion < minNSISVersion) { cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack requires NSIS Version 3.0 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 cpackPackageDesktopLinksVector; if (cpackPackageDeskTopLinks) { cmCPackLogger(cmCPackLog::LOG_DEBUG, "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks << std::endl); cmExpandList(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 cpackPackageExecutablesVector = cmExpandedList(cpackPackageExecutables); if (cpackPackageExecutablesVector.size() % 2 != 0) { cmCPackLogger( cmCPackLog::LOG_ERROR, "CPACK_PACKAGE_EXECUTABLES should contain pairs of and " "." << std::endl); return 0; } std::vector::iterator it; for (it = cpackPackageExecutablesVector.begin(); it != cpackPackageExecutablesVector.end(); ++it) { std::string execName = *it; ++it; std::string linkName = *it; str << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\" << execName << ".exe\"" << std::endl; deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName << ".lnk\"" << std::endl; // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on // if so add a desktop link if (cm::contains(cpackPackageDesktopLinksVector, execName)) { str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; str << " CreateShortCut \"$DESKTOP\\" << linkName << R"(.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 cpackMenuLinksVector = cmExpandedList(cpackMenuLinks); if (cpackMenuLinksVector.size() % 2 != 0) { cmCPackLogger( cmCPackLog::LOG_ERROR, "CPACK_NSIS_MENU_LINKS should contain pairs of and " "." << std::endl); return; } static cmsys::RegularExpression urlRegex( "^(mailto:|(ftps?|https?|news)://).*$"); std::vector::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 << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl; deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName << ".lnk\"" << std::endl; } else { str << R"( WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName << R"(.url" "InternetShortcut" "URL" ")" << sourceName << "\"" << std::endl; deleteStr << R"( 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 = cmStrCat("CPACK_CREATE_DESKTOP_LINK_", linkName); if (this->IsSet(desktop)) { str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n"; str << " CreateShortCut \"$DESKTOP\\" << linkName << R"(.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& 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 += cmStrCat(" SetOutPath \"", componentOutputDir, "\"\n"); // Create the actual installation commands if (component->IsDownloaded) { if (component->ArchiveFile.empty()) { // Compute the name of the archive. std::string packagesDir = cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".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 (cmNonempty(userUploadDirectory)) { uploadDirectory = userUploadDirectory; } else { uploadDirectory = cmStrCat(this->GetOption("CPACK_PACKAGE_DIRECTORY"), "/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 = cmStrCat( this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name, '/'); // Build the list of files to go into this archive, and determine the // size of the installed component. std::string zipListFileName = cmStrCat( this->GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist"); bool needQuotesInFile = cmIsOn(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, "", archiveFile.c_str()); cmsys::SystemTools::ReplaceString(cmd, "", 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 = cmStrCat( this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/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 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& 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& 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( cm::string_view componentName) { const char* outputDir = this->GetOption( cmStrCat("CPACK_NSIS_", componentName, "_INSTALL_DIRECTORY")); return outputDir ? outputDir : "$INSTDIR"; } std::string cmCPackNSISGenerator::TranslateNewlines(std::string str) { cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n"); return str; }