From 71f61636b6abfe5f243374b5575019098418b4c6 Mon Sep 17 00:00:00 2001 From: David Cole Date: Wed, 9 Jul 2008 13:38:56 -0400 Subject: [PATCH] ENH: One more patch from Doug Gregor including PackageMaker functionality for componentized-for-the-end-user and download-some-bit-on-demand installers. --- Modules/CPack.Info.plist.in | 2 +- Modules/CPack.cmake | 62 ++-- Source/CMakeLists.txt | 1 + Source/CPack/cmCPackComponentGroup.cxx | 49 +++ Source/CPack/cmCPackComponentGroup.h | 14 +- Source/CPack/cmCPackPackageMakerGenerator.cxx | 285 ++++++++++++------ Source/CPack/cmCPackPackageMakerGenerator.h | 1 + 7 files changed, 295 insertions(+), 119 deletions(-) create mode 100644 Source/CPack/cmCPackComponentGroup.cxx diff --git a/Modules/CPack.Info.plist.in b/Modules/CPack.Info.plist.in index 8971d6e81..6e325001c 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@@CPACK_MODULE_VERSION_SUFFIX@ +com.@CPACK_PACKAGE_VENDOR@.@CPACK_PACKAGE_NAME@@CPACK_MODULE_VERSION_SUFFIX@ diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake index c8f702913..75e0c2057 100644 --- a/Modules/CPack.cmake +++ b/Modules/CPack.cmake @@ -107,7 +107,7 @@ # that won't be packaged when building a source package. This is a # list of patterns, e.g., /CVS/;/\\.svn/;\\.swp$;\\.#;/#;.*~;cscope.* # -# The following variables are specific to the graphical installers build +# The following variables are specific to the graphical installers built # on Windows using the Nullsoft Installation System. # # CPACK_PACKAGE_INSTALL_REGISTRY_KEY - Registry key used when @@ -157,6 +157,22 @@ # CPACK_NSIS_DELETE_ICONS_EXTRA -Additional NSIS commands to # uninstall start menu shortcuts. # +# The following variable is specific to installers build on Mac OS X +# using PackageMaker: +# +# CPACK_OSX_PACKAGE_VERSION - The version of Mac OS X that the +# resulting PackageMaker archive should be compatible +# with. Different versions of Mac OS X support different +# features. For example, CPack can only build component-based +# installers for Mac OS X 10.4 or newer, and can only build +# installers that download component son-the-fly for Mac OS X 10.5 +# or newer. If left blank, this value will be set to the minimum +# version of Mac OS X that supports the requested features. Set this +# variable to some value (e.g., 10.4) only if you want to guarantee +# that your installer will work on that version of Mac OS X, and +# don't mind missing extra features available in the installer +# shipping with later versions of Mac OS X. +# # The following variables are for advanced uses of CPack: # # CPACK_CMAKE_GENERATOR - What CMake generator should be used if the @@ -251,9 +267,9 @@ # installer itself. For more information, see the cpack_configure_downloads # command. # -# ARCHIVE_FILE provides a name for the ZIP file created by CPack to -# be used for downloaded components. If not supplied, CPack will -# create ZIP file with some name based on CPACK_PACKAGE_FILE_NAME and +# ARCHIVE_FILE provides a name for the archive file created by CPack +# to be used for downloaded components. If not supplied, CPack will +# create a file with some name based on CPACK_PACKAGE_FILE_NAME and # the name of the component. See cpack_configure_downloads for more # information. # @@ -330,30 +346,34 @@ # # The cpack_configure_downloads command configures installation-time # downloads of selected components. For each downloadable component, -# CPack will create a ZIP file containing the contents of that component, -# which should be uploaded to the given site. When the user selects that -# component for installation, the installer will download and extract -# the component in place. This feature is useful for creating small -# installers that only download the requested components, saving -# bandwidth. Additionally, the installers are small enough that they -# will be installed as part of the normal installation process, and the -# "Change" button in Windows Add/Remove Programs control panel will -# allow one to add or remove parts of the application after the original -# installation. The downloaded-components functionality is currently -# only available with the NSIS generator on Windows. It requires the -# ZipDLL plug-in for NSIS, available at: +# CPack will create an archive containing the contents of that +# component, which should be uploaded to the given site. When the +# user selects that component for installation, the installer will +# download and extract the component in place. This feature is +# useful for creating small installers that only download the +# requested components, saving bandwidth. Additionally, the +# installers are small enough that they will be installed as part of +# the normal installation process, and the "Change" button in +# Windows Add/Remove Programs control panel will allow one to add or +# remove parts of the application after the original +# installation. On Windows, the downloaded-components functionality +# requires the ZipDLL plug-in for NSIS, available at: # # http://nsis.sourceforge.net/ZipDLL_plug-in # -# The site argument is a URL where the ZIP files for downloadable +# On Mac OS X, installers that download components on-the-fly can +# only be built and installed on system using Mac OS X 10.5 or +# later. +# +# The site argument is a URL where the archives for downloadable # components will reside, e.g., http://www.cmake.org/files/2.6.1/installer/ -# All of the ZIP files produced by CPack should be uploaded to that location. +# All of the archives produced by CPack should be uploaded to that location. # # UPLOAD_DIRECTORY is the local directory where CPack will create the -# various .ZIP archives for each of the components. The contents of this +# various archives for each of the components. The contents of this # directory should be uploaded to a location accessible by the URL given # in the site argument. If omitted, CPack will use the directory CPackUploads -# inside the CMake binary directory to store the generated ZIP files. +# inside the CMake binary directory to store the generated archives. # # The ALL flag indicates that all components be downloaded. Otherwise, only # those components explicitly marked as DOWNLOADED or that have a specified @@ -363,7 +383,7 @@ # ADD_REMOVE indicates that CPack should install a copy of the installer # that can be called from Windows' Add/Remove Programs dialog (via the # "Modify" button) to change the set of installed components. NO_ADD_REMOVE -# turns off this behavior. +# turns off this behavior. This option is ignored on Mac OS X. # Pick a configuration file diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index b4d1eb1c1..516e20809 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -349,6 +349,7 @@ TARGET_LINK_LIBRARIES(CTestLib CMakeLib ${CMAKE_CURL_LIBRARIES} ${CMAKE_XMLRPC_L # Sources for CPack # SET(CPACK_SRCS + CPack/cmCPackComponentGroup.cxx CPack/cmCPackGeneratorFactory.cxx CPack/cmCPackGenerator.cxx CPack/cmCPackLog.cxx diff --git a/Source/CPack/cmCPackComponentGroup.cxx b/Source/CPack/cmCPackComponentGroup.cxx new file mode 100644 index 000000000..63ad9d791 --- /dev/null +++ b/Source/CPack/cmCPackComponentGroup.cxx @@ -0,0 +1,49 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. 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. + +=========================================================================*/ + +#include "cmCPackComponentGroup.h" +#include "cmSystemTools.h" +#include +#include + +//---------------------------------------------------------------------- +unsigned long cmCPackComponent::GetInstalledSize(const char* installDir) const +{ + if (this->TotalSize != 0) + { + return this->TotalSize; + } + + std::vector::const_iterator fileIt; + for (fileIt = this->Files.begin(); fileIt != this->Files.end(); ++fileIt) + { + std::string path = installDir; + path += '/'; + path += *fileIt; + this->TotalSize += cmSystemTools::FileLength(path.c_str()); + } + + return this->TotalSize; +} + +//---------------------------------------------------------------------- +unsigned long +cmCPackComponent::GetInstalledSizeInKbytes(const char* installDir) const +{ + unsigned long result = (GetInstalledSize(installDir) + 512) / 1024; + return result? result : 1; +} diff --git a/Source/CPack/cmCPackComponentGroup.h b/Source/CPack/cmCPackComponentGroup.h index d080a8776..e17b1b750 100644 --- a/Source/CPack/cmCPackComponentGroup.h +++ b/Source/CPack/cmCPackComponentGroup.h @@ -49,7 +49,7 @@ public: class cmCPackComponent { public: - cmCPackComponent() : Group(0) { } + cmCPackComponent() : Group(0), TotalSize(0) { } /// The name of the component (used to reference the component). std::string Name; @@ -95,6 +95,18 @@ public: /// The list of installed directories that are part of this component. std::vector Directories; + + /// Get the total installed size of all of the files in this + /// component, in bytes. installDir is the directory into which the + /// component was installed. + unsigned long GetInstalledSize(const char* installDir) const; + + /// Identical to GetInstalledSize, but returns the result in + /// kilobytes. + unsigned long GetInstalledSizeInKbytes(const char* installDir) const; + + private: + mutable unsigned long TotalSize; }; /** \class cmCPackComponentGroup diff --git a/Source/CPack/cmCPackPackageMakerGenerator.cxx b/Source/CPack/cmCPackPackageMakerGenerator.cxx index a156971f4..d35e76467 100644 --- a/Source/CPack/cmCPackPackageMakerGenerator.cxx +++ b/Source/CPack/cmCPackPackageMakerGenerator.cxx @@ -32,6 +32,7 @@ cmCPackPackageMakerGenerator::cmCPackPackageMakerGenerator() { this->PackageMakerVersion = 0.0; + this->PackageCompatibilityVersion = 10.4; } //---------------------------------------------------------------------- @@ -42,7 +43,7 @@ cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator() //---------------------------------------------------------------------- bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const { - return true; + return this->PackageCompatibilityVersion >= 10.4; } //---------------------------------------------------------------------- @@ -165,7 +166,7 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName, if (!this->Components.empty()) { - // Create the directory where component packages will be installed. + // Create the directory where component packages will be built. std::string basePackageDir = packageDirFileName; basePackageDir += "/Contents/Packages"; if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str())) @@ -176,12 +177,78 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName, return 0; } + // Create the directory where downloaded component packages will + // be placed. + const char* userUploadDirectory = this->GetOption("CPACK_UPLOAD_DIRECTORY"); + std::string uploadDirectory; + if (userUploadDirectory && *userUploadDirectory) + { + uploadDirectory = userUploadDirectory; + } + else + { + uploadDirectory= this->GetOption("CPACK_PACKAGE_DIRECTORY"); + uploadDirectory += "/CPackUploads"; + } + // Create packages for each component + bool warnedAboutDownloadCompatibility = false; + std::map::iterator compIt; for (compIt = this->Components.begin(); compIt != this->Components.end(); ++compIt) { - std::string packageFile = basePackageDir; + std::string packageFile; + if (compIt->second.IsDownloaded) + { + if (this->PackageCompatibilityVersion >= 10.5 && + this->PackageMakerVersion >= 3.0) + { + // Build this package within the upload directory. + packageFile = uploadDirectory; + + if(!cmSystemTools::FileExists(uploadDirectory.c_str())) + { + if (!cmSystemTools::MakeDirectory(uploadDirectory.c_str())) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Unable to create package upload directory " + << uploadDirectory << std::endl); + return 0; + } + } + } + else if (!warnedAboutDownloadCompatibility) + { + if (this->PackageCompatibilityVersion < 10.5) + { + cmCPackLogger(cmCPackLog::LOG_WARNING, + "CPack warning: please set CPACK_OSX_PACKAGE_VERSION to 10.5 or greater enable downloaded packages. CPack will build a non-downloaded package." + << std::endl); + } + + if (this->PackageMakerVersion < 3) + { + cmCPackLogger(cmCPackLog::LOG_WARNING, + "CPack warning: unable to build downloaded packages with PackageMaker versions prior to 3.0. CPack will build a non-downloaded package." + << std::endl); + } + + warnedAboutDownloadCompatibility = true; + } + } + + if (packageFile.empty()) + { + // Build this package within the overall distribution + // metapackage. + packageFile = basePackageDir; + + // We're not downloading this component, even if the user + // requested it. + compIt->second.IsDownloaded = false; + } + packageFile += '/'; packageFile += GetPackageName(compIt->second); @@ -229,7 +296,7 @@ int cmCPackPackageMakerGenerator::CompressFiles(const char* outFileName, << "/Resources\" -i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Info.plist\" -d \"" - << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") + << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/Description.plist\""; if ( this->PackageMakerVersion > 2.0 ) { @@ -339,6 +406,30 @@ int cmCPackPackageMakerGenerator::InitializeInternal() cmCPackLogger(cmCPackLog::LOG_DEBUG, "PackageMaker version is: " << this->PackageMakerVersion << std::endl); + // Determine the package compatibility version. If it wasn't + // specified by the user, we define it based on which features the + // user requested. + const char *packageCompat = this->GetOption("CPACK_OSX_PACKAGE_VERSION"); + if (packageCompat && *packageCompat) + { + this->PackageCompatibilityVersion = atof(packageCompat); + } + else if (this->GetOption("CPACK_DOWNLOAD_SITE")) + { + this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.5"); + this->PackageCompatibilityVersion = 10.5; + } + else if (this->GetOption("CPACK_COMPONENTS_ALL")) + { + this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.4"); + this->PackageCompatibilityVersion = 10.4; + } + else + { + this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.3"); + this->PackageCompatibilityVersion = 10.3; + } + pkgPath += "/MacOS"; path.push_back(pkgPath); pkgPath = cmSystemTools::FindProgram("PackageMaker", path, false); @@ -491,12 +582,19 @@ bool cmCPackPackageMakerGenerator::RunPackageMaker(const char *command, 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(); + if (component.ArchiveFile.empty()) + { + std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + packagesDir += ".dummy"; + cmOStringStream out; + out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) + << "-" << component.Name << ".pkg"; + return out.str(); + } + else + { + return component.ArchiveFile + ".pkg"; + } } //---------------------------------------------------------------------- @@ -509,46 +607,74 @@ GenerateComponentPackage(const char *packageFile, 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(); + // The command that will be used to run PackageMaker + cmOStringStream pkgCmd; - // 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())) + if (this->PackageCompatibilityVersion < 10.5 || + this->PackageMakerVersion < 3.0) { - return false; + // Create Description.plist and Info.plist files for normal Mac OS + // X packages, which work on Mac OS X 10.3 and newer. + 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; + } + + pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") + << "\" -build -p \"" << packageFile << "\"" + << " -f \"" << packageDir << "\"" + << " -i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") + << "/" << infoFileName << "\"" + << " -d \"" << descriptionFile << "\""; + } + else + { + // Create a "flat" package on Mac OS X 10.5 and newer. Flat + // packages are stored in a single file, rather than a directory + // like normal packages, and can be downloaded by the installer + // on-the-fly in Mac OS X 10.5 or newer. Thus, we need to create + // flat packages when the packages will be downloaded on the fly. + std::string pkgId = "com."; + pkgId += this->GetOption("CPACK_PACKAGE_VENDOR"); + pkgId += '.'; + pkgId += this->GetOption("CPACK_PACKAGE_NAME"); + pkgId += '.'; + pkgId += component.Name; + + pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") + << "\" --root \"" << packageDir << "\"" + << " --id " << pkgId + << " --target " << this->GetOption("CPACK_OSX_PACKAGE_VERSION") + << " --out \"" << packageFile << "\""; } // 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); } @@ -670,8 +796,6 @@ cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponent& component, packageId += '.'; packageId += this->GetOption("CPACK_PACKAGE_NAME"); packageId += '.'; - packageId += this->GetOption("CPACK_PACKAGE_VERSION"); - packageId += '.'; packageId += component.Name; out << "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; - } - } - + // Determine the installed size of the package. + std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + dirName += '/'; + dirName += component.Name; + unsigned long installedSize + = component.GetInstalledSizeInKbytes(dirName.c_str()); out << "GetOption("CPACK_PACKAGE_VERSION") << "\" " << "installKBytes=\"" << installedSize << "\" " - << "auth=\"Admin\" onConclusion=\"None\">" - << "file:./" << relativePackageLocation << "" << std::endl; + << "auth=\"Admin\" onConclusion=\"None\">"; + if (component.IsDownloaded) + { + out << this->GetOption("CPACK_DOWNLOAD_SITE") + << this->GetPackageName(component); + } + else + { + out << "file:./" << relativePackageLocation; + } + out << "" << std::endl; } //---------------------------------------------------------------------- diff --git a/Source/CPack/cmCPackPackageMakerGenerator.h b/Source/CPack/cmCPackPackageMakerGenerator.h index 37f8f0e01..228e099ce 100644 --- a/Source/CPack/cmCPackPackageMakerGenerator.h +++ b/Source/CPack/cmCPackPackageMakerGenerator.h @@ -116,6 +116,7 @@ protected: std::string EscapeForXML(std::string str); double PackageMakerVersion; + double PackageCompatibilityVersion; }; #endif