You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
16 KiB
466 lines
16 KiB
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmCPackPKGGenerator.h"
|
|
|
|
#include <vector>
|
|
|
|
#include <cmext/string_view>
|
|
|
|
#include "cmCPackComponentGroup.h"
|
|
#include "cmCPackGenerator.h"
|
|
#include "cmCPackLog.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmValue.h"
|
|
#include "cmXMLWriter.h"
|
|
|
|
cmCPackPKGGenerator::cmCPackPKGGenerator()
|
|
{
|
|
this->componentPackageMethod = ONE_PACKAGE;
|
|
}
|
|
|
|
cmCPackPKGGenerator::~cmCPackPKGGenerator() = default;
|
|
|
|
bool cmCPackPKGGenerator::SupportsComponentInstallation() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int cmCPackPKGGenerator::InitializeInternal()
|
|
{
|
|
cmCPackLogger(cmCPackLog::LOG_DEBUG,
|
|
"cmCPackPKGGenerator::Initialize()" << std::endl);
|
|
|
|
return this->Superclass::InitializeInternal();
|
|
}
|
|
|
|
std::string cmCPackPKGGenerator::GetPackageName(
|
|
const cmCPackComponent& component)
|
|
{
|
|
if (component.ArchiveFile.empty()) {
|
|
std::string packagesDir =
|
|
cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
|
|
return cmStrCat(
|
|
cmSystemTools::GetFilenameWithoutLastExtension(packagesDir), '-',
|
|
component.Name, ".pkg");
|
|
}
|
|
|
|
return cmStrCat(component.ArchiveFile, ".pkg");
|
|
}
|
|
|
|
void cmCPackPKGGenerator::CreateBackground(const char* themeName,
|
|
const char* metapackageFile,
|
|
cm::string_view genName,
|
|
cmXMLWriter& xout)
|
|
{
|
|
std::string paramSuffix =
|
|
(themeName == nullptr) ? "" : cmSystemTools::UpperCase(themeName);
|
|
std::string opt = (themeName == nullptr)
|
|
? cmStrCat("CPACK_", genName, "_BACKGROUND")
|
|
: cmStrCat("CPACK_", genName, "_BACKGROUND_", paramSuffix);
|
|
cmValue bgFileName = this->GetOption(opt);
|
|
if (!bgFileName) {
|
|
return;
|
|
}
|
|
|
|
std::string bgFilePath =
|
|
cmStrCat(metapackageFile, "/Contents/", *bgFileName);
|
|
|
|
if (!cmSystemTools::FileExists(bgFilePath)) {
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
"Background image doesn't exist in the resource directory: "
|
|
<< *bgFileName << std::endl);
|
|
return;
|
|
}
|
|
|
|
if (themeName == nullptr) {
|
|
xout.StartElement("background");
|
|
} else {
|
|
xout.StartElement(cmStrCat("background-", themeName));
|
|
}
|
|
|
|
xout.Attribute("file", *bgFileName);
|
|
|
|
cmValue param = this->GetOption(cmStrCat(opt, "_ALIGNMENT"));
|
|
if (param != nullptr) {
|
|
xout.Attribute("alignment", *param);
|
|
}
|
|
|
|
param = this->GetOption(cmStrCat(opt, "_SCALING"));
|
|
if (param != nullptr) {
|
|
xout.Attribute("scaling", *param);
|
|
}
|
|
|
|
// Apple docs say that you must provide either mime-type or uti
|
|
// attribute for the background, but I've seen examples that
|
|
// doesn't have them, so don't make them mandatory.
|
|
param = this->GetOption(cmStrCat(opt, "_MIME_TYPE"));
|
|
if (param != nullptr) {
|
|
xout.Attribute("mime-type", *param);
|
|
}
|
|
|
|
param = this->GetOption(cmStrCat(opt, "_UTI"));
|
|
if (param != nullptr) {
|
|
xout.Attribute("uti", *param);
|
|
}
|
|
|
|
xout.EndElement();
|
|
}
|
|
|
|
void cmCPackPKGGenerator::WriteDistributionFile(const char* metapackageFile,
|
|
const char* genName)
|
|
{
|
|
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 =
|
|
cmStrCat(metapackageFile, "/Contents/distribution.dist");
|
|
|
|
std::ostringstream xContents;
|
|
cmXMLWriter xout(xContents, 1);
|
|
|
|
// Installer-wide options and domains. These need to be separate from the
|
|
// choices and background elements added further below so that we can
|
|
// preserve backward compatibility.
|
|
xout.StartElement("options");
|
|
xout.Attribute("allow-external-scripts", "no");
|
|
xout.Attribute("customize", "allow");
|
|
if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
|
|
xout.Attribute("rootVolumeOnly", "false");
|
|
}
|
|
xout.EndElement();
|
|
this->CreateDomains(xout);
|
|
|
|
// In order to preserve backward compatibility, all elements added below
|
|
// here need to be made available in a variable named
|
|
// CPACK_PACKAGEMAKER_CHOICES. The above elements are new and only appear
|
|
// in the CPACK_APPLE_PKG_INSTALLER_CONTENT variable, which is a superset
|
|
// of what CPACK_PACKAGEMAKER_CHOICES used to provide. The renaming reflects
|
|
// the fact that CMake has deprecated the PackageMaker generator.
|
|
|
|
// Create the choice outline, which provides a tree-based view of
|
|
// the components in their groups.
|
|
std::ostringstream choiceOut;
|
|
cmXMLWriter xChoiceOut(choiceOut, 1);
|
|
xChoiceOut.StartElement("choices-outline");
|
|
|
|
// Emit the outline for the groups
|
|
for (auto const& group : this->ComponentGroups) {
|
|
if (group.second.ParentGroup == nullptr) {
|
|
CreateChoiceOutline(group.second, xChoiceOut);
|
|
}
|
|
}
|
|
|
|
// Emit the outline for the non-grouped components
|
|
for (auto const& comp : this->Components) {
|
|
if (!comp.second.Group) {
|
|
xChoiceOut.StartElement("line");
|
|
xChoiceOut.Attribute("choice", cmStrCat(comp.first, "Choice"));
|
|
xChoiceOut.Content(""); // Avoid self-closing tag.
|
|
xChoiceOut.EndElement();
|
|
}
|
|
}
|
|
if (!this->PostFlightComponent.Name.empty()) {
|
|
xChoiceOut.StartElement("line");
|
|
xChoiceOut.Attribute("choice",
|
|
cmStrCat(PostFlightComponent.Name, "Choice"));
|
|
xChoiceOut.Content(""); // Avoid self-closing tag.
|
|
xChoiceOut.EndElement();
|
|
}
|
|
xChoiceOut.EndElement(); // choices-outline>
|
|
|
|
// Create the actual choices
|
|
for (auto const& group : this->ComponentGroups) {
|
|
CreateChoice(group.second, xChoiceOut);
|
|
}
|
|
for (auto const& comp : this->Components) {
|
|
CreateChoice(comp.second, xChoiceOut);
|
|
}
|
|
|
|
if (!this->PostFlightComponent.Name.empty()) {
|
|
CreateChoice(PostFlightComponent, xChoiceOut);
|
|
}
|
|
|
|
// default background. These are not strictly part of the choices, but they
|
|
// must be included in CPACK_PACKAGEMAKER_CHOICES to preserve backward
|
|
// compatibility.
|
|
this->CreateBackground(nullptr, metapackageFile, genName, xChoiceOut);
|
|
// Dark Aqua
|
|
this->CreateBackground("darkAqua", metapackageFile, genName, xChoiceOut);
|
|
|
|
// Provide the content for substitution into the template. Support both the
|
|
// old and new variables.
|
|
this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str());
|
|
this->SetOption("CPACK_APPLE_PKG_INSTALLER_CONTENT",
|
|
cmStrCat(xContents.str(), " ", choiceOut.str()));
|
|
|
|
// Create the distribution.dist file in the metapackage to turn it
|
|
// into a distribution package.
|
|
this->ConfigureFile(distributionTemplate, distributionFile);
|
|
}
|
|
|
|
void cmCPackPKGGenerator::CreateChoiceOutline(
|
|
const cmCPackComponentGroup& group, cmXMLWriter& xout)
|
|
{
|
|
xout.StartElement("line");
|
|
xout.Attribute("choice", cmStrCat(group.Name, "Choice"));
|
|
for (cmCPackComponentGroup* subgroup : group.Subgroups) {
|
|
CreateChoiceOutline(*subgroup, xout);
|
|
}
|
|
|
|
for (cmCPackComponent* comp : group.Components) {
|
|
xout.StartElement("line");
|
|
xout.Attribute("choice", cmStrCat(comp->Name, "Choice"));
|
|
xout.Content(""); // Avoid self-closing tag.
|
|
xout.EndElement();
|
|
}
|
|
xout.EndElement();
|
|
}
|
|
|
|
void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group,
|
|
cmXMLWriter& xout)
|
|
{
|
|
xout.StartElement("choice");
|
|
xout.Attribute("id", cmStrCat(group.Name, "Choice"));
|
|
xout.Attribute("title", group.DisplayName);
|
|
xout.Attribute("start_selected", "true");
|
|
xout.Attribute("start_enabled", "true");
|
|
xout.Attribute("start_visible", "true");
|
|
if (!group.Description.empty()) {
|
|
xout.Attribute("description", group.Description);
|
|
}
|
|
xout.EndElement();
|
|
}
|
|
|
|
void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component,
|
|
cmXMLWriter& xout)
|
|
{
|
|
std::string packageId;
|
|
if (cmValue i = this->GetOption("CPACK_PRODUCTBUILD_IDENTIFIER")) {
|
|
packageId = cmStrCat(i, '.', component.Name);
|
|
} else {
|
|
packageId =
|
|
cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"), '.',
|
|
this->GetOption("CPACK_PACKAGE_NAME"), '.', component.Name);
|
|
}
|
|
|
|
xout.StartElement("choice");
|
|
xout.Attribute("id", cmStrCat(component.Name, "Choice"));
|
|
xout.Attribute("title", component.DisplayName);
|
|
xout.Attribute(
|
|
"start_selected",
|
|
component.IsDisabledByDefault && !component.IsRequired ? "false" : "true");
|
|
xout.Attribute("start_enabled", component.IsRequired ? "false" : "true");
|
|
xout.Attribute("start_visible", component.IsHidden ? "false" : "true");
|
|
if (!component.Description.empty()) {
|
|
xout.Attribute("description", 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.
|
|
std::ostringstream selected("my.choice.selected", std::ios_base::ate);
|
|
std::set<const cmCPackComponent*> visited;
|
|
AddDependencyAttributes(component, visited, selected);
|
|
visited.clear();
|
|
AddReverseDependencyAttributes(component, visited, selected);
|
|
xout.Attribute("selected", selected.str());
|
|
}
|
|
xout.StartElement("pkg-ref");
|
|
xout.Attribute("id", packageId);
|
|
xout.EndElement(); // pkg-ref
|
|
xout.EndElement(); // choice
|
|
|
|
// Create a description of the package associated with this
|
|
// component.
|
|
std::string relativePackageLocation =
|
|
cmStrCat("Contents/Packages/", this->GetPackageName(component));
|
|
|
|
// Determine the installed size of the package.
|
|
std::string dirName =
|
|
cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component.Name,
|
|
this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"));
|
|
unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName);
|
|
|
|
xout.StartElement("pkg-ref");
|
|
xout.Attribute("id", packageId);
|
|
xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION"));
|
|
xout.Attribute("installKBytes", installedSize);
|
|
// The auth attribute is deprecated in favor of the domains element
|
|
if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
|
|
xout.Attribute("auth", "Admin");
|
|
}
|
|
xout.Attribute("onConclusion", "None");
|
|
if (component.IsDownloaded) {
|
|
xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE"));
|
|
xout.Content(this->GetPackageName(component));
|
|
} else {
|
|
xout.Content("file:./");
|
|
xout.Content(cmSystemTools::EncodeURL(relativePackageLocation,
|
|
/*escapeSlashes=*/false));
|
|
}
|
|
xout.EndElement(); // pkg-ref
|
|
}
|
|
|
|
void cmCPackPKGGenerator::CreateDomains(cmXMLWriter& xout)
|
|
{
|
|
if (cmIsOff(this->GetOption("CPACK_PRODUCTBUILD_DOMAINS"))) {
|
|
return;
|
|
}
|
|
|
|
xout.StartElement("domains");
|
|
|
|
// Product can be installed at the root of any volume by default
|
|
// unless specified
|
|
cmValue param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ANYWHERE");
|
|
xout.Attribute("enable_anywhere",
|
|
(param && cmIsOff(param)) ? "false" : "true");
|
|
|
|
// Product cannot be installed into the current user's home directory
|
|
// by default unless specified
|
|
param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_USER");
|
|
xout.Attribute("enable_currentUserHome",
|
|
(param && cmIsOn(param)) ? "true" : "false");
|
|
|
|
// Product can be installed into the root directory by default
|
|
// unless specified
|
|
param = this->GetOption("CPACK_PRODUCTBUILD_DOMAINS_ROOT");
|
|
xout.Attribute("enable_localSystem",
|
|
(param && cmIsOff(param)) ? "false" : "true");
|
|
|
|
xout.EndElement();
|
|
}
|
|
|
|
void cmCPackPKGGenerator::AddDependencyAttributes(
|
|
const cmCPackComponent& component,
|
|
std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
|
|
{
|
|
if (visited.find(&component) != visited.end()) {
|
|
return;
|
|
}
|
|
visited.insert(&component);
|
|
|
|
for (cmCPackComponent* depend : component.Dependencies) {
|
|
out << " && choices['" << depend->Name << "Choice'].selected";
|
|
AddDependencyAttributes(*depend, visited, out);
|
|
}
|
|
}
|
|
|
|
void cmCPackPKGGenerator::AddReverseDependencyAttributes(
|
|
const cmCPackComponent& component,
|
|
std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
|
|
{
|
|
if (visited.find(&component) != visited.end()) {
|
|
return;
|
|
}
|
|
visited.insert(&component);
|
|
|
|
for (cmCPackComponent* depend : component.ReverseDependencies) {
|
|
out << " || choices['" << depend->Name << "Choice'].selected";
|
|
AddReverseDependencyAttributes(*depend, visited, out);
|
|
}
|
|
}
|
|
|
|
bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name,
|
|
const std::string& dirName)
|
|
{
|
|
std::string uname = cmSystemTools::UpperCase(name);
|
|
std::string cpackVar = cmStrCat("CPACK_RESOURCE_FILE_", uname);
|
|
cmValue inFileName = this->GetOption(cpackVar);
|
|
if (!inFileName) {
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
"CPack option: "
|
|
<< cpackVar << " not specified. It should point to "
|
|
<< (!name.empty() ? name : "<empty>") << ".rtf, " << name
|
|
<< ".html, or " << name << ".txt file" << std::endl);
|
|
return false;
|
|
}
|
|
if (!cmSystemTools::FileExists(inFileName)) {
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
"Cannot find " << (!name.empty() ? name : "<empty>")
|
|
<< " resource file: " << inFileName
|
|
<< std::endl);
|
|
return false;
|
|
}
|
|
std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
|
|
if (ext != ".rtfd"_s && ext != ".rtf"_s && ext != ".html"_s &&
|
|
ext != ".txt"_s) {
|
|
cmCPackLogger(
|
|
cmCPackLog::LOG_ERROR,
|
|
"Bad file extension specified: "
|
|
<< ext
|
|
<< ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
|
|
<< std::endl);
|
|
return false;
|
|
}
|
|
|
|
std::string destFileName = cmStrCat(dirName, '/', name, ext);
|
|
|
|
// Set this so that distribution.dist gets the right name (without
|
|
// the path).
|
|
this->SetOption(cmStrCat("CPACK_RESOURCE_FILE_", uname, "_NOPATH"),
|
|
cmStrCat(name, ext));
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_VERBOSE,
|
|
"Configure file: " << (inFileName ? *inFileName : "(NULL)")
|
|
<< " to " << destFileName << std::endl);
|
|
this->ConfigureFile(inFileName, destFileName);
|
|
return true;
|
|
}
|
|
|
|
bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name,
|
|
const char* outName)
|
|
{
|
|
if (!outName) {
|
|
outName = name.c_str();
|
|
}
|
|
|
|
std::string inFName = cmStrCat("CPack.", name, ".in");
|
|
std::string inFileName = this->FindTemplate(inFName.c_str());
|
|
if (inFileName.empty()) {
|
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
|
"Cannot find input file: " << inFName << std::endl);
|
|
return false;
|
|
}
|
|
|
|
std::string destFileName =
|
|
cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), '/', outName);
|
|
|
|
cmCPackLogger(cmCPackLog::LOG_VERBOSE,
|
|
"Configure file: " << inFileName << " to " << destFileName
|
|
<< std::endl);
|
|
this->ConfigureFile(inFileName, destFileName);
|
|
return true;
|
|
}
|
|
|
|
int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir,
|
|
const std::string& script,
|
|
const std::string& name)
|
|
{
|
|
std::string dst = cmStrCat(resdir, '/', name);
|
|
cmSystemTools::CopyFileAlways(script, dst);
|
|
cmSystemTools::SetPermissions(dst, 0777);
|
|
cmCPackLogger(cmCPackLog::LOG_VERBOSE,
|
|
"copy script : " << script << "\ninto " << dst << std::endl);
|
|
|
|
return 1;
|
|
}
|