diff options
-rw-r--r-- | Modules/CPack.Info.plist.in | 2 | ||||
-rw-r--r-- | Modules/CPack.distribution.dist.in | 6 | ||||
-rw-r--r-- | Modules/NSIS.template.in | 232 | ||||
-rw-r--r-- | Source/CPack/cmCPackComponentGroup.h | 117 | ||||
-rw-r--r-- | Source/CPack/cmCPackGenerator.cxx | 403 | ||||
-rw-r--r-- | Source/CPack/cmCPackGenerator.h | 14 | ||||
-rw-r--r-- | Source/CPack/cmCPackNSISGenerator.cxx | 329 | ||||
-rw-r--r-- | Source/CPack/cmCPackNSISGenerator.h | 27 | ||||
-rw-r--r-- | Source/CPack/cmCPackPackageMakerGenerator.cxx | 518 | ||||
-rw-r--r-- | Source/CPack/cmCPackPackageMakerGenerator.h | 61 | ||||
-rw-r--r-- | Tests/CMakeLists.txt | 45 | ||||
-rw-r--r-- | Tests/CPackComponents/CMakeLists.txt | 92 | ||||
-rw-r--r-- | Tests/CPackComponents/VerifyResult.cmake | 48 | ||||
-rw-r--r-- | Tests/CPackComponents/mylib.cpp | 7 | ||||
-rw-r--r-- | Tests/CPackComponents/mylib.h | 1 | ||||
-rw-r--r-- | Tests/CPackComponents/mylibapp.cpp | 6 | ||||
-rw-r--r-- | Tests/SimpleInstall/CMakeLists.txt | 26 | ||||
-rw-r--r-- | Tests/SimpleInstallS2/CMakeLists.txt | 26 |
18 files changed, 1724 insertions, 236 deletions
diff --git a/Modules/CPack.Info.plist.in b/Modules/CPack.Info.plist.in index da4872b..8971d6e 100644 --- a/Modules/CPack.Info.plist.in +++ b/Modules/CPack.Info.plist.in @@ -32,6 +32,6 @@ <key>IFPkgFormatVersion</key> <real>0.10000000149011612</real> <key>CFBundleIdentifier</key> -<string>com.@CPACK_PACKAGE_VENDOR@.@CPACK_PACKAGE_NAME@.@CPACK_PACKAGE_VERSION@</string> +<string>com.@CPACK_PACKAGE_VENDOR@.@CPACK_PACKAGE_NAME@.@CPACK_PACKAGE_VERSION@@CPACK_MODULE_VERSION_SUFFIX@</string> </dict> </plist> diff --git a/Modules/CPack.distribution.dist.in b/Modules/CPack.distribution.dist.in new file mode 100644 index 0000000..b5cae03 --- /dev/null +++ b/Modules/CPack.distribution.dist.in @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<installer-gui-script minSpecVersion="1.0"> + <title>@CPACK_PACKAGE_NAME@</title> + <options allow-external-scripts="no" customize="allow" rootVolumeOnly="false"></options> + @CPACK_PACKAGEMAKER_CHOICES@ +</installer-gui-script> diff --git a/Modules/NSIS.template.in b/Modules/NSIS.template.in index 7aaf137..8879a7f 100644 --- a/Modules/NSIS.template.in +++ b/Modules/NSIS.template.in @@ -36,58 +36,113 @@ ;Set compression SetCompressor @CPACK_NSIS_COMPRESSOR@ + + !include Sections.nsh + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; http://nsis.sourceforge.net/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend -;-------------------------------- -; determine admin versus local install -; Is install for "AllUsers" or "JustMe"? -; Default to "JustMe" - set to "AllUsers" if admin or on Win9x -; This function is used for the very first "custom page" of the installer. -; This custom page does not show up visibly, but it executes prior to the -; first visible page and sets up $INSTDIR properly... -; Choose different default installation folder based on SV_ALLUSERS... -; "Program Files" for AllUsers, "My Documents" for JustMe... - -Function .onInit - StrCpy $SV_ALLUSERS "JustMe" - StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + ClearErrors - UserInfo::GetName - IfErrors noLM - Pop $0 - UserInfo::GetAccountType - Pop $1 - StrCmp $1 "Admin" 0 +3 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Admin group' - StrCpy $SV_ALLUSERS "AllUsers" - Goto done - StrCmp $1 "Power" 0 +3 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Power Users group' - StrCpy $SV_ALLUSERS "AllUsers" - Goto done - - noLM: - StrCpy $SV_ALLUSERS "AllUsers" - ;Get installation folder from registry if available - - done: - StrCmp $SV_ALLUSERS "AllUsers" 0 +2 - StrCpy $INSTDIR "$PROGRAMFILES\@CPACK_PACKAGE_INSTALL_DIRECTORY@" - - StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage - !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_DIRECTORY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + ;Calling Section uninstall macro and writing zero installed flag + !insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_DIRECTORY@\Components\${SecName}" \ + "Installed" 0 + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_DIRECTORY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend - noOptionsPage: -FunctionEnd +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- ;-------------------------------- ;Interface Settings !define MUI_HEADERIMAGE !define MUI_ABORTWARNING - + ;-------------------------------- ; path functions @@ -289,7 +344,6 @@ Function un.RemoveFromPath Pop $0 FunctionEnd - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Uninstall sutff ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -407,9 +461,18 @@ Function ConditionalAddToRegisty FunctionEnd ;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ + +;-------------------------------- ; Define some macro setting for the gui @CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ @CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ ;-------------------------------- ;Pages @@ -425,6 +488,8 @@ FunctionEnd !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + @CPACK_NSIS_PAGE_COMPONENTS@ + !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH @@ -446,16 +511,14 @@ FunctionEnd ReserveFile "NSIS.InstallOptions.ini" !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS - ;-------------------------------- ;Installer Sections -Section "Installer Section" InstSection - +Section "-Core installation" ;Use the entire tree produced by the INSTALL target. Keep the ;list of directories here in sync with the RMDir commands below. SetOutPath "$INSTDIR" - File /r "${INST_DIR}\*.*" + @CPACK_NSIS_FULL_INSTALL@ ;Store installation folder WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR @@ -474,7 +537,8 @@ Section "Installer Section" InstSection Push "UninstallString" Push "$INSTDIR\Uninstall.exe" Call ConditionalAddToRegisty - + Push "ModifyPath" + ; Optional registration Push "DisplayIcon" Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" @@ -520,7 +584,7 @@ Section "Installer Section" InstSection SectionEnd -Section "Add to path" +Section "-Add to path" Push $INSTDIR\bin ;Read a value from an InstallOptions INI file !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" @@ -532,7 +596,6 @@ Section "Add to path" doNotAddToPath: SectionEnd - ;-------------------------------- ; Create custom pages Function InstallOptionsPage @@ -564,6 +627,26 @@ Function un.onInit ;Get installation folder from registry if available done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged FunctionEnd ;-------------------------------- @@ -601,6 +684,9 @@ Section "Uninstall" ; Remove the registry entries. DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + ; Removes all optional components + !insertmacro SectionList "RemoveSection" + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" @@ -647,4 +733,50 @@ Section "Uninstall" doNotRemoveFromPath: SectionEnd +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + StrCpy $SV_ALLUSERS "JustMe" + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ;Get installation folder from registry if available + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +2 + StrCpy $INSTDIR "$PROGRAMFILES\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd diff --git a/Source/CPack/cmCPackComponentGroup.h b/Source/CPack/cmCPackComponentGroup.h new file mode 100644 index 0000000..2e1703c --- /dev/null +++ b/Source/CPack/cmCPackComponentGroup.h @@ -0,0 +1,117 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + +#ifndef cmCPackComponentGroup_h +#define cmCPackComponentGroup_h + +#include <map> +#include <string> +#include <vector> + +class cmCPackComponentGroup; + +/** \class cmCPackInstallationType + * \brief A certain type of installation, which encompasses a + * set of components. + */ +class cmCPackInstallationType +{ +public: + /// The name of the installation type (used to reference this + /// installation type). + std::string Name; + + /// The name of the installation type as displayed to the user. + std::string DisplayName; + + /// The index number of the installation type. This is an arbitrary + /// numbering from 1 to the number of installation types. + unsigned Index; +}; + +/** \class cmCPackComponent + * \brief A single component to be installed by CPack. + */ +class cmCPackComponent +{ +public: + cmCPackComponent() : Group(0) { } + + /// The name of the component (used to reference the component). + std::string Name; + + /// The name of the component as displayed to the user. + std::string DisplayName; + + /// The component group that contains this component (if any). + cmCPackComponentGroup *Group; + + /// Whether this component group must always be installed. + bool IsRequired : 1; + + /// Whether this component group is hidden. A hidden component group + /// is always installed. However, it may still be shown to the user. + bool IsHidden : 1; + + /// Whether this component defaults to "disabled". + bool IsDisabledByDefault : 1; + + /// A description of this component. + std::string Description; + + /// The installation types that this component is a part of. + std::vector<cmCPackInstallationType *> InstallationTypes; + + /// The components that this component depends on. + std::vector<cmCPackComponent *> Dependencies; + + /// The components that depend on this component. + std::vector<cmCPackComponent *> ReverseDependencies; + + /// The list of installed files that are part of this component. + std::vector<std::string> Files; + + /// The list of installed directories that are part of this component. + std::vector<std::string> Directories; +}; + +/** \class cmCPackComponentGroup + * \brief A component group to be installed by CPack. + */ +class cmCPackComponentGroup +{ +public: + /// The name of the group (used to reference the group). + std::string Name; + + /// The name of the component as displayed to the user. + std::string DisplayName; + + /// The description of this component group. + std::string Description; + + /// Whether the name of the component will be shown in bold. + bool IsBold : 1; + + /// Whether the section should be expanded by default + bool IsExpandedByDefault : 1; + + /// The components within this group. + std::vector<cmCPackComponent*> Components; +}; + +#endif diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx index 8502df0..396bd9e 100644 --- a/Source/CPack/cmCPackGenerator.cxx +++ b/Source/CPack/cmCPackGenerator.cxx @@ -23,6 +23,7 @@ #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmGeneratedFileStream.h" +#include "cmCPackComponentGroup.h" #include <cmsys/SystemTools.hxx> #include <cmsys/Glob.hxx> @@ -101,13 +102,6 @@ int cmCPackGenerator::PrepareNames() std::string destFile = pdir; destFile += "/" + outName; std::string outFile = topDirectory + "/" + outName; - bool setDestDir = cmSystemTools::IsOn(this->GetOption("CPACK_SET_DESTDIR")); - std::string installPrefix = tempDirectory; - if (!setDestDir) - { - installPrefix += this->GetPackagingInstallPrefix(); - } - this->SetOptionIfNotSet("CPACK_TOPLEVEL_DIRECTORY", topDirectory.c_str()); this->SetOptionIfNotSet("CPACK_TEMPORARY_DIRECTORY", tempDirectory.c_str()); this->SetOptionIfNotSet("CPACK_OUTPUT_FILE_NAME", outName.c_str()); @@ -118,7 +112,7 @@ int cmCPackGenerator::PrepareNames() this->SetOptionIfNotSet("CPACK_NATIVE_INSTALL_DIRECTORY", cmsys::SystemTools::ConvertToOutputPath(this->GetInstallPath()).c_str()); this->SetOptionIfNotSet("CPACK_TEMPORARY_INSTALL_DIRECTORY", - installPrefix.c_str()); + tempDirectory.c_str()); cmCPackLogger(cmCPackLog::LOG_DEBUG, "Look for: CPACK_PACKAGE_DESCRIPTION_FILE" << std::endl); @@ -172,11 +166,19 @@ int cmCPackGenerator::InstallProject() { cmCPackLogger(cmCPackLog::LOG_OUTPUT, "Install projects" << std::endl); this->CleanTemporaryDirectory(); - std::string tempInstallDirectoryWithPostfix + + std::string bareTempInstallDirectory = this->GetOption("CPACK_TEMPORARY_INSTALL_DIRECTORY"); - const char* tempInstallDirectory = tempInstallDirectoryWithPostfix.c_str(); + std::string tempInstallDirectoryStr = bareTempInstallDirectory; + bool setDestDir = cmSystemTools::IsOn(this->GetOption("CPACK_SET_DESTDIR")); + if (!setDestDir) + { + tempInstallDirectoryStr += this->GetPackagingInstallPrefix(); + } + + const char* tempInstallDirectory = tempInstallDirectoryStr.c_str(); int res = 1; - if ( !cmsys::SystemTools::MakeDirectory(tempInstallDirectory)) + if ( !cmsys::SystemTools::MakeDirectory(bareTempInstallDirectory.c_str())) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem creating temporary directory: " @@ -185,7 +187,6 @@ int cmCPackGenerator::InstallProject() return 0; } - bool setDestDir = cmSystemTools::IsOn(this->GetOption("CPACK_SET_DESTDIR")); if ( setDestDir ) { std::string destDir = "DESTDIR="; @@ -227,7 +228,7 @@ int cmCPackGenerator::InstallProject() // If the project is a CMAKE project then run pre-install // and then read the cmake_install script to run it if ( !this->InstallProjectViaInstallCMakeProjects( - setDestDir, tempInstallDirectory) ) + setDestDir, bareTempInstallDirectory.c_str()) ) { return 0; } @@ -244,8 +245,6 @@ int cmCPackGenerator::InstallProject() int cmCPackGenerator::InstallProjectViaInstallCommands( bool setDestDir, const char* tempInstallDirectory) { - (void)setDestDir; - (void)tempInstallDirectory; const char* installCommands = this->GetOption("CPACK_INSTALL_COMMANDS"); if ( installCommands && *installCommands ) { @@ -454,7 +453,7 @@ int cmCPackGenerator::InstallProjectViaInstallScript( //---------------------------------------------------------------------- int cmCPackGenerator::InstallProjectViaInstallCMakeProjects( - bool setDestDir, const char* tempInstallDirectory) + bool setDestDir, const char* baseTempInstallDirectory) { const char* cmakeProjects = this->GetOption("CPACK_INSTALL_CMAKE_PROJECTS"); @@ -502,6 +501,51 @@ int cmCPackGenerator::InstallProjectViaInstallCMakeProjects( std::string installSubDirectory = it->c_str(); std::string installFile = installDirectory + "/cmake_install.cmake"; + std::vector<std::string> componentsVector; + + bool componentInstall = false; + if (this->SupportsComponentInstallation()) + { + // Determine the installation types for this project (if provided). + std::string installTypesVar = "CPACK_" + + cmSystemTools::UpperCase(installComponent) + "_INSTALL_TYPES"; + const char *installTypes = this->GetOption(installTypesVar.c_str()); + if (installTypes && *installTypes) + { + std::vector<std::string> installTypesVector; + cmSystemTools::ExpandListArgument(installTypes, installTypesVector); + std::vector<std::string>::iterator installTypeIt; + for (installTypeIt = installTypesVector.begin(); + installTypeIt != installTypesVector.end(); + ++installTypeIt) + { + this->GetInstallationType(installProjectName.c_str(), + installTypeIt->c_str()); + } + } + + // Determine the set of components that will be used in this project + std::string componentsVar + = "CPACK_COMPONENTS_" + cmSystemTools::UpperCase(installComponent); + const char *components = this->GetOption(componentsVar.c_str()); + if (components && *components) + { + cmSystemTools::ExpandListArgument(components, componentsVector); + std::vector<std::string>::iterator compIt; + for (compIt = componentsVector.begin(); + compIt != componentsVector.end(); + ++compIt) + { + GetComponent(installProjectName.c_str(), compIt->c_str()); + } + componentInstall = true; + } + } + if (componentsVector.empty()) + { + componentsVector.push_back(installComponent); + } + const char* buildConfig = this->GetOption("CPACK_BUILD_CONFIG"); cmGlobalGenerator* globalGenerator = this->MakefileMap->GetCMakeInstance()->CreateGlobalGenerator( @@ -555,72 +599,100 @@ int cmCPackGenerator::InstallProjectViaInstallCMakeProjects( cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Install project: " << installProjectName << std::endl); - cmake cm; - cm.AddCMakePaths(); - cm.SetProgressCallback(cmCPackGeneratorProgress, this); - cmGlobalGenerator gg; - gg.SetCMakeInstance(&cm); - std::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator()); - lg->SetGlobalGenerator(&gg); - cmMakefile *mf = lg->GetMakefile(); - std::string realInstallDirectory = tempInstallDirectory; - if ( !installSubDirectory.empty() && installSubDirectory != "/" ) - { - realInstallDirectory += installSubDirectory; - } - if ( setDestDir ) + // Run the installation for each component + std::vector<std::string>::iterator componentIt; + for (componentIt = componentsVector.begin(); + componentIt != componentsVector.end(); + ++componentIt) { - // For DESTDIR based packaging, use the *project* CMAKE_INSTALL_PREFIX - // underneath the tempInstallDirectory. The value of the project's - // CMAKE_INSTALL_PREFIX is sent in here as the value of the - // CPACK_INSTALL_PREFIX variable. - std::string dir; - if (this->GetOption("CPACK_INSTALL_PREFIX")) + std::string tempInstallDirectory = baseTempInstallDirectory; + installComponent = *componentIt; + if (componentInstall) { - dir += this->GetOption("CPACK_INSTALL_PREFIX"); + cmCPackLogger(cmCPackLog::LOG_OUTPUT, + "- Install component: " << installComponent + << std::endl); + } + + cmake cm; + cm.AddCMakePaths(); + cm.SetProgressCallback(cmCPackGeneratorProgress, this); + cmGlobalGenerator gg; + gg.SetCMakeInstance(&cm); + std::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator()); + lg->SetGlobalGenerator(&gg); + cmMakefile *mf = lg->GetMakefile(); + std::string realInstallDirectory = tempInstallDirectory; + if ( !installSubDirectory.empty() && installSubDirectory != "/" ) + { + realInstallDirectory += installSubDirectory; + } + if (componentInstall) + { + tempInstallDirectory += "/"; + tempInstallDirectory += installComponent; } - mf->AddDefinition("CMAKE_INSTALL_PREFIX", dir.c_str()); - cmCPackLogger(cmCPackLog::LOG_DEBUG, - "- Using DESTDIR + CPACK_INSTALL_PREFIX... (mf->AddDefinition)" - << std::endl); - cmCPackLogger(cmCPackLog::LOG_DEBUG, - "- Setting CMAKE_INSTALL_PREFIX to '" << dir << "'" << std::endl); - } - else - { - mf->AddDefinition("CMAKE_INSTALL_PREFIX", tempInstallDirectory); + if (!setDestDir) + { + tempInstallDirectory += this->GetPackagingInstallPrefix(); + } - cmCPackLogger(cmCPackLog::LOG_DEBUG, - "- Using non-DESTDIR install... (mf->AddDefinition)" << std::endl); - cmCPackLogger(cmCPackLog::LOG_DEBUG, - "- Setting CMAKE_INSTALL_PREFIX to '" << tempInstallDirectory - << "'" << std::endl); - } + if ( setDestDir ) + { + // For DESTDIR based packaging, use the *project* CMAKE_INSTALL_PREFIX + // underneath the tempInstallDirectory. The value of the project's + // CMAKE_INSTALL_PREFIX is sent in here as the value of the + // CPACK_INSTALL_PREFIX variable. + std::string dir; + if (this->GetOption("CPACK_INSTALL_PREFIX")) + { + dir += this->GetOption("CPACK_INSTALL_PREFIX"); + } + mf->AddDefinition("CMAKE_INSTALL_PREFIX", dir.c_str()); + + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "- Using DESTDIR + CPACK_INSTALL_PREFIX... (mf->AddDefinition)" + << std::endl); + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "- Setting CMAKE_INSTALL_PREFIX to '" << dir << "'" + << std::endl); + } + else + { + mf->AddDefinition("CMAKE_INSTALL_PREFIX", tempInstallDirectory.c_str()); - if ( buildConfig && *buildConfig ) - { - mf->AddDefinition("BUILD_TYPE", buildConfig); - } - std::string installComponentLowerCase - = cmSystemTools::LowerCase(installComponent); - if ( installComponentLowerCase != "all" ) - { - mf->AddDefinition("CMAKE_INSTALL_COMPONENT", - installComponent.c_str()); - } + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "- Using non-DESTDIR install... (mf->AddDefinition)" << std::endl); + cmCPackLogger(cmCPackLog::LOG_DEBUG, + "- Setting CMAKE_INSTALL_PREFIX to '" << tempInstallDirectory + << "'" << std::endl); + } - // strip on TRUE, ON, 1, one or several file names, but not on - // FALSE, OFF, 0 and an empty string - if (!cmSystemTools::IsOff(this->GetOption("CPACK_STRIP_FILES"))) - { - mf->AddDefinition("CMAKE_INSTALL_DO_STRIP", "1"); - } - int res = mf->ReadListFile(0, installFile.c_str()); - if ( cmSystemTools::GetErrorOccuredFlag() || !res ) - { - return 0; + if ( buildConfig && *buildConfig ) + { + mf->AddDefinition("BUILD_TYPE", buildConfig); + } + std::string installComponentLowerCase + = cmSystemTools::LowerCase(installComponent); + if ( installComponentLowerCase != "all" ) + { + mf->AddDefinition("CMAKE_INSTALL_COMPONENT", + installComponent.c_str()); + } + + // strip on TRUE, ON, 1, one or several file names, but not on + // FALSE, OFF, 0 and an empty string + if (!cmSystemTools::IsOff(this->GetOption("CPACK_STRIP_FILES"))) + { + mf->AddDefinition("CMAKE_INSTALL_DO_STRIP", "1"); + } + int res = mf->ReadListFile(0, installFile.c_str()); + if ( cmSystemTools::GetErrorOccuredFlag() || !res ) + { + return 0; + } } } } @@ -709,7 +781,6 @@ int cmCPackGenerator::DoPackage() const char* packageFileName = this->GetOption("CPACK_OUTPUT_FILE_PATH"); const char* tempDirectory = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); - cmCPackLogger(cmCPackLog::LOG_DEBUG, "Find files" << std::endl); cmsys::Glob gl; std::string findExpr = tempDirectory; @@ -736,8 +807,33 @@ int cmCPackGenerator::DoPackage() { tempDirectory = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); } + + // The files to be installed + std::vector<std::string> files = gl.GetFiles(); + + // For component installations, determine which files go into which + // components. + if (!this->Components.empty()) + { + std::vector<std::string>::const_iterator it; + for ( it = files.begin(); it != files.end(); ++ it ) + { + std::string fileN = cmSystemTools::RelativePath(tempDirectory, + it->c_str()); + + // Determine which component we are in. + std::string componentName = fileN.substr(0, fileN.find('/')); + + // Strip off the component part of the path. + fileN = fileN.substr(fileN.find('/')+1, std::string::npos); + + // Add this file to the list of files for the component. + this->Components[componentName].Files.push_back(fileN); + } + } + if ( !this->CompressFiles(tempPackageFileName, - tempDirectory, gl.GetFiles()) || cmSystemTools::GetErrorOccuredFlag()) + tempDirectory, files) || cmSystemTools::GetErrorOccuredFlag()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem compressing the directory" << std::endl); @@ -1092,3 +1188,158 @@ int cmCPackGenerator::CleanTemporaryDirectory() } return 1; } + +//---------------------------------------------------------------------- +bool cmCPackGenerator::SupportsComponentInstallation() const +{ + return false; +} + +//---------------------------------------------------------------------- +cmCPackInstallationType* +cmCPackGenerator::GetInstallationType(const char *projectName, const char *name) +{ + bool hasInstallationType = this->InstallationTypes.count(name) != 0; + cmCPackInstallationType *installType = &this->InstallationTypes[name]; + if (!hasInstallationType) + { + // Define the installation type + std::string macroPrefix = "CPACK_INSTALL_TYPE_" + + cmsys::SystemTools::UpperCase(name); + installType->Name = name; + + const char* displayName + = this->GetOption((macroPrefix + "_DISPLAY_NAME").c_str()); + if (displayName && *displayName) + { + installType->DisplayName = displayName; + } + else + { + installType->DisplayName = installType->Name; + } + + installType->Index = this->InstallationTypes.size(); + } + return installType; +} + +//---------------------------------------------------------------------- +cmCPackComponent* +cmCPackGenerator::GetComponent(const char *projectName, const char *name) +{ + bool hasComponent = this->Components.count(name) != 0; + cmCPackComponent *component = &this->Components[name]; + if (!hasComponent) + { + // Define the component + std::string macroPrefix = "CPACK_COMPONENT_" + + cmsys::SystemTools::UpperCase(name); + component->Name = name; + const char* displayName + = this->GetOption((macroPrefix + "_DISPLAY_NAME").c_str()); + if (displayName && *displayName) + { + component->DisplayName = displayName; + } + else + { + component->DisplayName = component->Name; + } + component->IsHidden + = this->IsSet((macroPrefix + "_HIDDEN").c_str()); + component->IsRequired + = this->IsSet((macroPrefix + "_REQUIRED").c_str()); + component->IsDisabledByDefault + = this->IsSet((macroPrefix + "_DISABLED").c_str()); + const char* groupName = this->GetOption((macroPrefix + "_GROUP").c_str()); + if (groupName && *groupName) + { + component->Group = GetComponentGroup(projectName, groupName); + component->Group->Components.push_back(component); + } + else + { + component->Group = 0; + } + + const char* description + = this->GetOption((macroPrefix + "_DESCRIPTION").c_str()); + if (description && *description) + { + component->Description = description; + } + + // Determine the installation types. + const char *installTypes + = this->GetOption((macroPrefix + "_INSTALL_TYPES").c_str()); + if (installTypes && *installTypes) + { + std::vector<std::string> installTypesVector; + cmSystemTools::ExpandListArgument(installTypes, installTypesVector); + std::vector<std::string>::iterator installTypesIt; + for (installTypesIt = installTypesVector.begin(); + installTypesIt != installTypesVector.end(); + ++installTypesIt) + { + component->InstallationTypes.push_back( + this->GetInstallationType(projectName, installTypesIt->c_str())); + } + } + + // Determine the component dependencies. + const char *depends = this->GetOption((macroPrefix + "_DEPENDS").c_str()); + if (depends && *depends) + { + std::vector<std::string> dependsVector; + cmSystemTools::ExpandListArgument(depends, dependsVector); + std::vector<std::string>::iterator dependIt; + for (dependIt = dependsVector.begin(); + dependIt != dependsVector.end(); + ++dependIt) + { + cmCPackComponent *child = GetComponent(projectName, dependIt->c_str()); + component->Dependencies.push_back(child); + child->ReverseDependencies.push_back(component); + } + } + } + return component; +} + +//---------------------------------------------------------------------- +cmCPackComponentGroup* +cmCPackGenerator::GetComponentGroup(const char *projectName, const char *name) +{ + std::string macroPrefix = "CPACK_COMPONENT_GROUP_" + + cmsys::SystemTools::UpperCase(name); + bool hasGroup = this->ComponentGroups.count(name) != 0; + cmCPackComponentGroup *group = &this->ComponentGroups[name]; + if (!hasGroup) + { + // Define the group + group->Name = name; + const char* displayName + = this->GetOption((macroPrefix + "_DISPLAY_NAME").c_str()); + if (displayName && *displayName) + { + group->DisplayName = displayName; + } + else + { + group->DisplayName = group->Name; + } + + const char* description + = this->GetOption((macroPrefix + "_DESCRIPTION").c_str()); + if (description && *description) + { + group->Description = description; + } + group->IsBold + = this->IsSet((macroPrefix + "_BOLD_TITLE").c_str()); + group->IsExpandedByDefault + = this->IsSet((macroPrefix + "_EXPANDED").c_str()); + } + return group; +} diff --git a/Source/CPack/cmCPackGenerator.h b/Source/CPack/cmCPackGenerator.h index da819d6..ad61d65 100644 --- a/Source/CPack/cmCPackGenerator.h +++ b/Source/CPack/cmCPackGenerator.h @@ -19,6 +19,8 @@ #define cmCPackGenerator_h #include "cmObject.h" +#include <map> +#include <vector> #define cmCPackTypeMacro(class, superclass) \ cmTypeMacro(class, superclass); \ @@ -44,6 +46,9 @@ class cmMakefile; class cmCPackLog; +class cmCPackInstallationType; +class cmCPackComponent; +class cmCPackComponentGroup; /** \class cmCPackGenerator * \brief A superclass of all CPack Generators @@ -120,6 +125,11 @@ protected: virtual int InstallProjectViaInstallCMakeProjects( bool setDestDir, const char* tempInstallDirectory); + virtual bool SupportsComponentInstallation() const; + virtual cmCPackInstallationType* GetInstallationType(const char *projectName, const char* name); + virtual cmCPackComponent* GetComponent(const char *projectName, const char* name); + virtual cmCPackComponentGroup* GetComponentGroup(const char *projectName, const char* name); + bool GeneratorVerbose; std::string Name; @@ -129,6 +139,10 @@ protected: std::string CMakeSelf; std::string CMakeRoot; + std::map<std::string, cmCPackInstallationType> InstallationTypes; + std::map<std::string, cmCPackComponent> Components; + std::map<std::string, cmCPackComponentGroup> ComponentGroups; + cmCPackLog* Logger; private: cmMakefile* MakefileMap; diff --git a/Source/CPack/cmCPackNSISGenerator.cxx b/Source/CPack/cmCPackNSISGenerator.cxx index eb1cb1f..6b43e1e 100644 --- a/Source/CPack/cmCPackNSISGenerator.cxx +++ b/Source/CPack/cmCPackNSISGenerator.cxx @@ -23,6 +23,7 @@ #include "cmMakefile.h" #include "cmGeneratedFileStream.h" #include "cmCPackLog.h" +#include "cmCPackComponentGroup.h" #include <cmsys/SystemTools.hxx> #include <cmsys/Glob.hxx> @@ -79,7 +80,12 @@ int cmCPackNSISGenerator::CompressFiles(const char* outFileName, for ( it = files.begin(); it != files.end(); ++ it ) { std::string fileN = cmSystemTools::RelativePath(toplevel, - it->c_str()); + it->c_str()); + if (!this->Components.empty()) + { + // Strip off the component part of the path. + fileN = fileN.substr(fileN.find('/')+1, std::string::npos); + } cmSystemTools::ReplaceString(fileN, "/", "\\"); str << " Delete \"$INSTDIR\\" << fileN.c_str() << "\"" << std::endl; } @@ -92,14 +98,32 @@ int cmCPackNSISGenerator::CompressFiles(const char* outFileName, cmOStringStream dstr; for ( sit = dirs.begin(); sit != dirs.end(); ++ sit ) { - std::string fileN = cmSystemTools::RelativePath(toplevel, - sit->c_str()); + std::string componentName; + std::string fileN = cmSystemTools::RelativePath(toplevel, sit->c_str()); 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::string::npos); + } + } cmSystemTools::ReplaceString(fileN, "/", "\\"); dstr << " RMDir \"$INSTDIR\\" << fileN.c_str() << "\"" << std::endl; + if (!componentName.empty()) + { + this->Components[componentName].Directories.push_back(fileN); + } } cmCPackLogger(cmCPackLog::LOG_DEBUG, "Uninstall Dirs: " << dstr.str().c_str() << std::endl); @@ -128,6 +152,116 @@ int cmCPackNSISGenerator::CompressFiles(const char* outFileName, this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE", installerIconCode.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; + + // 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()); + std::map<std::string, cmCPackInstallationType>::iterator installTypeIt; + for (installTypeIt = this->InstallationTypes.begin(); + installTypeIt != this->InstallationTypes.end(); + ++installTypeIt) + { + installTypes[installTypeIt->second.Index-1] = &installTypeIt->second; + } + std::vector<cmCPackInstallationType *>::iterator installTypeIt2; + for (installTypeIt2 = installTypes.begin(); + installTypeIt2 != installTypes.end(); + ++installTypeIt2) + { + installTypesCode += "InstType \""; + installTypesCode += (*installTypeIt2)->DisplayName; + installTypesCode += + "\"\n"; + } + + // Create installation groups first + std::map<std::string, cmCPackComponentGroup>::iterator groupIt; + for (groupIt = this->ComponentGroups.begin(); + groupIt != this->ComponentGroups.end(); + ++groupIt) + { + componentCode += this->CreateComponentGroupDescription(&groupIt->second); + + // Add the group description, if any. + if (!groupIt->second.Description.empty()) + { + groupDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" + + groupIt->first + "} \"" + + this->TranslateNewlines(groupIt->second.Description) + "\"\n"; + } + } + + // Create the remaining components, which aren't associated with groups. + std::map<std::string, cmCPackComponent>::iterator compIt; + for (compIt = this->Components.begin(); + compIt != this->Components.end(); + ++compIt) + { + if (!compIt->second.Group) + { + componentCode += this->CreateComponentDescription(&compIt->second); + } + + // Add this component to the various section lists. + sectionList += " !insertmacro \"${MacroName}\" \""; + sectionList += compIt->first; + sectionList += "\"\n"; + selectedVarsList += "Var " + compIt->first + "_selected\n"; + + // Add the component description, if any. + if (!compIt->second.Description.empty()) + { + componentDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" + + compIt->first + "} \"" + + this->TranslateNewlines(compIt->second.Description) + "\"\n"; + } + } + + 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()); + } + 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->ConfigureFile(nsisInInstallOptions.c_str(), nsisInstallOptions.c_str()); this->ConfigureFile(nsisInFileName.c_str(), nsisFileName.c_str()); @@ -252,9 +386,8 @@ int cmCPackNSISGenerator::InitializeInternal() } else { - cmCPackLogger(cmCPackLog::LOG_DEBUG, "CPACK_CREATE_DESKTOP_LINKS: " - << "not set" << std::endl); - + cmCPackLogger(cmCPackLog::LOG_DEBUG, "CPACK_CREATE_DESKTOP_LINKS: " + << "not set" << std::endl); } if ( cpackPackageExecutables ) { @@ -274,8 +407,8 @@ int cmCPackNSISGenerator::InitializeInternal() } std::vector<std::string>::iterator it; for ( it = cpackPackageExecutablesVector.begin(); - it != cpackPackageExecutablesVector.end(); - ++it ) + it != cpackPackageExecutablesVector.end(); + ++it ) { std::string execName = *it; ++ it; @@ -415,3 +548,183 @@ bool cmCPackNSISGenerator::GetListOfSubdirectories(const char* topdir, dirs.push_back(topdir); return true; } + +//---------------------------------------------------------------------- +bool cmCPackNSISGenerator::SupportsComponentInstallation() const +{ + return true; +} + +//---------------------------------------------------------------------- +std::string +cmCPackNSISGenerator:: +CreateComponentDescription(cmCPackComponent *component) const +{ + // 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; + std::vector<cmCPackInstallationType *>::iterator installTypeIter; + for (installTypeIter = component->InstallationTypes.begin(); + installTypeIter != component->InstallationTypes.end(); + ++installTypeIter) + { + out << " " << (*installTypeIter)->Index; + } + componentCode += " SectionIn" + out.str() + "\n"; + } + componentCode += " SetOutPath \"$INSTDIR\"\n"; + componentCode += " File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n"; + componentCode += "SectionEnd\n"; + + // Macro used to remove the component + componentCode += "!macro Remove_${" + component->Name + "}\n"; + std::vector<std::string>::iterator pathIt; + for (pathIt = component->Files.begin(); + pathIt != component->Files.end(); + ++pathIt) + { + componentCode += " Delete \"$INSTDIR\\" + *pathIt + "\"\n"; + } + for (pathIt = component->Directories.begin(); + pathIt != component->Directories.end(); + ++pathIt) + { + componentCode += " RMDir \"$INSTDIR\\" + *pathIt + "\"\n"; + } + componentCode += "!macroend\n"; + + // Macro used to select each of the components that this component + // depends on. + std::set<cmCPackComponent *> visited; + componentCode += "!macro Select_" + component->Name + "_depends\n"; + componentCode += CreateSelectionDependenciesDescription(component, visited); + componentCode += "!macroend\n"; + + // Macro used to deselect each of the components that depend on this + // component. + visited.clear(); + componentCode += "!macro Deselect_required_by_" + component->Name + "\n"; + componentCode += CreateDeselectionDependenciesDescription(component, visited); + componentCode += "!macroend\n"; + return componentCode; +} + +//---------------------------------------------------------------------- +std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription + (cmCPackComponent *component, + std::set<cmCPackComponent *>& visited) const +{ + // Don't visit a component twice + if (visited.count(component)) + { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + std::vector<cmCPackComponent *>::iterator dependIt; + for (dependIt = component->Dependencies.begin(); + dependIt != component->Dependencies.end(); + ++dependIt) + { + // Write NSIS code to select this dependency + out << " SectionGetFlags ${" << (*dependIt)->Name << "} $0\n"; + out << " IntOp $0 $0 | ${SF_SELECTED}\n"; + out << " SectionSetFlags ${" << (*dependIt)->Name << "} $0\n"; + out << " IntOp $" << (*dependIt)->Name << "_selected 0 + ${SF_SELECTED}\n"; + // Recurse + out << CreateSelectionDependenciesDescription(*dependIt, visited).c_str(); + } + + return out.str(); +} + + +//---------------------------------------------------------------------- +std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription + (cmCPackComponent *component, + std::set<cmCPackComponent *>& visited) const +{ + // Don't visit a component twice + if (visited.count(component)) + { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + std::vector<cmCPackComponent *>::iterator dependIt; + for (dependIt = component->ReverseDependencies.begin(); + dependIt != component->ReverseDependencies.end(); + ++dependIt) + { + // Write NSIS code to deselect this dependency + out << " SectionGetFlags ${" << (*dependIt)->Name << "} $0\n"; + out << " IntOp $1 ${SF_SELECTED} ~\n"; + out << " IntOp $0 $0 & $1\n"; + out << " SectionSetFlags ${" << (*dependIt)->Name << "} $0\n"; + out << " IntOp $" << (*dependIt)->Name << "_selected 0 + 0\n"; + + // Recurse + out << CreateDeselectionDependenciesDescription(*dependIt, visited).c_str(); + } + + return out.str(); +} + +//---------------------------------------------------------------------- +std::string +cmCPackNSISGenerator:: +CreateComponentGroupDescription(cmCPackComponentGroup *group) const +{ + if (group->Components.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"; + } + std::vector<cmCPackComponent*>::iterator comp; + for (comp = group->Components.begin(); + comp != group->Components.end(); + ++comp) + { + code += this->CreateComponentDescription(*comp); + } + code += "SectionGroupEnd\n"; + return code; +} + +std::string cmCPackNSISGenerator::TranslateNewlines(std::string str) +{ + cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n"); + return str; +} diff --git a/Source/CPack/cmCPackNSISGenerator.h b/Source/CPack/cmCPackNSISGenerator.h index 0ae4ef7..70068e41 100644 --- a/Source/CPack/cmCPackNSISGenerator.h +++ b/Source/CPack/cmCPackNSISGenerator.h @@ -20,6 +20,7 @@ #include "cmCPackGenerator.h" +#include <set> /** \class cmCPackNSISGenerator * \brief A generator for NSIS files @@ -48,6 +49,32 @@ protected: bool GetListOfSubdirectories(const char* dir, std::vector<std::string>& dirs); + + virtual bool SupportsComponentInstallation() const; + + /// Produce a string that contains the NSIS code to describe a + /// particular component. + std::string CreateComponentDescription(cmCPackComponent *component) const; + + /// Produce NSIS code that selects all of the components that this component + /// depends on, recursively. + std::string CreateSelectionDependenciesDescription + (cmCPackComponent *component, + std::set<cmCPackComponent *>& visited) const; + + /// Produce NSIS code that de-selects all of the components that are dependent + /// on this component, recursively. + std::string CreateDeselectionDependenciesDescription + (cmCPackComponent *component, + std::set<cmCPackComponent *>& visited) const; + + /// Produce a string that contains the NSIS code to describe a + /// particular component group, including its components. + std::string CreateComponentGroupDescription(cmCPackComponentGroup *group) const; + + /// Translations any newlines found in the string into \r\n, so that the + /// resulting string can be used within NSIS. + static std::string TranslateNewlines(std::string str); }; #endif diff --git a/Source/CPack/cmCPackPackageMakerGenerator.cxx b/Source/CPack/cmCPackPackageMakerGenerator.cxx index a5884a9..e4a003d 100644 --- a/Source/CPack/cmCPackPackageMakerGenerator.cxx +++ b/Source/CPack/cmCPackPackageMakerGenerator.cxx @@ -22,6 +22,7 @@ #include "cmSystemTools.h" #include "cmMakefile.h" #include "cmGeneratedFileStream.h" +#include "cmCPackComponentGroup.h" #include "cmCPackLog.h" #include <cmsys/SystemTools.hxx> @@ -38,6 +39,13 @@ cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator() { } +//---------------------------------------------------------------------- +bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const +{ + return true; +} + +//---------------------------------------------------------------------- int cmCPackPackageMakerGenerator::CopyInstallScript(const char* resdir, const char* script, const char* name) @@ -96,23 +104,59 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName, // them executable if(preflight) { - this->CopyInstallScript(resDir.c_str(), - preflight, - "preflight"); + this->CopyInstallScript(resDir.c_str(), + preflight, + "preflight"); } if(postflight) { - this->CopyInstallScript(resDir.c_str(), - postflight, - "postflight"); + this->CopyInstallScript(resDir.c_str(), + postflight, + "postflight"); } if(postupgrade) { - this->CopyInstallScript(resDir.c_str(), - postupgrade, - "postupgrade"); + this->CopyInstallScript(resDir.c_str(), + postupgrade, + "postupgrade"); } + if (!this->Components.empty()) + { + // Create the directory where component packages will be installed. + std::string basePackageDir = toplevel; + basePackageDir += "/packages"; + if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str())) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem creating component packages directory: " + << basePackageDir.c_str() << std::endl); + return 0; + } + + // Create packages for each component + std::map<std::string, cmCPackComponent>::iterator compIt; + for (compIt = this->Components.begin(); compIt != this->Components.end(); + ++compIt) + { + std::string packageFile = basePackageDir; + packageFile += '/'; + packageFile += GetPackageName(compIt->second); + + std::string packageDir = toplevel; + packageDir += '/'; + packageDir += compIt->first; + if (!this->GenerateComponentPackage(packageFile.c_str(), + packageDir.c_str(), + compIt->second)) + { + return 0; + } + } + } + this->SetOption("CPACK_MODULE_VERSION_SUFFIX", ""); + + // Copy or create all of the resource files we need. if ( !this->CopyCreateResourceFile("License") || !this->CopyCreateResourceFile("ReadMe") || !this->CopyCreateResourceFile("Welcome") @@ -126,68 +170,58 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName, std::string packageDirFileName = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); - packageDirFileName += ".pkg"; + if (this->Components.empty()) + { + packageDirFileName += ".pkg"; + } + else + { + packageDirFileName += ".mpkg"; + if (this->PackageMakerVersion == 3.0) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "PackageMaker 3.0 cannot build component-based installations." + << std::endl << "Please use PackageMaker 2.5 instead." << std::endl); + } + } - std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); - tmpFile += "/PackageMakerOutput.log"; cmOStringStream pkgCmd; pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") - << "\" -build -p \"" << packageDirFileName << "\" -f \"" - << this->GetOption("CPACK_TEMPORARY_DIRECTORY") - << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") - << "/Resources\" -i \"" - << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Info.plist\" -d \"" - << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Description.plist\""; - if ( this->PackageMakerVersion > 2.0 ) + << "\" -build -p \"" << packageDirFileName << "\""; + if (this->Components.empty()) { - pkgCmd << " -v"; + pkgCmd << " -f \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY"); } - cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << pkgCmd.str().c_str() - << std::endl); - std::string output; - int retVal = 1; - //bool res = cmSystemTools::RunSingleCommand(pkgCmd.str().c_str(), &output, - //&retVal, 0, this->GeneratorVerbose, 0); - bool res = true; - retVal = system(pkgCmd.str().c_str()); - cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running package maker" - << std::endl); - if ( !res || retVal ) + else { - cmGeneratedFileStream ofs(tmpFile.c_str()); - ofs << "# Run command: " << pkgCmd.str().c_str() << std::endl - << "# Output:" << std::endl - << output.c_str() << std::endl; - cmCPackLogger(cmCPackLog::LOG_ERROR, - "Problem running PackageMaker command: " << pkgCmd.str().c_str() - << std::endl << "Please check " << tmpFile.c_str() << " for errors" - << std::endl); - return 0; + pkgCmd << " -mi \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY") + << "/packages/"; } - // sometimes the pkgCmd finishes but the directory is not yet - // created, so try 10 times to see if it shows up - int tries = 10; - while(tries > 0 && - !cmSystemTools::FileExists(packageDirFileName.c_str())) + pkgCmd << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") + << "/Resources\" -i \"" + << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Info.plist\" -d \"" + << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Description.plist\""; + if ( this->PackageMakerVersion > 2.0 ) { - cmSystemTools::Delay(500); - tries--; + pkgCmd << " -v"; } - if(!cmSystemTools::FileExists(packageDirFileName.c_str())) + if (!RunPackageMaker(pkgCmd.str().c_str(), packageDirFileName.c_str())) + return 0; + + if (!this->Components.empty()) { - cmCPackLogger( - cmCPackLog::LOG_ERROR, - "Problem running PackageMaker command: " << pkgCmd.str().c_str() - << std::endl << "Package not created: " << packageDirFileName.c_str() - << std::endl); + WriteDistributionFile(packageDirFileName.c_str()); } - tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); tmpFile += "/hdiutilOutput.log"; cmOStringStream dmgCmd; dmgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM_DISK_IMAGE") << "\" create -ov -format UDZO -srcfolder \"" << packageDirFileName << "\" \"" << outFileName << "\""; - res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output, + std::string output; + int retVal = 1; + bool res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output, &retVal, 0, this->GeneratorVerbose, 0); if ( !res || retVal ) { @@ -339,8 +373,14 @@ bool cmCPackPackageMakerGenerator::CopyCreateResourceFile(const char* name) return true; } -bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name) +bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name, + const char* outName) { + if (!outName) + { + outName = name; + } + std::string inFName = "CPack."; inFName += name; inFName += ".in"; @@ -354,10 +394,374 @@ bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name) std::string destFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); destFileName += "/"; - destFileName += name; + destFileName += outName; cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: " << inFileName.c_str() << " to " << destFileName.c_str() << std::endl); this->ConfigureFile(inFileName.c_str(), destFileName.c_str()); return true; } + +//---------------------------------------------------------------------- +bool cmCPackPackageMakerGenerator::RunPackageMaker(const char *command, + const char *packageFile) +{ + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + tmpFile += "/PackageMakerOutput.log"; + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl); + std::string output; + int retVal = 1; + bool res = cmSystemTools::RunSingleCommand(command, &output, &retVal, 0, + this->GeneratorVerbose, 0); + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running package maker" + << std::endl); + if ( !res || retVal ) + { + cmGeneratedFileStream ofs(tmpFile.c_str()); + ofs << "# Run command: " << command << std::endl + << "# Output:" << std::endl + << output.c_str() << std::endl; + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem running PackageMaker command: " << command + << std::endl << "Please check " << tmpFile.c_str() << " for errors" + << std::endl); + return false; + } + // sometimes the command finishes but the directory is not yet + // created, so try 10 times to see if it shows up + int tries = 10; + while(tries > 0 && + !cmSystemTools::FileExists(packageFile)) + { + cmSystemTools::Delay(500); + tries--; + } + if(!cmSystemTools::FileExists(packageFile)) + { + cmCPackLogger( + cmCPackLog::LOG_ERROR, + "Problem running PackageMaker command: " << command + << std::endl << "Package not created: " << packageFile + << std::endl); + return false; + } + + return true; +} + +//---------------------------------------------------------------------- +std::string +cmCPackPackageMakerGenerator::GetPackageName(const cmCPackComponent& component) +{ + std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + packagesDir += ".dummy"; + cmOStringStream out; + out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) + << "-" << component.Name << ".pkg"; + return out.str(); +} + +//---------------------------------------------------------------------- +bool +cmCPackPackageMakerGenerator:: +GenerateComponentPackage(const char *packageFile, + const char *packageDir, + const cmCPackComponent& component) +{ + cmCPackLogger(cmCPackLog::LOG_OUTPUT, + "- Building component package: " << packageFile << std::endl); + + // Create the description file for this component. + std::string descriptionFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + descriptionFile += '/' + component.Name + "-Description.plist"; + std::ofstream out(descriptionFile.c_str()); + out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl + << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"" + << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << std::endl + << "<plist version=\"1.4\">" << std::endl + << "<dict>" << std::endl + << " <key>IFPkgDescriptionTitle</key>" << std::endl + << " <string>" << component.DisplayName << "</string>" << std::endl + << " <key>IFPkgDescriptionVersion</key>" << std::endl + << " <string>" << this->GetOption("CPACK_PACKAGE_VERSION") + << "</string>" << std::endl + << " <key>IFPkgDescriptionDescription</key>" << std::endl + << " <string>" + this->EscapeForXML(component.Description) + << "</string>" << std::endl + << "</dict>" << std::endl + << "</plist>" << std::endl; + out.close(); + + // Create the Info.plist file for this component + std::string moduleVersionSuffix = "."; + moduleVersionSuffix += component.Name; + this->SetOption("CPACK_MODULE_VERSION_SUFFIX", moduleVersionSuffix.c_str()); + std::string infoFileName = component.Name; + infoFileName += "-Info.plist"; + if (!this->CopyResourcePlistFile("Info.plist", infoFileName.c_str())) + { + return false; + } + + // Run PackageMaker + cmOStringStream pkgCmd; + pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") + << "\" -build -p \"" << packageFile << "\"" + << " -f \"" << packageDir << "\"" + << "-i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") + << "/" << infoFileName << "\"" + << "-d \"" << descriptionFile << "\""; + return RunPackageMaker(pkgCmd.str().c_str(), packageFile); +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator:: +WriteDistributionFile(const char* metapackageFile) +{ + std::string distributionTemplate + = this->FindTemplate("CPack.distribution.dist.in"); + if ( distributionTemplate.empty() ) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find input file: " + << distributionTemplate << std::endl); + return; + } + + std::string distributionFile = metapackageFile; + distributionFile += "/Contents/distribution.dist"; + + // Create the choice outline, which provides a tree-based view of + // the components in their groups. + cmOStringStream choiceOut; + choiceOut << "<choices-outline>" << std::endl; + + // Emit the outline for the groups + std::map<std::string, cmCPackComponentGroup>::iterator groupIt; + for (groupIt = this->ComponentGroups.begin(); + groupIt != this->ComponentGroups.end(); + ++groupIt) + { + CreateChoiceOutline(groupIt->second, choiceOut); + } + + // Emit the outline for the non-grouped components + std::map<std::string, cmCPackComponent>::iterator compIt; + for (compIt = this->Components.begin(); compIt != this->Components.end(); + ++compIt) + { + if (!compIt->second.Group) + { + choiceOut << "<line choice=\"" << compIt->first << "Choice\"></line>" + << std::endl; + } + } + choiceOut << "</choices-outline>" << std::endl; + + // Create the actual choices + for (groupIt = this->ComponentGroups.begin(); + groupIt != this->ComponentGroups.end(); + ++groupIt) + { + CreateChoice(groupIt->second, choiceOut); + } + for (compIt = this->Components.begin(); compIt != this->Components.end(); + ++compIt) + { + CreateChoice(compIt->second, choiceOut); + } + this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str().c_str()); + + // Create the distribution.dist file in the metapackage to turn it + // into a distribution package. + this->ConfigureFile(distributionTemplate.c_str(), + distributionFile.c_str()); +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator:: +CreateChoiceOutline(const cmCPackComponentGroup& group, cmOStringStream& out) +{ + out << "<line choice=\"" << group.Name << "Choice\">" << std::endl; + std::vector<cmCPackComponent*>::const_iterator compIt; + for (compIt = group.Components.begin(); compIt != group.Components.end(); + ++compIt) + { + out << " <line choice=\"" << (*compIt)->Name << "Choice\"></line>" + << std::endl; + } + out << "</line>" << std::endl; +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponentGroup& group, + cmOStringStream& out) +{ + out << "<choice id=\"" << group.Name << "Choice\" " + << "title=\"" << group.DisplayName << "\" " + << "start_selected=\"true\" " + << "start_enabled=\"true\" " + << "start_visible=\"true\" "; + if (!group.Description.empty()) + { + out << "description=\"" << EscapeForXML(group.Description) + << "\""; + } + out << "></choice>" << std::endl; +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponent& component, + cmOStringStream& out) +{ + std::string packageId = "com."; + packageId += this->GetOption("CPACK_PACKAGE_VENDOR"); + packageId += '.'; + packageId += this->GetOption("CPACK_PACKAGE_NAME"); + packageId += '.'; + packageId += this->GetOption("CPACK_PACKAGE_VERSION"); + packageId += '.'; + packageId += component.Name; + + out << "<choice id=\"" << component.Name << "Choice\" " + << "title=\"" << component.DisplayName << "\" " + << "start_selected=\"" + << (component.IsDisabledByDefault && !component.IsRequired? "false" : "true") + << "\" " + << "start_enabled=\"" + << (component.IsRequired? "false" : "true") + << "\" " + << "start_visible=\"" << (component.IsHidden? "false" : "true") << "\" "; + if (!component.Description.empty()) + { + out << "description=\"" << EscapeForXML(component.Description) + << "\" "; + } + if (!component.Dependencies.empty() || !component.ReverseDependencies.empty()) + { + // The "selected" expression is evaluated each time any choice is + // selected, for all choices *except* the one that the user + // selected. A component is marked selected if it has been + // selected (my.choice.selected in Javascript) and all of the + // components it depends on have been selected (transitively) or + // if any of the components that depend on it have been selected + // (transitively). Assume that we have components A, B, C, D, and + // E, where each component depends on the previous component (B + // depends on A, C depends on B, D depends on C, and E depends on + // D). The expression we build for the component C will be + // my.choice.selected && B && A || D || E + // This way, selecting C will automatically select everything it depends + // on (B and A), while selecting something that depends on C--either D + // or E--will automatically cause C to get selected. + out << "selected=\"my.choice.selected"; + AddDependencyAttributes(component, out); + AddReverseDependencyAttributes(component, out); + out << "\""; + } + out << ">" << std::endl; + out << " <pkg-ref id=\"" << packageId << "\"></pkg-ref>" << std::endl; + out << "</choice>" << std::endl; + + // Create a description of the package associated with this + // component. + std::string relativePackageLocation = "Contents/Packages/"; + relativePackageLocation += GetPackageName(component); + + // Determine the installed size of the package. To do so, we dig + // into the Info.plist file from the generated package to retrieve + // this size. + int installedSize = 0; + std::string infoPlistFile = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + infoPlistFile += ".mpkg/"; + infoPlistFile += relativePackageLocation; + infoPlistFile += "/Contents/Info.plist"; + bool foundFlagInstalledSize = false; + std::string line; + std::ifstream ifs(infoPlistFile.c_str()); + while ( cmSystemTools::GetLineFromStream(ifs, line) ) + { + if (foundFlagInstalledSize) + { + std::string::size_type pos = line.find("<integer>"); + if (pos == std::string::npos) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot parse package size in " + << infoPlistFile << std::endl + << "String is \"" << line << "\"" << std::endl); + } + else + { + line.erase(0, pos + 9); + pos = line.find("</integer>"); + if (pos == std::string::npos) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot parse package size in " + << infoPlistFile << std::endl); + } + else + { + line.erase(pos, std::string::npos); + installedSize = atoi(line.c_str()); + } + } + foundFlagInstalledSize = false; + } + else + { + foundFlagInstalledSize + = line.find("IFPkgFlagInstalledSize") != std::string::npos; + } + } + + + out << "<pkg-ref id=\"" << packageId << "\" " + << "version=\"" << this->GetOption("CPACK_PACKAGE_VERSION") << "\" " + << "installKBytes=\"" << installedSize << "\" " + << "auth=\"Admin\" onConclusion=\"None\">" + << "file:./" << relativePackageLocation << "</pkg-ref>" << std::endl; +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator:: +AddDependencyAttributes(const cmCPackComponent& component, cmOStringStream& out) +{ + std::vector<cmCPackComponent *>::const_iterator dependIt; + for (dependIt = component.Dependencies.begin(); + dependIt != component.Dependencies.end(); + ++dependIt) + { + out << " && choices['" << (*dependIt)->Name << "Choice'].selected"; + AddDependencyAttributes(**dependIt, out); + } +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator:: +AddReverseDependencyAttributes(const cmCPackComponent& component, + cmOStringStream& out) +{ + std::vector<cmCPackComponent *>::const_iterator dependIt; + for (dependIt = component.ReverseDependencies.begin(); + dependIt != component.ReverseDependencies.end(); + ++dependIt) + { + out << " || choices['" << (*dependIt)->Name << "Choice'].selected"; + AddReverseDependencyAttributes(**dependIt, out); + } +} + +//---------------------------------------------------------------------- +std::string cmCPackPackageMakerGenerator::EscapeForXML(std::string str) +{ + cmSystemTools::ReplaceString(str, "&", "&"); + cmSystemTools::ReplaceString(str, "<", "<"); + cmSystemTools::ReplaceString(str, ">", ">"); + cmSystemTools::ReplaceString(str, "\"", """); + return str; +} diff --git a/Source/CPack/cmCPackPackageMakerGenerator.h b/Source/CPack/cmCPackPackageMakerGenerator.h index 6c16c75..2be8a2c 100644 --- a/Source/CPack/cmCPackPackageMakerGenerator.h +++ b/Source/CPack/cmCPackPackageMakerGenerator.h @@ -21,6 +21,8 @@ #include "cmCPackGenerator.h" +class cmCPackComponent; + /** \class cmCPackPackageMakerGenerator * \brief A generator for PackageMaker files * @@ -38,6 +40,8 @@ public: cmCPackPackageMakerGenerator(); virtual ~cmCPackPackageMakerGenerator(); + virtual bool SupportsComponentInstallation() const; + protected: int CopyInstallScript(const char* resdir, const char* script, @@ -49,8 +53,63 @@ protected: virtual const char* GetOutputPostfix() { return "darwin"; } bool CopyCreateResourceFile(const char* name); - bool CopyResourcePlistFile(const char* name); + bool CopyResourcePlistFile(const char* name, const char* outName = 0); + + // Run PackageMaker with the given command line, which will (if + // successful) produce the given package file. Returns true if + // PackageMaker succeeds, false otherwise. + bool RunPackageMaker(const char *command, const char *packageFile); + + // Retrieve the name of package file that will be generated for this + // component. The name is just the file name with extension, and + // does not include the subdirectory. + std::string GetPackageName(const cmCPackComponent& component); + + // Generate a package in the file packageFile for the given + // component. All of the files within this component are stored in + // the directory packageDir. Returns true if successful, false + // otherwise. + bool GenerateComponentPackage(const char *packageFile, + const char *packageDir, + const cmCPackComponent& component); + + // Writes a distribution.dist file, which turns a metapackage into a + // full-fledged distribution. This file is used to describe + // inter-component dependencies. metapackageFile is the name of the + // metapackage for the distribution. Only valid for a + // component-based install. + void WriteDistributionFile(const char* metapackageFile); + + // Subroutine of WriteDistributionFile that writes out the + // dependency attributes for inter-component dependencies. + void AddDependencyAttributes(const cmCPackComponent& component, + cmOStringStream& out); + + // Subroutine of WriteDistributionFile that writes out the + // reverse dependency attributes for inter-component dependencies. + void AddReverseDependencyAttributes(const cmCPackComponent& component, + cmOStringStream& out); + + // Generates XML that encodes the hierarchy of component groups and + // their components in a form that can be used by distribution + // metapackages. + void CreateChoiceOutline(const cmCPackComponentGroup& group, + cmOStringStream& out); + + /// Create the "choice" XML element to describe a component group + /// for the installer GUI. + void CreateChoice(const cmCPackComponentGroup& group, + cmOStringStream& out); + + /// Create the "choice" XML element to describe a component for the + /// installer GUI. + void CreateChoice(const cmCPackComponent& component, + cmOStringStream& out); + // Escape the given string to make it usable as an XML attribute + // value. + std::string EscapeForXML(std::string str); + double PackageMakerVersion; }; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 03d50fb..74bbc07 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -19,7 +19,7 @@ IF(BUILD_TESTING) OPTION(CMAKE_RUN_LONG_TESTS "Should the long tests be run (such as Bootstrap)." ON) MARK_AS_ADVANCED(CMAKE_RUN_LONG_TESTS) - + IF (CMAKE_RUN_LONG_TESTS) OPTION(CTEST_TEST_CTEST "Should the tests that run a full sub ctest process be run?" @@ -27,6 +27,34 @@ IF(BUILD_TESTING) MARK_AS_ADVANCED(CTEST_TEST_CTEST) ENDIF (CMAKE_RUN_LONG_TESTS) + # Should CPack tests be run? By default, yes, but... + # + # Disable packaging test on Apple 10.3 and below. PackageMaker starts + # DiskManagementTool as root and disowns it + # (http://lists.apple.com/archives/installer-dev/2005/Jul/msg00005.html). + # It is left holding open pipe handles and preventing ProcessUNIX from + # detecting end-of-data even after its immediate child exits. Then + # the test hangs until it times out and is killed. This is a + # well-known bug in kwsys process execution that I would love to get + # time to fix. + # + OPTION(CTEST_TEST_CPACK + "Should the tests that use '--build-target package' be run?" + ON) + MARK_AS_ADVANCED(CTEST_TEST_CPACK) + IF(APPLE AND CTEST_TEST_CPACK) + EXECUTE_PROCESS( + COMMAND sw_vers -productVersion + OUTPUT_VARIABLE OSX_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + IF(OSX_VERSION MATCHES "^10\\.[0123]" OR OSX_VERSION MATCHES "ProductVersion:\t10\\.[0123]") + MESSAGE(STATUS "Forcing CTEST_TEST_CPACK=OFF on OSX < 10.4") + MESSAGE(STATUS "OSX_VERSION='${OSX_VERSION}'") + SET(CTEST_TEST_CPACK OFF) + ENDIF(OSX_VERSION MATCHES "^10\\.[0123]" OR OSX_VERSION MATCHES "ProductVersion:\t10\\.[0123]") + ENDIF(APPLE AND CTEST_TEST_CPACK) + # Use 1500 or CTEST_TEST_TIMEOUT for long test timeout value, # whichever is greater. SET(CMAKE_LONG_TEST_TIMEOUT 1500) @@ -259,6 +287,21 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel "-DSTAGE2:BOOL=1" --test-command ${SimpleInstallInstallDir}/MyTest/bin/SimpleInstExeS2) + IF(CTEST_TEST_CPACK) + ADD_TEST(CPackComponents ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/CPackComponents" + "${CMake_BINARY_DIR}/Tests/CPackComponents" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-project CPackComponents + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-two-config + --build-target package + --test-command ${CMAKE_CMAKE_COMMAND} + "-DCPackComponents_BINARY_DIR:PATH=${CMake_BINARY_DIR}/Tests/CPackComponents" + -P "${CMake_SOURCE_DIR}/Tests/CPackComponents/VerifyResult.cmake") + ENDIF(CTEST_TEST_CPACK) + ADD_TEST(X11 ${CMAKE_CTEST_COMMAND} --build-and-test "${CMake_SOURCE_DIR}/Tests/X11" diff --git a/Tests/CPackComponents/CMakeLists.txt b/Tests/CPackComponents/CMakeLists.txt new file mode 100644 index 0000000..e52eaa8 --- /dev/null +++ b/Tests/CPackComponents/CMakeLists.txt @@ -0,0 +1,92 @@ +# CPack Example: User-selectable Installation Components +# +# In this example, we have a simple library (mylib) with an example +# application (mylibapp). We create a binary installer that allows +# users to select which pieces will be installed: the example +# application, the library binaries, and/or the header file. +cmake_minimum_required(VERSION 2.6) +project(CPackComponents) + +# Create the mylib library +add_library(mylib mylib.cpp) + +# Create the mylibapp application +add_executable(mylibapp mylibapp.cpp) +target_link_libraries(mylibapp mylib) + +# Create installation targets. Note that we put each kind of file +# into a different component via COMPONENT. These components will +# be used to create the installation components. +install(TARGETS mylib + ARCHIVE + DESTINATION lib + COMPONENT libraries) +install(TARGETS mylibapp + RUNTIME + DESTINATION bin + COMPONENT applications) +install(FILES mylib.h + DESTINATION include + COMPONENT headers) + +# CPack boilerplate for this project +set(CPACK_PACKAGE_NAME "MyLib") +set(CPACK_PACKAGE_VENDOR "CMake.org") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MyLib - CPack Component Installation Example") +set(CPACK_PACKAGE_VERSION "1.0.0") +set(CPACK_PACKAGE_VERSION_MAJOR "1") +set(CPACK_PACKAGE_VERSION_MINOR "0") +set(CPACK_PACKAGE_VERSION_PATCH "0") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "CPack Component Example") + +# Tell CPack all of the components to install. The "ALL" +# refers to the fact that this is the set of components that +# will be included when CPack is instructed to put everything +# into the binary installer (the default behavior). +set(CPACK_COMPONENTS_ALL applications libraries headers) + +# Set the displayed names for each of the components to install. +# These will be displayed in the list of components inside the installer. +set(CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "MyLib Application") +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C++ Headers") + +# Provide descriptions for each of the components to install. +# When the user hovers the mouse over the name of a component, +# the description will be shown in the "Description" box in the +# installer. If no descriptions are provided, the "Description" +# box will be removed. +set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION + "An extremely useful application that makes use of MyLib") +set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION + "Static libraries used to build programs with MyLib") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with MyLib") + +# Put the components into two different groups: "Runtime" and "Development" +set(CPACK_COMPONENT_APPLICATIONS_GROUP "Runtime") +set(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +# Expand the "Development" group by default, since we have so few components. +# Also, provide this group with a description. +set(CPACK_COMPONENT_GROUP_DEVELOPMENT_EXPANDED ON) +set(CPACK_COMPONENT_GROUP_DEVELOPMENT_DESCRIPTION + "All of the tools you'll ever need to develop software") + +# It doesn't make sense to install the headers without the libraries +# (because you could never use the headers!), so make the headers component +# depend on the libraries component. +set(CPACK_COMPONENT_HEADERS_DEPENDS libraries) + +# Create two installation types with pre-selected components. +# The "Developer" installation has just the library and headers, +# while the "Full" installation has everything. +set(CPACK_ALL_INSTALL_TYPES Full Developer) +set(CPACK_INSTALL_TYPE_FULL_DISPLAY_NAME "Everything") +set(CPACK_COMPONENT_LIBRARIES_INSTALL_TYPES Developer Full) +set(CPACK_COMPONENT_HEADERS_INSTALL_TYPES Developer Full) +set(CPACK_COMPONENT_APPLICATIONS_INSTALL_TYPES Full) + +# Include CPack to introduce the appropriate targets +include(CPack) diff --git a/Tests/CPackComponents/VerifyResult.cmake b/Tests/CPackComponents/VerifyResult.cmake new file mode 100644 index 0000000..3e64a46 --- /dev/null +++ b/Tests/CPackComponents/VerifyResult.cmake @@ -0,0 +1,48 @@ +message(STATUS "=============================================================================") +message(STATUS "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)") +message(STATUS "") + +if(NOT CPackComponents_BINARY_DIR) + message(FATAL_ERROR "CPackComponents_BINARY_DIR not set") +endif(NOT CPackComponents_BINARY_DIR) + +set(expected_file_mask "") + +if(WIN32) + # Only expect the *.exe installer if it looks like NSIS is + # installed on this machine: + # + find_program(NSIS_MAKENSIS_EXECUTABLE NAMES makensis + PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS] + DOC "makensis.exe location" + ) + if(NSIS_MAKENSIS_EXECUTABLE) + set(expected_file_mask "${CPackComponents_BINARY_DIR}/*.exe") + endif(NSIS_MAKENSIS_EXECUTABLE) +endif(WIN32) + +if(APPLE) + # Always expect the *.dmg installer - PackageMaker should always + # be installed on a development Mac: + # + set(expected_file_mask "${CPackComponents_BINARY_DIR}/*.dmg") +endif(APPLE) + +if(expected_file_mask) + set(expected_count 1) + file(GLOB expected_file "${expected_file_mask}") + + message(STATUS "expected_count='${expected_count}'") + message(STATUS "expected_file='${expected_file}'") + message(STATUS "expected_file_mask='${expected_file_mask}'") + + if(NOT expected_file) + message(FATAL_ERROR "error: expected_file does not exist: CPackComponents test fails.") + endif(NOT expected_file) + + list(LENGTH expected_file actual_count) + message(STATUS "actual_count='${actual_count}'") + if(NOT actual_count EQUAL expected_count) + message(FATAL_ERROR "error: expected_count does not match actual_count: CPackComponents test fails.") + endif(NOT actual_count EQUAL expected_count) +endif(expected_file_mask) diff --git a/Tests/CPackComponents/mylib.cpp b/Tests/CPackComponents/mylib.cpp new file mode 100644 index 0000000..8ddac19 --- /dev/null +++ b/Tests/CPackComponents/mylib.cpp @@ -0,0 +1,7 @@ +#include "mylib.h" +#include "stdio.h" + +void mylib_function() +{ + printf("This is mylib"); +} diff --git a/Tests/CPackComponents/mylib.h b/Tests/CPackComponents/mylib.h new file mode 100644 index 0000000..5d0a822 --- /dev/null +++ b/Tests/CPackComponents/mylib.h @@ -0,0 +1 @@ +void mylib_function(); diff --git a/Tests/CPackComponents/mylibapp.cpp b/Tests/CPackComponents/mylibapp.cpp new file mode 100644 index 0000000..a438ac7 --- /dev/null +++ b/Tests/CPackComponents/mylibapp.cpp @@ -0,0 +1,6 @@ +#include "mylib.h" + +int main() +{ + mylib_function(); +} diff --git a/Tests/SimpleInstall/CMakeLists.txt b/Tests/SimpleInstall/CMakeLists.txt index 23d3d27..34914b6 100644 --- a/Tests/SimpleInstall/CMakeLists.txt +++ b/Tests/SimpleInstall/CMakeLists.txt @@ -355,27 +355,11 @@ SET(CMAKE_INSTALL_DEBUG_LIBRARIES 1) INCLUDE(InstallRequiredSystemLibraries) INCLUDE(CPack) -# Disable packaging test on Apple 10.3 and below. PackageMaker starts -# DiskManagementTool as root and disowns it -# (http://lists.apple.com/archives/installer-dev/2005/Jul/msg00005.html). -# It is left holding open pipe handles and preventing ProcessUNIX from -# detecting end-of-data even after its immediate child exits. Then -# the test hangs until it times out and is killed. This is a -# well-known bug in kwsys process execution that I would love to get -# time to fix. -SET(PACKAGE_TARGET --build-target package) -IF(APPLE AND NOT CTEST_TEST_CPACK) - EXECUTE_PROCESS( - COMMAND sw_vers -productVersion - OUTPUT_VARIABLE OSX_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - IF("${OSX_VERSION}" MATCHES "^10\\.[0123]" OR "${OSX_VERSION}" MATCHES "ProductVersion:\t10\\.[0123]") - MESSAGE(STATUS "Disabling package test on OSX < 10.4") - MESSAGE(STATUS "OSX_VERSION='${OSX_VERSION}'") - SET(PACKAGE_TARGET) - ENDIF("${OSX_VERSION}" MATCHES "^10\\.[0123]" OR "${OSX_VERSION}" MATCHES "ProductVersion:\t10\\.[0123]") -ENDIF(APPLE AND NOT CTEST_TEST_CPACK) +IF(CTEST_TEST_CPACK) + SET(PACKAGE_TARGET --build-target package) +ELSE(CTEST_TEST_CPACK) + SET(PACKAGE_TARGET) +ENDIF(CTEST_TEST_CPACK) ADD_CUSTOM_COMMAND( TARGET ${install_target} diff --git a/Tests/SimpleInstallS2/CMakeLists.txt b/Tests/SimpleInstallS2/CMakeLists.txt index 23d3d27..34914b6 100644 --- a/Tests/SimpleInstallS2/CMakeLists.txt +++ b/Tests/SimpleInstallS2/CMakeLists.txt @@ -355,27 +355,11 @@ SET(CMAKE_INSTALL_DEBUG_LIBRARIES 1) INCLUDE(InstallRequiredSystemLibraries) INCLUDE(CPack) -# Disable packaging test on Apple 10.3 and below. PackageMaker starts -# DiskManagementTool as root and disowns it -# (http://lists.apple.com/archives/installer-dev/2005/Jul/msg00005.html). -# It is left holding open pipe handles and preventing ProcessUNIX from -# detecting end-of-data even after its immediate child exits. Then -# the test hangs until it times out and is killed. This is a -# well-known bug in kwsys process execution that I would love to get -# time to fix. -SET(PACKAGE_TARGET --build-target package) -IF(APPLE AND NOT CTEST_TEST_CPACK) - EXECUTE_PROCESS( - COMMAND sw_vers -productVersion - OUTPUT_VARIABLE OSX_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - IF("${OSX_VERSION}" MATCHES "^10\\.[0123]" OR "${OSX_VERSION}" MATCHES "ProductVersion:\t10\\.[0123]") - MESSAGE(STATUS "Disabling package test on OSX < 10.4") - MESSAGE(STATUS "OSX_VERSION='${OSX_VERSION}'") - SET(PACKAGE_TARGET) - ENDIF("${OSX_VERSION}" MATCHES "^10\\.[0123]" OR "${OSX_VERSION}" MATCHES "ProductVersion:\t10\\.[0123]") -ENDIF(APPLE AND NOT CTEST_TEST_CPACK) +IF(CTEST_TEST_CPACK) + SET(PACKAGE_TARGET --build-target package) +ELSE(CTEST_TEST_CPACK) + SET(PACKAGE_TARGET) +ENDIF(CTEST_TEST_CPACK) ADD_CUSTOM_COMMAND( TARGET ${install_target} |