blob: 94b5b5f7e72cdfb1de78353c03f3d21d494adc1c [file] [log] [blame]
/* 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 <map>
#include <sstream>
#include <stddef.h>
#include "cmCPackComponentGroup.h"
#include "cmCPackLog.h"
#include "cmDuration.h"
#include "cmGeneratedFileStream.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 = packageDirFileName;
basePackageDir += "/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 = 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, 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());
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 = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
tmpFile += "/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 = packageFileDir;
packageFile += '/';
packageFile += 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 = "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");
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);
}