cmCPackPKGGenerator.cxx 13.6 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3 4
#include "cmCPackPKGGenerator.h"

5 6
#include <vector>

7
#include "cmCPackComponentGroup.h"
8
#include "cmCPackGenerator.h"
9
#include "cmCPackLog.h"
10
#include "cmStringAlgorithms.h"
11
#include "cmSystemTools.h"
12
#include "cmXMLWriter.h"
13 14 15 16 17 18

cmCPackPKGGenerator::cmCPackPKGGenerator()
{
  this->componentPackageMethod = ONE_PACKAGE;
}

wahikihiki's avatar
wahikihiki committed
19
cmCPackPKGGenerator::~cmCPackPKGGenerator() = default;
20 21 22 23 24 25 26 27

bool cmCPackPKGGenerator::SupportsComponentInstallation() const
{
  return true;
}

int cmCPackPKGGenerator::InitializeInternal()
{
28 29
  cmCPackLogger(cmCPackLog::LOG_DEBUG,
                "cmCPackPKGGenerator::Initialize()" << std::endl);
30 31 32 33 34 35 36 37

  return this->Superclass::InitializeInternal();
}

std::string cmCPackPKGGenerator::GetPackageName(
  const cmCPackComponent& component)
{
  if (component.ArchiveFile.empty()) {
38 39
    std::string packagesDir =
      cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
40
    std::ostringstream out;
41 42
    out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-"
        << component.Name << ".pkg";
43 44
    return out.str();
  }
45 46

  return component.ArchiveFile + ".pkg";
47 48
}

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
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);
  const char* bgFileName = this->GetOption(opt);
  if (bgFileName == nullptr) {
    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);

  const char* 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)
109 110
{
  std::string distributionTemplate =
111
    this->FindTemplate("Internal/CPack/CPack.distribution.dist.in");
112
  if (distributionTemplate.empty()) {
113 114 115
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "Cannot find input file: " << distributionTemplate
                                             << std::endl);
116 117 118
    return;
  }

119 120
  std::string distributionFile =
    cmStrCat(metapackageFile, "/Contents/distribution.dist");
121 122 123 124

  // Create the choice outline, which provides a tree-based view of
  // the components in their groups.
  std::ostringstream choiceOut;
125 126
  cmXMLWriter xout(choiceOut, 1);
  xout.StartElement("choices-outline");
127 128

  // Emit the outline for the groups
129 130 131
  for (auto const& group : this->ComponentGroups) {
    if (group.second.ParentGroup == nullptr) {
      CreateChoiceOutline(group.second, xout);
132 133 134 135
    }
  }

  // Emit the outline for the non-grouped components
136 137
  for (auto const& comp : this->Components) {
    if (!comp.second.Group) {
138
      xout.StartElement("line");
139
      xout.Attribute("choice", comp.first + "Choice");
140 141
      xout.Content(""); // Avoid self-closing tag.
      xout.EndElement();
142 143 144
    }
  }
  if (!this->PostFlightComponent.Name.empty()) {
145 146 147 148
    xout.StartElement("line");
    xout.Attribute("choice", PostFlightComponent.Name + "Choice");
    xout.Content(""); // Avoid self-closing tag.
    xout.EndElement();
149
  }
150
  xout.EndElement(); // choices-outline>
151 152

  // Create the actual choices
153 154
  for (auto const& group : this->ComponentGroups) {
    CreateChoice(group.second, xout);
155
  }
156 157
  for (auto const& comp : this->Components) {
    CreateChoice(comp.second, xout);
158 159 160
  }

  if (!this->PostFlightComponent.Name.empty()) {
161
    CreateChoice(PostFlightComponent, xout);
162 163
  }

164 165 166 167 168
  // default background
  this->CreateBackground(nullptr, metapackageFile, genName, xout);
  // Dark Aqua
  this->CreateBackground("darkAqua", metapackageFile, genName, xout);

