| /* 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() |
| { |
| } |
| |
| 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 + "} \"" + |
| this->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 + "} \"" + |
| this->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.c_str(), |
| nsisInstallOptions.c_str()); |
| this->ConfigureFile(nsisInFileName.c_str(), nsisFileName.c_str()); |
| 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.c_str(), &output, &output, &retVal, nullptr, |
| this->GeneratorVerbose, cmDuration::zero()); |
| if (!res || retVal) { |
| cmGeneratedFileStream ofs(tmpFile.c_str()); |
| 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.c_str(), &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.c_str()); |
| 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.push_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("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.c_str()); |
| 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.c_str(), &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.c_str()); |
| 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; |
| } |