From 2e3c67d1b636f752eea35ef407c5243a79742d63 Mon Sep 17 00:00:00 2001 From: Clinton Stimpson Date: Sat, 2 Nov 2013 10:24:53 -0600 Subject: [PATCH] productbuild: Add new productbuild cpack generator. This cpack generator basically replaces the obsolete PackageMaker generator. --- Help/manual/cmake-modules.7.rst | 1 + Help/module/CPackProductBuild.rst | 1 + Modules/CPack.cmake | 3 + Modules/CPackProductBuild.cmake | 38 +++ Source/CMakeLists.txt | 1 + Source/CPack/cmCPackGeneratorFactory.cxx | 5 + Source/CPack/cmCPackPKGGenerator.cxx | 1 - Source/CPack/cmCPackPackageMakerGenerator.cxx | 2 + Source/CPack/cmCPackProductBuildGenerator.cxx | 260 ++++++++++++++++++ Source/CPack/cmCPackProductBuildGenerator.h | 60 ++++ Tests/CMakeLists.txt | 11 + 11 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 Help/module/CPackProductBuild.rst create mode 100644 Modules/CPackProductBuild.cmake create mode 100644 Source/CPack/cmCPackProductBuildGenerator.cxx create mode 100644 Source/CPack/cmCPackProductBuildGenerator.h diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst index 62910cf5a..f91166299 100644 --- a/Help/manual/cmake-modules.7.rst +++ b/Help/manual/cmake-modules.7.rst @@ -60,6 +60,7 @@ All Modules /module/CPackIFW /module/CPackNSIS /module/CPackPackageMaker + /module/CPackProductBuild /module/CPackRPM /module/CPack /module/CPackWIX diff --git a/Help/module/CPackProductBuild.rst b/Help/module/CPackProductBuild.rst new file mode 100644 index 000000000..6081fe48a --- /dev/null +++ b/Help/module/CPackProductBuild.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../Modules/CPackProductBuild.cmake diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake index 77f854d5e..4d51a3ee3 100644 --- a/Modules/CPack.cmake +++ b/Modules/CPack.cmake @@ -455,6 +455,7 @@ if(NOT CPACK_GENERATOR) option(CPACK_BINARY_DRAGNDROP "Enable to build OSX Drag And Drop package" OFF) option(CPACK_BINARY_OSXX11 "Enable to build OSX X11 packages" OFF) option(CPACK_BINARY_PACKAGEMAKER "Enable to build PackageMaker packages" OFF) + option(CPACK_BINARY_PRODUCTBUILD "Enable to build productbuild packages" OFF) else() option(CPACK_BINARY_TZ "Enable to build TZ packages" ON) endif() @@ -483,6 +484,7 @@ if(NOT CPACK_GENERATOR) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_NSIS NSIS) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_OSXX11 OSXX11) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_PACKAGEMAKER PackageMaker) + cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_PRODUCTBUILD productbuild) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_RPM RPM) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_STGZ STGZ) cpack_optional_append(CPACK_GENERATOR CPACK_BINARY_TBZ2 TBZ2) @@ -531,6 +533,7 @@ mark_as_advanced( CPACK_BINARY_NSIS CPACK_BINARY_OSXX11 CPACK_BINARY_PACKAGEMAKER + CPACK_BINARY_PRODUCTBUILD CPACK_BINARY_RPM CPACK_BINARY_STGZ CPACK_BINARY_TBZ2 diff --git a/Modules/CPackProductBuild.cmake b/Modules/CPackProductBuild.cmake new file mode 100644 index 000000000..6545a3a8e --- /dev/null +++ b/Modules/CPackProductBuild.cmake @@ -0,0 +1,38 @@ +#.rst: +# CPackProductBuild +# ----------------- +# +# productbuild CPack generator (Mac OS X). +# +# Variables specific to CPack productbuild generator +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# The following variable is specific to installers built on Mac +# OS X using productbuild: +# +# .. variable:: CPACK_COMMAND_PRODUCTBUILD +# +# Path to the productbuild(1) command used to generate a product archive for +# the OS X Installer or Mac App Store. This variable can be used to override +# the automatically detected command (or specify its location if the +# auto-detection fails to find it.) +# +# .. variable:: CPACK_COMMAND_PKGBUILD +# +# Path to the pkgbuild(1) command used to generate an OS X component package +# on OS X. This variable can be used to override the automatically detected +# command (or specify its location if the auto-detection fails to find it.) +# + +#============================================================================= +# Copyright 2006-2012 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f85f14326..7cf11caa2 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -719,6 +719,7 @@ if(APPLE) CPack/cmCPackOSXX11Generator.cxx CPack/cmCPackPKGGenerator.cxx CPack/cmCPackPackageMakerGenerator.cxx + CPack/cmCPackProductBuildGenerator.cxx ) endif() diff --git a/Source/CPack/cmCPackGeneratorFactory.cxx b/Source/CPack/cmCPackGeneratorFactory.cxx index b17f52ef5..cf072bbe6 100644 --- a/Source/CPack/cmCPackGeneratorFactory.cxx +++ b/Source/CPack/cmCPackGeneratorFactory.cxx @@ -28,6 +28,7 @@ #include "cmCPackDragNDropGenerator.h" #include "cmCPackOSXX11Generator.h" #include "cmCPackPackageMakerGenerator.h" +#include "cmCPackProductBuildGenerator.h" #endif #ifdef __CYGWIN__ @@ -122,6 +123,10 @@ cmCPackGeneratorFactory::cmCPackGeneratorFactory() this->RegisterGenerator("OSXX11", "Mac OSX X11 bundle", cmCPackOSXX11Generator::CreateGenerator); } + if (cmCPackProductBuildGenerator::CanGenerate()) { + this->RegisterGenerator("productbuild", "Mac OSX pkg", + cmCPackProductBuildGenerator::CreateGenerator); + } #endif #if !defined(_WIN32) && !defined(__QNXNTO__) && !defined(__BEOS__) && \ !defined(__HAIKU__) diff --git a/Source/CPack/cmCPackPKGGenerator.cxx b/Source/CPack/cmCPackPKGGenerator.cxx index 8282714a7..ff0fa8cc7 100644 --- a/Source/CPack/cmCPackPKGGenerator.cxx +++ b/Source/CPack/cmCPackPKGGenerator.cxx @@ -41,7 +41,6 @@ int cmCPackPKGGenerator::InitializeInternal() { cmCPackLogger(cmCPackLog::LOG_DEBUG, "cmCPackPKGGenerator::Initialize()" << std::endl); - this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr"); return this->Superclass::InitializeInternal(); } diff --git a/Source/CPack/cmCPackPackageMakerGenerator.cxx b/Source/CPack/cmCPackPackageMakerGenerator.cxx index e50c2e681..5bb24c0ba 100644 --- a/Source/CPack/cmCPackPackageMakerGenerator.cxx +++ b/Source/CPack/cmCPackPackageMakerGenerator.cxx @@ -329,6 +329,8 @@ int cmCPackPackageMakerGenerator::PackageFiles() int cmCPackPackageMakerGenerator::InitializeInternal() { + this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr"); + // Starting with Xcode 4.3, PackageMaker is a separate app, and you // can put it anywhere you want. So... use a variable for its location. // People who put it in unexpected places can use the variable to tell diff --git a/Source/CPack/cmCPackProductBuildGenerator.cxx b/Source/CPack/cmCPackProductBuildGenerator.cxx new file mode 100644 index 000000000..c1f63cc23 --- /dev/null +++ b/Source/CPack/cmCPackProductBuildGenerator.cxx @@ -0,0 +1,260 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "cmCPackProductBuildGenerator.h" + +#include "cmake.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmSystemTools.h" +#include "cmMakefile.h" +#include "cmGeneratedFileStream.h" +#include "cmCPackComponentGroup.h" +#include "cmCPackLog.h" + +#include +#include + +cmCPackProductBuildGenerator::cmCPackProductBuildGenerator() +{ + this->componentPackageMethod = ONE_PACKAGE; +} + +cmCPackProductBuildGenerator::~cmCPackProductBuildGenerator() +{ +} + +int cmCPackProductBuildGenerator::PackageFiles() +{ + // TODO: Use toplevel + // It is used! Is this an obsolete comment? + + std::string packageDirFileName + = this->GetOption("CPACK_TEMPORARY_DIRECTORY"); + + // Create the directory where component packages will be built. + std::string basePackageDir = packageDirFileName; + basePackageDir += "/Contents/Packages"; + if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str())) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem creating component packages directory: " + << basePackageDir.c_str() << std::endl); + return 0; + } + + if (!this->Components.empty()) + { + std::map::iterator compIt; + for (compIt = this->Components.begin(); compIt != this->Components.end(); + ++compIt) + { + std::string packageDir = toplevel; + packageDir += '/'; + packageDir += compIt->first; + if (!this->GenerateComponentPackage(basePackageDir, + GetPackageName(compIt->second), + packageDir, + &compIt->second)) + { + return 0; + } + } + } + else + { + if(!this->GenerateComponentPackage(basePackageDir, + this->GetOption("CPACK_PACKAGE_NAME"), + toplevel, NULL)) + { + return 0; + } + } + + // Copy or create all of the resource files we need. + std::string resDir = packageDirFileName + "/Contents"; + if ( !this->CopyCreateResourceFile("License", resDir.c_str()) + || !this->CopyCreateResourceFile("ReadMe", resDir.c_str()) + || !this->CopyCreateResourceFile("Welcome", resDir.c_str())) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem copying the resource files" + << std::endl); + return 0; + } + + // combine package(s) into a distribution + WriteDistributionFile(packageDirFileName.c_str()); + std::ostringstream pkgCmd; + + std::string version = this->GetOption("CPACK_PACKAGE_VERSION"); + std::string productbuild = this->GetOption("CPACK_COMMAND_PRODUCTBUILD"); + + pkgCmd << productbuild + << " --distribution \"" << packageDirFileName + << "/Contents/distribution.dist\"" + << " --package-path \"" << packageDirFileName << "/Contents/Packages" << "\"" + << " --resources \"" << resDir << "\"" + << " --version \"" << version << "\"" + << " \"" << packageFileNames[0] << "\""; + + // Run ProductBuild + return RunProductBuild(pkgCmd.str()); +} + +int cmCPackProductBuildGenerator::InitializeInternal() +{ + this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/Applications"); + + std::vector no_paths; + std::string program = + cmSystemTools::FindProgram("pkgbuild", no_paths, false); + if (program.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find pkgbuild executable" + << std::endl); + return 0; + } + this->SetOptionIfNotSet("CPACK_COMMAND_PKGBUILD", program.c_str()); + + + program = cmSystemTools::FindProgram("productbuild", no_paths, false); + if (program.empty()) { + cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find productbuild executable" + << std::endl); + return 0; + } + this->SetOptionIfNotSet("CPACK_COMMAND_PRODUCTBUILD", program.c_str()); + + return this->Superclass::InitializeInternal(); +} + + +bool cmCPackProductBuildGenerator::RunProductBuild( + const std::string& command) +{ + std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); + tmpFile += "/ProductBuildOutput.log"; + + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl); + std::string output, error_output; + int retVal = 1; + bool res = cmSystemTools::RunSingleCommand(command.c_str(), + &output, &error_output, &retVal, 0, this->GeneratorVerbose, 0); + cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running command" + << 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 command: " << command + << std::endl << "Please check " << tmpFile.c_str() << " for errors" + << std::endl); + return false; + } + return true; +} + +bool cmCPackProductBuildGenerator::GenerateComponentPackage( + const std::string& packageFileDir, + const std::string& packageFileName, + const std::string& packageDir, + const cmCPackComponent* component) +{ + std::string packageFile = packageFileDir; + packageFile += '/'; + packageFile += packageFileName; + + cmCPackLogger(cmCPackLog::LOG_OUTPUT, + "- Building component package: " << + packageFile << std::endl); + + const char* comp_name = component ? component->Name.c_str() : NULL; + + const char* preflight = this->GetComponentScript("PREFLIGHT", comp_name); + const char* postflight = this->GetComponentScript("POSTFLIGHT", comp_name); + + std::string resDir = packageFileDir; + if(component) + { + resDir += "/"; + resDir += component->Name; + } + std::string scriptDir = resDir + "/scripts"; + + if ( !cmsys::SystemTools::MakeDirectory(scriptDir.c_str())) + { + cmCPackLogger(cmCPackLog::LOG_ERROR, + "Problem creating installer directory: " + << scriptDir.c_str() << std::endl); + return 0; + } + + // if preflight, postflight, or postupgrade are set + // then copy them into the script directory and make + // them executable + if(preflight) + { + this->CopyInstallScript(scriptDir.c_str(), + preflight, + "preinstall"); + } + if(postflight) + { + this->CopyInstallScript(scriptDir.c_str(), + postflight, + "postinstall"); + } + + + // The command that will be used to run ProductBuild + std::ostringstream pkgCmd; + + std::string pkgId = "com."; + pkgId += this->GetOption("CPACK_PACKAGE_VENDOR"); + pkgId += '.'; + pkgId += this->GetOption("CPACK_PACKAGE_NAME"); + if(component) + { + pkgId += '.'; + pkgId += component->Name; + } + + std::string version = this->GetOption("CPACK_PACKAGE_VERSION"); + std::string pkgbuild = this->GetOption("CPACK_COMMAND_PKGBUILD"); + + pkgCmd << pkgbuild + << " --root \"" << packageDir << "\"" + << " --identifier \"" << pkgId << "\"" + << " --scripts \"" << scriptDir << "\"" + << " --version \"" << version << "\"" + << " --install-location \"/\"" + << " \"" << packageFile << "\""; + + // Run ProductBuild + return RunProductBuild(pkgCmd.str()); +} + +const char* cmCPackProductBuildGenerator::GetComponentScript( + const char* script, + const char* component_name) +{ + std::string scriptname = std::string("CPACK_") + script + "_"; + if(component_name) + { + scriptname += cmSystemTools::UpperCase(component_name); + scriptname += "_"; + } + scriptname += "SCRIPT"; + + return this->GetOption(scriptname); +} diff --git a/Source/CPack/cmCPackProductBuildGenerator.h b/Source/CPack/cmCPackProductBuildGenerator.h new file mode 100644 index 000000000..0740f8933 --- /dev/null +++ b/Source/CPack/cmCPackProductBuildGenerator.h @@ -0,0 +1,60 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 Kitware, Inc. + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ + +#ifndef cmCPackProductBuildGenerator_h +#define cmCPackProductBuildGenerator_h + + +#include "cmCPackPKGGenerator.h" + +class cmCPackComponent; + +/** \class cmCPackProductBuildGenerator + * \brief A generator for ProductBuild files + * + */ +class cmCPackProductBuildGenerator : public cmCPackPKGGenerator +{ +public: + cmCPackTypeMacro(cmCPackProductBuildGenerator, cmCPackPKGGenerator); + + /** + * Construct generator + */ + cmCPackProductBuildGenerator(); + virtual ~cmCPackProductBuildGenerator(); + +protected: + virtual int InitializeInternal(); + int PackageFiles(); + virtual const char* GetOutputExtension() { return ".pkg"; } + + // Run ProductBuild with the given command line, which will (if + // successful) produce the given package file. Returns true if + // ProductBuild succeeds, false otherwise. + bool RunProductBuild(const std::string& command); + + // 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 std::string& packageFileDir, + const std::string& packageFileName, + const std::string& packageDir, + const cmCPackComponent* component); + + const char* GetComponentScript(const char* script, + const char* script_component); + +}; + +#endif diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 2db5deda3..bffa4b7f8 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -951,6 +951,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release set(CPackComponents_BUILD_OPTIONS) if(APPLE) set(CPackComponents_BUILD_OPTIONS -DCPACK_BINARY_DRAGNDROP:BOOL=ON) + if(CMake_TEST_XCODE_VERSION VERSION_GREATER "4.6") + set(CPackComponents_BUILD_OPTIONS ${CPackComponents_BUILD_OPTIONS} + -DCPACK_BINARY_PRODUCTBUILD:BOOL=ON) + endif() endif() if(NSIS_MAKENSIS_EXECUTABLE) set(CPackComponents_BUILD_OPTIONS ${CPackComponents_BUILD_OPTIONS} @@ -995,6 +999,9 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release list(APPEND ACTIVE_CPACK_GENERATORS "ZIP") if(APPLE) list(APPEND ACTIVE_CPACK_GENERATORS "DragNDrop") + if(CMake_TEST_XCODE_VERSION VERSION_GREATER "4.6") + list(APPEND ACTIVE_CPACK_GENERATORS "productbuild") + endif() endif() # set up list of component packaging ways @@ -1105,6 +1112,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release set(CPackComponents_BUILD_OPTIONS) if(APPLE) set(CPackComponents_BUILD_OPTIONS -DCPACK_BINARY_DRAGNDROP:BOOL=ON) + if(CMake_TEST_XCODE_VERSION VERSION_GREATER "4.6") + set(CPackComponents_BUILD_OPTIONS ${CPackComponents_BUILD_OPTIONS} + -DCPACK_BINARY_PRODUCTBUILD:BOOL=ON) + endif() endif() if(NOT NSIS_MAKENSIS_EXECUTABLE) set(CPackComponents_BUILD_OPTIONS ${CPackComponents_BUILD_OPTIONS}