diff options
Diffstat (limited to 'Source')
-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 |
7 files changed, 1327 insertions, 142 deletions
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; }; |