169 170 171 172
  this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str().c_str());

  // Create the distribution.dist file in the metapackage to turn it
  // into a distribution package.
173
  this->ConfigureFile(distributionTemplate, distributionFile);
174 175 176
}

void cmCPackPKGGenerator::CreateChoiceOutline(
177
  const cmCPackComponentGroup& group, cmXMLWriter& xout)
178
{
179 180
  xout.StartElement("line");
  xout.Attribute("choice", group.Name + "Choice");
181 182
  for (cmCPackComponentGroup* subgroup : group.Subgroups) {
    CreateChoiceOutline(*subgroup, xout);
183 184
  }

185
  for (cmCPackComponent* comp : group.Components) {
186
    xout.StartElement("line");
187
    xout.Attribute("choice", comp->Name + "Choice");
188 189
    xout.Content(""); // Avoid self-closing tag.
    xout.EndElement();
190
  }
191
  xout.EndElement();
192 193
}

194
void cmCPackPKGGenerator::CreateChoice(const cmCPackComponentGroup& group,
195
                                       cmXMLWriter& xout)
196
{
197 198 199 200 201 202
  xout.StartElement("choice");
  xout.Attribute("id", group.Name + "Choice");
  xout.Attribute("title", group.DisplayName);
  xout.Attribute("start_selected", "true");
  xout.Attribute("start_enabled", "true");
  xout.Attribute("start_visible", "true");
203
  if (!group.Description.empty()) {
204
    xout.Attribute("description", group.Description);
205
  }
206
  xout.EndElement();
207 208
}

209
void cmCPackPKGGenerator::CreateChoice(const cmCPackComponent& component,
210
                                       cmXMLWriter& xout)
211
{
212 213 214
  std::string packageId =
    cmStrCat("com.", this->GetOption("CPACK_PACKAGE_VENDOR"), '.',
             this->GetOption("CPACK_PACKAGE_NAME"), '.', component.Name);
215

216 217 218 219 220 221 222 223
  xout.StartElement("choice");
  xout.Attribute("id", 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");
224
  if (!component.Description.empty()) {
225
    xout.Attribute("description", component.Description);
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
  }
  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.
243
    std::ostringstream selected("my.choice.selected", std::ios_base::ate);
244
    std::set<const cmCPackComponent*> visited;
245
    AddDependencyAttributes(component, visited, selected);
246
    visited.clear();
247 248
    AddReverseDependencyAttributes(component, visited, selected);
    xout.Attribute("selected", selected.str());
249
  }
250 251 252 253
  xout.StartElement("pkg-ref");
  xout.Attribute("id", packageId);
  xout.EndElement(); // pkg-ref
  xout.EndElement(); // choice
254 255 256

  // Create a description of the package associated with this
  // component.
257 258
  std::string relativePackageLocation =
    cmStrCat("Contents/Packages/", this->GetPackageName(component));
259 260

  // Determine the installed size of the package.
261 262 263
  std::string dirName =
    cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component.Name,
             this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"));
264
  unsigned long installedSize = component.GetInstalledSizeInKbytes(dirName);
265

266 267 268 269 270 271
  xout.StartElement("pkg-ref");
  xout.Attribute("id", packageId);
  xout.Attribute("version", this->GetOption("CPACK_PACKAGE_VERSION"));
  xout.Attribute("installKBytes", installedSize);
  xout.Attribute("auth", "Admin");
  xout.Attribute("onConclusion", "None");
272
  if (component.IsDownloaded) {
273 274
    xout.Content(this->GetOption("CPACK_DOWNLOAD_SITE"));
    xout.Content(this->GetPackageName(component));
275
  } else {
276
    xout.Content("file:./");
277 278
    xout.Content(cmSystemTools::EncodeURL(relativePackageLocation,
                                          /*escapeSlashes=*/false));
279
  }
280
  xout.EndElement(); // pkg-ref
281 282 283 284 285 286 287 288 289 290 291
}

