1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCPackIFWInstaller.h"
9 #include "cmCPackIFWCommon.h"
10 #include "cmCPackIFWGenerator.h"
11 #include "cmCPackIFWPackage.h"
12 #include "cmCPackIFWRepository.h"
13 #include "cmCPackLog.h" // IWYU pragma: keep
14 #include "cmGeneratedFileStream.h"
15 #include "cmStringAlgorithms.h"
16 #include "cmSystemTools.h"
18 #include "cmXMLParser.h"
19 #include "cmXMLWriter.h"
21 cmCPackIFWInstaller::cmCPackIFWInstaller() = default;
23 void cmCPackIFWInstaller::printSkippedOptionWarning(
24 const std::string& optionName, const std::string& optionValue)
29 << optionName << " contains the value \"" << optionValue
30 << "\" but will be skipped because the specified file does not exist."
34 void cmCPackIFWInstaller::ConfigureFromOptions()
37 if (cmValue optIFW_PACKAGE_NAME =
38 this->GetOption("CPACK_IFW_PACKAGE_NAME")) {
39 this->Name = *optIFW_PACKAGE_NAME;
40 } else if (cmValue optPACKAGE_NAME = this->GetOption("CPACK_PACKAGE_NAME")) {
41 this->Name = *optPACKAGE_NAME;
43 this->Name = "Your package";
47 if (cmValue optIFW_PACKAGE_TITLE =
48 this->GetOption("CPACK_IFW_PACKAGE_TITLE")) {
49 this->Title = *optIFW_PACKAGE_TITLE;
50 } else if (cmValue optPACKAGE_DESCRIPTION_SUMMARY =
51 this->GetOption("CPACK_PACKAGE_DESCRIPTION_SUMMARY")) {
52 this->Title = *optPACKAGE_DESCRIPTION_SUMMARY;
54 this->Title = "Your package description";
58 if (cmValue option = this->GetOption("CPACK_PACKAGE_VERSION")) {
59 this->Version = *option;
61 this->Version = "1.0.0";
65 if (cmValue optIFW_PACKAGE_PUBLISHER =
66 this->GetOption("CPACK_IFW_PACKAGE_PUBLISHER")) {
67 this->Publisher = *optIFW_PACKAGE_PUBLISHER;
68 } else if (cmValue optPACKAGE_VENDOR =
69 this->GetOption("CPACK_PACKAGE_VENDOR")) {
70 this->Publisher = *optPACKAGE_VENDOR;
74 if (cmValue option = this->GetOption("CPACK_IFW_PRODUCT_URL")) {
75 this->ProductUrl = *option;
79 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_ICON")) {
80 if (cmSystemTools::FileExists(option)) {
81 this->InstallerApplicationIcon = *option;
83 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_ICON", option);
88 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_WINDOW_ICON")) {
89 if (cmSystemTools::FileExists(option)) {
90 this->InstallerWindowIcon = *option;
92 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_WINDOW_ICON", option);
97 if (this->IsSetToOff("CPACK_IFW_PACKAGE_REMOVE_TARGET_DIR")) {
98 this->RemoveTargetDir = "false";
99 } else if (this->IsOn("CPACK_IFW_PACKAGE_REMOVE_TARGET_DIR")) {
100 this->RemoveTargetDir = "true";
102 this->RemoveTargetDir.clear();
106 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_LOGO")) {
107 if (cmSystemTools::FileExists(option)) {
108 this->Logo = *option;
110 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_LOGO", option);
115 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_WATERMARK")) {
116 if (cmSystemTools::FileExists(option)) {
117 this->Watermark = *option;
119 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_WATERMARK", option);
124 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_BANNER")) {
125 if (cmSystemTools::FileExists(option)) {
126 this->Banner = *option;
128 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_BANNER", option);
133 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_BACKGROUND")) {
134 if (cmSystemTools::FileExists(option)) {
135 this->Background = *option;
137 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_BACKGROUND", option);
142 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_WIZARD_STYLE")) {
143 // Setting the user value in any case
144 this->WizardStyle = *option;
145 // Check known values
146 if (this->WizardStyle != "Modern" && this->WizardStyle != "Aero" &&
147 this->WizardStyle != "Mac" && this->WizardStyle != "Classic") {
150 "Option CPACK_IFW_PACKAGE_WIZARD_STYLE has unknown value \""
151 << option << "\". Expected values are: Modern, Aero, Mac, Classic."
157 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_STYLE_SHEET")) {
158 if (cmSystemTools::FileExists(option)) {
159 this->StyleSheet = *option;
161 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_STYLE_SHEET", option);
165 // WizardDefaultWidth
167 this->GetOption("CPACK_IFW_PACKAGE_WIZARD_DEFAULT_WIDTH")) {
168 this->WizardDefaultWidth = *option;
171 // WizardDefaultHeight
173 this->GetOption("CPACK_IFW_PACKAGE_WIZARD_DEFAULT_HEIGHT")) {
174 this->WizardDefaultHeight = *option;
177 // WizardShowPageList
179 this->GetOption("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) {
180 if (!this->IsVersionLess("4.0")) {
181 if (this->IsSetToOff("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) {
182 this->WizardShowPageList = "false";
183 } else if (this->IsOn("CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST")) {
184 this->WizardShowPageList = "true";
186 this->WizardShowPageList.clear();
189 std::string currentVersionMsg;
190 if (this->Generator) {
192 "QtIFW version " + this->Generator->FrameworkVersion;
194 currentVersionMsg = "an older QtIFW version";
198 "Option CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST is set to \""
200 << "\", but it is only supported with QtIFW version 4.0 or later. "
201 "It is being ignored because you are using "
202 << currentVersionMsg << std::endl);
207 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_TITLE_COLOR")) {
208 this->TitleColor = *option;
212 if (cmValue optIFW_START_MENU_DIR =
213 this->GetOption("CPACK_IFW_PACKAGE_START_MENU_DIRECTORY")) {
214 this->StartMenuDir = *optIFW_START_MENU_DIR;
216 this->StartMenuDir = this->Name;
219 // Default target directory for installation
220 if (cmValue optIFW_TARGET_DIRECTORY =
221 this->GetOption("CPACK_IFW_TARGET_DIRECTORY")) {
222 this->TargetDir = *optIFW_TARGET_DIRECTORY;
223 } else if (cmValue optPACKAGE_INSTALL_DIRECTORY =
224 this->GetOption("CPACK_PACKAGE_INSTALL_DIRECTORY")) {
226 cmStrCat("@ApplicationsDir@/", optPACKAGE_INSTALL_DIRECTORY);
228 this->TargetDir = "@RootDir@/usr/local";
231 // Default target directory for installation with administrator rights
232 if (cmValue option = this->GetOption("CPACK_IFW_ADMIN_TARGET_DIRECTORY")) {
233 this->AdminTargetDir = *option;
237 if (cmValue optIFW_MAINTENANCE_TOOL =
238 this->GetOption("CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_NAME")) {
239 this->MaintenanceToolName = *optIFW_MAINTENANCE_TOOL;
242 // Maintenance tool ini file
243 if (cmValue optIFW_MAINTENANCE_TOOL_INI =
244 this->GetOption("CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_INI_FILE")) {
245 this->MaintenanceToolIniFile = *optIFW_MAINTENANCE_TOOL_INI;
248 // Allow non-ASCII characters
249 if (this->GetOption("CPACK_IFW_PACKAGE_ALLOW_NON_ASCII_CHARACTERS")) {
250 if (this->IsOn("CPACK_IFW_PACKAGE_ALLOW_NON_ASCII_CHARACTERS")) {
251 this->AllowNonAsciiCharacters = "true";
253 this->AllowNonAsciiCharacters = "false";
257 // DisableCommandLineInterface
258 if (this->GetOption("CPACK_IFW_PACKAGE_DISABLE_COMMAND_LINE_INTERFACE")) {
259 if (this->IsOn("CPACK_IFW_PACKAGE_DISABLE_COMMAND_LINE_INTERFACE")) {
260 this->DisableCommandLineInterface = "true";
261 } else if (this->IsSetToOff(
262 "CPACK_IFW_PACKAGE_DISABLE_COMMAND_LINE_INTERFACE")) {
263 this->DisableCommandLineInterface = "false";
268 if (this->GetOption("CPACK_IFW_PACKAGE_ALLOW_SPACE_IN_PATH")) {
269 if (this->IsOn("CPACK_IFW_PACKAGE_ALLOW_SPACE_IN_PATH")) {
270 this->AllowSpaceInPath = "true";
272 this->AllowSpaceInPath = "false";
277 if (cmValue optIFW_CONTROL_SCRIPT =
278 this->GetOption("CPACK_IFW_PACKAGE_CONTROL_SCRIPT")) {
279 if (!cmSystemTools::FileExists(optIFW_CONTROL_SCRIPT)) {
280 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_CONTROL_SCRIPT",
281 optIFW_CONTROL_SCRIPT);
283 this->ControlScript = *optIFW_CONTROL_SCRIPT;
288 if (cmValue optIFW_PACKAGE_RESOURCES =
289 this->GetOption("CPACK_IFW_PACKAGE_RESOURCES")) {
290 this->Resources.clear();
291 cmExpandList(optIFW_PACKAGE_RESOURCES, this->Resources);
292 for (const auto& file : this->Resources) {
293 if (!cmSystemTools::FileExists(file)) {
294 // The warning will say skipped, but there will later be a hard error
295 // when the binarycreator tool tries to read the missing file.
296 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_RESOURCES", file);
302 if (cmValue productImages =
303 this->GetOption("CPACK_IFW_PACKAGE_PRODUCT_IMAGES")) {
304 this->ProductImages.clear();
305 cmExpandList(productImages, this->ProductImages);
306 for (const auto& file : this->ProductImages) {
307 if (!cmSystemTools::FileExists(file)) {
308 // The warning will say skipped, but there will later be a hard error
309 // when the binarycreator tool tries to read the missing file.
310 this->printSkippedOptionWarning("CPACK_IFW_PACKAGE_PRODUCT_IMAGES",
316 // Run program, run program arguments, and run program description
317 if (cmValue program = this->GetOption("CPACK_IFW_PACKAGE_RUN_PROGRAM")) {
318 this->RunProgram = *program;
320 if (cmValue arguments =
321 this->GetOption("CPACK_IFW_PACKAGE_RUN_PROGRAM_ARGUMENTS")) {
322 this->RunProgramArguments.clear();
323 cmExpandList(arguments, this->RunProgramArguments);
325 if (cmValue description =
326 this->GetOption("CPACK_IFW_PACKAGE_RUN_PROGRAM_DESCRIPTION")) {
327 this->RunProgramDescription = *description;
331 // Code signing identity for signing the generated app bundle
332 if (cmValue id = this->GetOption("CPACK_IFW_PACKAGE_SIGNING_IDENTITY")) {
333 this->SigningIdentity = *id;
338 /** \class cmCPackIFWResourcesParser
339 * \brief Helper class that parse resources form .qrc (Qt)
341 class cmCPackIFWResourcesParser : public cmXMLParser
344 explicit cmCPackIFWResourcesParser(cmCPackIFWInstaller* i)
347 this->path = i->Directory + "/resources";
350 bool ParseResource(size_t r)
352 this->hasFiles = false;
353 this->hasErrors = false;
356 cmSystemTools::GetFilenamePath(this->installer->Resources[r]);
358 this->ParseFile(this->installer->Resources[r].data());
360 return this->hasFiles && !this->hasErrors;
363 cmCPackIFWInstaller* installer;
365 bool hasFiles = false;
366 bool hasErrors = false;
367 std::string path, basePath;
370 void StartElement(const std::string& name, const char** /*atts*/) override
372 this->file = name == "file";
374 this->hasFiles = true;
378 void CharacterDataHandler(const char* data, int length) override
381 std::string content(data, data + length);
382 content = cmTrimWhitespace(content);
383 std::string source = this->basePath + "/" + content;
384 std::string destination = this->path + "/" + content;
385 if (!cmSystemTools::CopyFileIfDifferent(source, destination)) {
386 this->hasErrors = true;
391 void EndElement(const std::string& /*name*/) override {}
394 void cmCPackIFWInstaller::GenerateInstallerFile()
396 // Lazy directory initialization
397 if (this->Directory.empty() && this->Generator) {
398 this->Directory = this->Generator->toplevel;
402 cmGeneratedFileStream fout(this->Directory + "/config/config.xml");
403 cmXMLWriter xout(fout);
405 xout.StartDocument();
407 this->WriteGeneratedByToStrim(xout);
409 xout.StartElement("Installer");
411 xout.Element("Name", this->Name);
412 xout.Element("Version", this->Version);
413 xout.Element("Title", this->Title);
415 if (!this->Publisher.empty()) {
416 xout.Element("Publisher", this->Publisher);
419 if (!this->ProductUrl.empty()) {
420 xout.Element("ProductUrl", this->ProductUrl);
424 if (!this->Logo.empty()) {
425 std::string srcName = cmSystemTools::GetFilenameName(this->Logo);
426 std::string suffix = cmSystemTools::GetFilenameLastExtension(srcName);
427 std::string name = "cm_logo" + suffix;
428 std::string path = this->Directory + "/config/" + name;
429 cmsys::SystemTools::CopyFileIfDifferent(this->Logo, path);
430 xout.Element("Logo", name);
434 if (!this->Banner.empty()) {
435 std::string name = cmSystemTools::GetFilenameName(this->Banner);
436 std::string path = this->Directory + "/config/" + name;
437 cmsys::SystemTools::CopyFileIfDifferent(this->Banner, path);
438 xout.Element("Banner", name);
442 if (!this->Watermark.empty()) {
443 std::string name = cmSystemTools::GetFilenameName(this->Watermark);
444 std::string path = this->Directory + "/config/" + name;
445 cmsys::SystemTools::CopyFileIfDifferent(this->Watermark, path);
446 xout.Element("Watermark", name);
450 if (!this->Background.empty()) {
451 std::string name = cmSystemTools::GetFilenameName(this->Background);
452 std::string path = this->Directory + "/config/" + name;
453 cmsys::SystemTools::CopyFileIfDifferent(this->Background, path);
454 xout.Element("Background", name);
457 // Attributes introduced in QtIFW 1.4.0
458 if (!this->IsVersionLess("1.4")) {
460 if (!this->InstallerApplicationIcon.empty()) {
461 std::string srcName =
462 cmSystemTools::GetFilenameName(this->InstallerApplicationIcon);
463 std::string suffix = cmSystemTools::GetFilenameLastExtension(srcName);
464 std::string name = "cm_appicon" + suffix;
465 std::string path = this->Directory + "/config/" + name;
466 cmsys::SystemTools::CopyFileIfDifferent(this->InstallerApplicationIcon,
468 // The actual file is looked up by attaching a '.icns' (macOS),
469 // '.ico' (Windows). No functionality on Unix.
470 name = cmSystemTools::GetFilenameWithoutExtension(name);
471 xout.Element("InstallerApplicationIcon", name);
475 if (!this->InstallerWindowIcon.empty()) {
476 std::string srcName =
477 cmSystemTools::GetFilenameName(this->InstallerWindowIcon);
478 std::string suffix = cmSystemTools::GetFilenameLastExtension(srcName);
479 std::string name = "cm_winicon" + suffix;
480 std::string path = this->Directory + "/config/" + name;
481 cmsys::SystemTools::CopyFileIfDifferent(this->InstallerWindowIcon, path);
482 xout.Element("InstallerWindowIcon", name);
486 // Attributes introduced in QtIFW 2.0.0
487 if (!this->IsVersionLess("2.0")) {
488 // WizardDefaultWidth
489 if (!this->WizardDefaultWidth.empty()) {
490 xout.Element("WizardDefaultWidth", this->WizardDefaultWidth);
493 // WizardDefaultHeight
494 if (!this->WizardDefaultHeight.empty()) {
495 xout.Element("WizardDefaultHeight", this->WizardDefaultHeight);
498 // Start menu directory
499 if (!this->StartMenuDir.empty()) {
500 xout.Element("StartMenuDir", this->StartMenuDir);
504 if (!this->MaintenanceToolName.empty()) {
505 xout.Element("MaintenanceToolName", this->MaintenanceToolName);
508 // Maintenance tool ini file
509 if (!this->MaintenanceToolIniFile.empty()) {
510 xout.Element("MaintenanceToolIniFile", this->MaintenanceToolIniFile);
513 if (!this->AllowNonAsciiCharacters.empty()) {
514 xout.Element("AllowNonAsciiCharacters", this->AllowNonAsciiCharacters);
516 if (!this->AllowSpaceInPath.empty()) {
517 xout.Element("AllowSpaceInPath", this->AllowSpaceInPath);
520 // Control script (copy to config dir)
521 if (!this->ControlScript.empty()) {
522 std::string name = cmSystemTools::GetFilenameName(this->ControlScript);
523 std::string path = this->Directory + "/config/" + name;
524 cmsys::SystemTools::CopyFileIfDifferent(this->ControlScript, path);
525 xout.Element("ControlScript", name);
528 // CPack IFW default policy
529 xout.Comment("CPack IFW default policy for QtIFW less 2.0");
530 xout.Element("AllowNonAsciiCharacters", "true");
531 xout.Element("AllowSpaceInPath", "true");
535 if (!this->TargetDir.empty()) {
536 xout.Element("TargetDir", this->TargetDir);
540 if (!this->AdminTargetDir.empty()) {
541 xout.Element("AdminTargetDir", this->AdminTargetDir);
544 // Remote repositories
545 if (!this->RemoteRepositories.empty()) {
546 xout.StartElement("RemoteRepositories");
547 for (cmCPackIFWRepository* r : this->RemoteRepositories) {
548 r->WriteRepositoryConfig(xout);
553 // Attributes introduced in QtIFW 3.0.0
554 if (!this->IsVersionLess("3.0")) {
556 if (!this->WizardStyle.empty()) {
557 xout.Element("WizardStyle", this->WizardStyle);
560 // Stylesheet (copy to config dir)
561 if (!this->StyleSheet.empty()) {
562 std::string name = cmSystemTools::GetFilenameName(this->StyleSheet);
563 std::string path = this->Directory + "/config/" + name;
564 cmsys::SystemTools::CopyFileIfDifferent(this->StyleSheet, path);
565 xout.Element("StyleSheet", name);
569 if (!this->TitleColor.empty()) {
570 xout.Element("TitleColor", this->TitleColor);
574 // Attributes introduced in QtIFW 4.0.0
575 if (!this->IsVersionLess("4.0")) {
576 // WizardShowPageList
577 if (!this->WizardShowPageList.empty()) {
578 xout.Element("WizardShowPageList", this->WizardShowPageList);
581 // DisableCommandLineInterface
582 if (!this->DisableCommandLineInterface.empty()) {
583 xout.Element("DisableCommandLineInterface",
584 this->DisableCommandLineInterface);
588 if (!this->RunProgram.empty()) {
589 xout.Element("RunProgram", this->RunProgram);
592 // RunProgramArguments
593 if (!this->RunProgramArguments.empty()) {
594 xout.StartElement("RunProgramArguments");
595 for (const auto& arg : this->RunProgramArguments) {
596 xout.Element("Argument", arg);
601 // RunProgramDescription
602 if (!this->RunProgramDescription.empty()) {
603 xout.Element("RunProgramDescription", this->RunProgramDescription);
607 if (!this->RemoveTargetDir.empty()) {
608 xout.Element("RemoveTargetDir", this->RemoveTargetDir);
611 // Product images (copy to config dir)
612 if (!this->IsVersionLess("4.0") && !this->ProductImages.empty()) {
613 xout.StartElement("ProductImages");
614 for (auto const& srcImg : this->ProductImages) {
615 std::string name = cmSystemTools::GetFilenameName(srcImg);
616 std::string dstImg = this->Directory + "/config/" + name;
617 cmsys::SystemTools::CopyFileIfDifferent(srcImg, dstImg);
618 xout.Element("Image", name);
623 // Resources (copy to resources dir)
624 if (!this->Resources.empty()) {
625 std::vector<std::string> resources;
626 cmCPackIFWResourcesParser parser(this);
627 for (size_t i = 0; i < this->Resources.size(); i++) {
628 if (parser.ParseResource(i)) {
629 std::string name = cmSystemTools::GetFilenameName(this->Resources[i]);
630 std::string path = this->Directory + "/resources/" + name;
631 cmsys::SystemTools::CopyFileIfDifferent(this->Resources[i], path);
632 resources.push_back(std::move(name));
634 cmCPackIFWLogger(WARNING,
635 "Can't copy resources from \""
636 << this->Resources[i]
637 << "\". Resource will be skipped." << std::endl);
640 this->Resources = resources;
647 void cmCPackIFWInstaller::GeneratePackageFiles()
649 if (this->Packages.empty() || this->Generator->IsOnePackage()) {
650 // Generate default package
651 cmCPackIFWPackage package;
652 package.Generator = this->Generator;
653 package.Installer = this;
654 // Check package group
655 if (cmValue option = this->GetOption("CPACK_IFW_PACKAGE_GROUP")) {
656 package.ConfigureFromGroup(option);
657 std::string forcedOption = "CPACK_IFW_COMPONENT_GROUP_" +
658 cmsys::SystemTools::UpperCase(option) + "_FORCED_INSTALLATION";
659 if (!this->GetOption(forcedOption)) {
660 package.ForcedInstallation = "true";
663 package.ConfigureFromOptions();
665 package.GeneratePackageFile();
669 // Generate packages meta information
670 for (auto& p : this->Packages) {
671 cmCPackIFWPackage* package = p.second;
672 package->GeneratePackageFile();