From 1105a86c520d34a59b482b86b6f288c11ff45b81 Mon Sep 17 00:00:00 2001 From: David Cole Date: Tue, 17 Jun 2008 11:39:26 -0400 Subject: ENH: Add patch for feature request #6847 - CPack components for NSIS and PackageMaker installers. Thanks to Doug Gregor for all the hard work involved with implementing this patch! Also added new test CPackComponents that is conditionally executed only when NSIS or PackageMaker installer builders are available. --- Modules/CPack.Info.plist.in | 2 +- Modules/CPack.distribution.dist.in | 6 + Modules/NSIS.template.in | 232 +++++++++--- Source/CPack/cmCPackComponentGroup.h | 117 ++++++ Source/CPack/cmCPackGenerator.cxx | 403 ++++++++++++++++---- Source/CPack/cmCPackGenerator.h | 14 + Source/CPack/cmCPackNSISGenerator.cxx | 329 +++++++++++++++- Source/CPack/cmCPackNSISGenerator.h | 27 ++ Source/CPack/cmCPackPackageMakerGenerator.cxx | 518 +++++++++++++++++++++++--- Source/CPack/cmCPackPackageMakerGenerator.h | 61 ++- Tests/CMakeLists.txt | 45 ++- Tests/CPackComponents/CMakeLists.txt | 92 +++++ Tests/CPackComponents/VerifyResult.cmake | 48 +++ Tests/CPackComponents/mylib.cpp | 7 + Tests/CPackComponents/mylib.h | 1 + Tests/CPackComponents/mylibapp.cpp | 6 + Tests/SimpleInstall/CMakeLists.txt | 26 +- Tests/SimpleInstallS2/CMakeLists.txt | 26 +- 18 files changed, 1724 insertions(+), 236 deletions(-) create mode 100644 Modules/CPack.distribution.dist.in create mode 100644 Source/CPack/cmCPackComponentGroup.h create mode 100644 Tests/CPackComponents/CMakeLists.txt create mode 100644 Tests/CPackComponents/VerifyResult.cmake create mode 100644 Tests/CPackComponents/mylib.cpp create mode 100644 Tests/CPackComponents/mylib.h create mode 100644 Tests/CPackComponents/mylibapp.cpp 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 @@ IFPkgFormatVersion 0.10000000149011612 CFBundleIdentifier -com.@CPACK_PACKAGE_VENDOR@.@CPACK_PACKAGE_NAME@.@CPACK_PACKAGE_VERSION@ +com.@CPACK_PACKAGE_VENDOR@.@CPACK_PACKAGE_NAME@.@CPACK_PACKAGE_VERSION@@CPACK_MODULE_VERSION_SUFFIX@ 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 @@ + + + @CPACK_PACKAGE_NAME@ + + @CPACK_PACKAGEMAKER_CHOICES@ + 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 +#include +#include + +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 InstallationTypes; + + /// The components that this component depends on. + std::vector Dependencies; + + /// The components that depend on this component. + std::vector ReverseDependencies; + + /// The list of installed files that are part of this component. + std::vector Files; + + /// The list of installed directories that are part of this component. + std::vector 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 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 #include @@ -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 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 installTypesVector; + cmSystemTools::ExpandListArgument(installTypes, installTypesVector); + std::vector::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::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 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::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 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 files = gl.GetFiles(); + + // For component installations, determine which files go into which + // components. + if (!this->Components.empty()) + { + std::vector::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 installTypesVector; + cmSystemTools::ExpandListArgument(installTypes, installTypesVector); + std::vector::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 dependsVector; + cmSystemTools::ExpandListArgument(depends, dependsVector); + std::vector::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 +#include #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 InstallationTypes; + std::map Components; + std::map 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 #include @@ -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 + installTypes(this->InstallationTypes.size()); + std::map::iterator installTypeIt; + for (installTypeIt = this->InstallationTypes.begin(); + installTypeIt != this->InstallationTypes.end(); + ++installTypeIt) + { + installTypes[installTypeIt->second.Index-1] = &installTypeIt->second; + } + std::vector::iterator installTypeIt2; + for (installTypeIt2 = installTypes.begin(); + installTypeIt2 != installTypes.end(); + ++installTypeIt2) + { + installTypesCode += "InstType \""; + installTypesCode += (*installTypeIt2)->DisplayName; + installTypesCode += + "\"\n"; + } + + // Create installation groups first + std::map::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::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::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::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::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 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& visited) const +{ + // Don't visit a component twice + if (visited.count(component)) + { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + std::vector::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& visited) const +{ + // Don't visit a component twice + if (visited.count(component)) + { + return std::string(); + } + visited.insert(component); + + std::ostringstream out; + std::vector::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::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 /** \class cmCPackNSISGenerator * \brief A generator for NSIS files @@ -48,6 +49,32 @@ protected: bool GetListOfSubdirectories(const char* dir, std::vector& 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& 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& 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 @@ -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::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 << "" << std::endl + << "" << std::endl + << "" << std::endl + << "" << std::endl + << " IFPkgDescriptionTitle" << std::endl + << " " << component.DisplayName << "" << std::endl + << " IFPkgDescriptionVersion" << std::endl + << " " << this->GetOption("CPACK_PACKAGE_VERSION") + << "" << std::endl + << " IFPkgDescriptionDescription" << std::endl + << " " + this->EscapeForXML(component.Description) + << "" << std::endl + << "" << std::endl + << "" << 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 << "" << std::endl; + + // Emit the outline for the groups + std::map::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::iterator compIt; + for (compIt = this->Components.begin(); compIt != this->Components.end(); + ++compIt) + { + if (!compIt->second.Group) + { + choiceOut << "first << "Choice\">" + << std::endl; + } + } + choiceOut << "" << 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 << "" << std::endl; + std::vector::const_iterator compIt; + for (compIt = group.Components.begin(); compIt != group.Components.end(); + ++compIt) + { + out << " Name << "Choice\">" + << std::endl; + } + out << "" << std::endl; +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponentGroup& group, + cmOStringStream& out) +{ + out << "" << 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 << "" << std::endl; + out << " " << std::endl; + out << "" << 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(""); + 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(""); + 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 << "GetOption("CPACK_PACKAGE_VERSION") << "\" " + << "installKBytes=\"" << installedSize << "\" " + << "auth=\"Admin\" onConclusion=\"None\">" + << "file:./" << relativePackageLocation << "" << std::endl; +} + +//---------------------------------------------------------------------- +void +cmCPackPackageMakerGenerator:: +AddDependencyAttributes(const cmCPackComponent& component, cmOStringStream& out) +{ + std::vector::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::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} -- cgit v0.12