void cmCPackPKGGenerator::AddDependencyAttributes(
  const cmCPackComponent& component,
  std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
{
  if (visited.find(&component) != visited.end()) {
    return;
  }
  visited.insert(&component);

292 293 294
  for (cmCPackComponent* depend : component.Dependencies) {
    out << " && choices['" << depend->Name << "Choice'].selected";
    AddDependencyAttributes(*depend, visited, out);
295 296 297 298 299 300 301 302 303 304 305 306
  }
}

void cmCPackPKGGenerator::AddReverseDependencyAttributes(
  const cmCPackComponent& component,
  std::set<const cmCPackComponent*>& visited, std::ostringstream& out)
{
  if (visited.find(&component) != visited.end()) {
    return;
  }
  visited.insert(&component);

307 308 309
  for (cmCPackComponent* depend : component.ReverseDependencies) {
    out << " || choices['" << depend->Name << "Choice'].selected";
    AddReverseDependencyAttributes(*depend, visited, out);
310 311 312
  }
}

313 314
bool cmCPackPKGGenerator::CopyCreateResourceFile(const std::string& name,
                                                 const std::string& dirName)
315 316 317
{
  std::string uname = cmSystemTools::UpperCase(name);
  std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
318
  const char* inFileName = this->GetOption(cpackVar);
319
  if (!inFileName) {
320 321 322 323 324 325
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "CPack option: " << cpackVar.c_str()
                                   << " not specified. It should point to "
                                   << (!name.empty() ? name : "<empty>")
                                   << ".rtf, " << name << ".html, or " << name
                                   << ".txt file" << std::endl);
326 327 328
    return false;
  }
  if (!cmSystemTools::FileExists(inFileName)) {
329 330 331 332
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "Cannot find " << (!name.empty() ? name : "<empty>")
                                 << " resource file: " << inFileName
                                 << std::endl);
333 334 335 336 337
    return false;
  }
  std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
  if (ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt") {
    cmCPackLogger(
338 339
      cmCPackLog::LOG_ERROR,
      "Bad file extension specified: "
340 341 342 343 344 345
        << ext
        << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
        << std::endl);
    return false;
  }

346
  std::string destFileName = cmStrCat(dirName, '/', name, ext);
347 348 349

  // Set this so that distribution.dist gets the right name (without
  // the path).
350
  this->SetOption("CPACK_RESOURCE_FILE_" + uname + "_NOPATH",
351 352
                  (name + ext).c_str());

353 354 355
  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                "Configure file: " << (inFileName ? inFileName : "(NULL)")
                                   << " to " << destFileName << std::endl);
356
  this->ConfigureFile(inFileName, destFileName);
357 358 359
  return true;
}

360 361
bool cmCPackPKGGenerator::CopyResourcePlistFile(const std::string& name,
                                                const char* outName)
362 363 364 365 366
{
  if (!outName) {
    outName = name.c_str();
  }

367
  std::string inFName = cmStrCat("Internal/CPack/CPack.", name, ".in");
368 369 370 371 372 373 374
  std::string inFileName = this->FindTemplate(inFName.c_str());
  if (inFileName.empty()) {
    cmCPackLogger(cmCPackLog::LOG_ERROR,
                  "Cannot find input file: " << inFName << std::endl);
    return false;
  }

375 376
  std::string destFileName =
    cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), '/', outName);
377

378 379 380
  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                "Configure file: " << inFileName << " to " << destFileName
                                   << std::endl);
381
  this->ConfigureFile(inFileName, destFileName);
382 383 384 385 386 387 388
  return true;
}

int cmCPackPKGGenerator::CopyInstallScript(const std::string& resdir,
                                           const std::string& script,
                                           const std::string& name)
{
389
  std::string dst = cmStrCat(resdir, '/', name);
390
  cmSystemTools::CopyFileAlways(script, dst);
391
  cmSystemTools::SetPermissions(dst.c_str(), 0777);
392 393
  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                "copy script : " << script << "\ninto " << dst << std::endl);
394 395 396

  return 1;
}