|
|
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
|
|
#include "cmCPackProductBuildGenerator.h"
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
#include <map>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include "cmCPackComponentGroup.h"
|
|
|
|
#include "cmCPackLog.h"
|
|
|
|
#include "cmDuration.h"
|
|
|
|
#include "cmGeneratedFileStream.h"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
#include "cmSystemTools.h"
|
|
|
|
|
|
|
|
cmCPackProductBuildGenerator::cmCPackProductBuildGenerator()
|
|
|
|
{
|
|
|
|
this->componentPackageMethod = ONE_PACKAGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmCPackProductBuildGenerator::~cmCPackProductBuildGenerator() = default;
|
|
|
|
|
|
|
|
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 =
|
|
|
|
cmStrCat(packageDirFileName, "/Contents/Packages");
|
|
|
|
if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str())) {
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
|
|
"Problem creating component packages directory: "
|
|
|
|
<< basePackageDir << std::endl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this->Components.empty()) {
|
|
|
|
std::map<std::string, cmCPackComponent>::iterator compIt;
|
|
|
|
for (compIt = this->Components.begin(); compIt != this->Components.end();
|
|
|
|
++compIt) {
|
|
|
|
std::string packageDir = cmStrCat(toplevel, '/', 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, nullptr)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string resDir = packageDirFileName + "/Contents";
|
|
|
|
|
|
|
|
if (this->IsSet("CPACK_PRODUCTBUILD_RESOURCES_DIR")) {
|
|
|
|
std::string userResDir =
|
|
|
|
this->GetOption("CPACK_PRODUCTBUILD_RESOURCES_DIR");
|
|
|
|
|
|
|
|
if (!cmSystemTools::CopyADirectory(userResDir, resDir)) {
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
|
|
"Problem copying the resource files" << std::endl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy or create all of the resource files we need.
|
|
|
|
if (!this->CopyCreateResourceFile("License", resDir) ||
|
|
|
|
!this->CopyCreateResourceFile("ReadMe", resDir) ||
|
|
|
|
!this->CopyCreateResourceFile("Welcome", resDir)) {
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
|
|
"Problem copying the License, ReadMe and Welcome files"
|
|
|
|
<< std::endl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// combine package(s) into a distribution
|
|
|
|
WriteDistributionFile(packageDirFileName.c_str(), "PRODUCTBUILD");
|
|
|
|
std::ostringstream pkgCmd;
|
|
|
|
|
|
|
|
std::string version = this->GetOption("CPACK_PACKAGE_VERSION");
|
|
|
|
std::string productbuild = this->GetOption("CPACK_COMMAND_PRODUCTBUILD");
|
|
|
|
std::string identityName;
|
|
|
|
if (const char* n = this->GetOption("CPACK_PRODUCTBUILD_IDENTITY_NAME")) {
|
|
|
|
identityName = n;
|
|
|
|
}
|
|
|
|
std::string keychainPath;
|
|
|
|
if (const char* p = this->GetOption("CPACK_PRODUCTBUILD_KEYCHAIN_PATH")) {
|
|
|
|
keychainPath = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
pkgCmd << productbuild << " --distribution \"" << packageDirFileName
|
|
|
|
<< "/Contents/distribution.dist\""
|
|
|
|
<< " --package-path \"" << packageDirFileName << "/Contents/Packages"
|
|
|
|
<< "\""
|
|
|
|
<< " --resources \"" << resDir << "\""
|
|
|
|
<< " --version \"" << version << "\""
|
|
|
|
<< (identityName.empty() ? "" : " --sign \"" + identityName + "\"")
|
|
|
|
<< (keychainPath.empty() ? ""
|
|
|
|
: " --keychain \"" + keychainPath + "\"")
|
|
|
|
<< " \"" << packageFileNames[0] << "\"";
|
|
|
|
|
|
|
|
// Run ProductBuild
|
|
|
|
return RunProductBuild(pkgCmd.str());
|
|
|
|
}
|
|
|
|
|
|
|
|
int cmCPackProductBuildGenerator::InitializeInternal()
|
|
|
|
{
|
|
|
|
this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/Applications");
|
|
|
|
|
|
|
|
std::vector<std::string> 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 = cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"),
|
|
|
|
"/ProductBuildOutput.log");
|
|
|
|
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl);
|
|
|
|
std::string output;
|
|
|
|
int retVal = 1;
|
|
|
|
bool res = cmSystemTools::RunSingleCommand(
|
|
|
|
command, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
|
|
|
|
cmDuration::zero());
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running command" << std::endl);
|
|
|
|
if (!res || retVal) {
|
|
|
|
cmGeneratedFileStream ofs(tmpFile);
|
|
|
|
ofs << "# Run command: " << command << std::endl
|
|
|
|
<< "# Output:" << std::endl
|
|
|
|
<< output << std::endl;
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
|
|
"Problem running command: " << command << std::endl
|
|
|
|
<< "Please check " << tmpFile
|
|
|
|
<< " 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 = cmStrCat(packageFileDir, '/', packageFileName);
|
|
|
|
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_OUTPUT,
|
|
|
|
"- Building component package: " << packageFile
|
|
|
|
<< std::endl);
|
|
|
|
|
|
|
|
const char* comp_name = component ? component->Name.c_str() : nullptr;
|
|
|
|
|
|
|
|
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
|
|
|
|
<< std::endl);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if preflight, postflight, or postupgrade are set
|
|
|
|
// then copy them into the script directory and make
|
|
|
|
// them executable
|
|
|
|
if (preflight) {
|
|
|
|
this->CopyInstallScript(scriptDir, preflight, "preinstall");
|
|
|
|
}
|
|
|
|
if (postflight) {
|
|
|
|
this->CopyInstallScript(scriptDir, postflight, "postinstall");
|
|
|
|
}
|
|
|
|
|
|
|
|
// The command that will be used to run ProductBuild
|
|
|
|
std::ostringstream pkgCmd;
|
|
|
|
|
|
|
|
std::string pkgId = cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"),
|
|
|
|
'.', 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");
|
|
|
|
std::string identityName;
|
|
|
|
if (const char* n = this->GetOption("CPACK_PKGBUILD_IDENTITY_NAME")) {
|
|
|
|
identityName = n;
|
|
|
|
}
|
|
|
|
std::string keychainPath;
|
|
|
|
if (const char* p = this->GetOption("CPACK_PKGBUILD_KEYCHAIN_PATH")) {
|
|
|
|
keychainPath = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
pkgCmd << pkgbuild << " --root \"" << packageDir << "\""
|
|
|
|
<< " --identifier \"" << pkgId << "\""
|
|
|
|
<< " --scripts \"" << scriptDir << "\""
|
|
|
|
<< " --version \"" << version << "\""
|
|
|
|
<< " --install-location \"/\""
|
|
|
|
<< (identityName.empty() ? "" : " --sign \"" + identityName + "\"")
|
|
|
|
<< (keychainPath.empty() ? ""
|
|
|
|
: " --keychain \"" + keychainPath + "\"")
|
|
|
|
<< " \"" << packageFile << "\"";
|
|
|
|
|
|
|
|
if (component && !component->Plist.empty()) {
|
|
|
|
pkgCmd << " --component-plist \"" << component->Plist << "\"";
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|